import React from 'react' import { act, render, screen } from '@testing-library/react' import { vi } from 'vitest' import { useContainerQueries } from './useContainerQueries' const ExampleComponent = (): JSX.Element => { const { containerRef, queries } = useContainerQueries() return (
{queries.isSmOrLarger && } {queries.isMdOrLarger && }
) } // Mock ResizeObserver class ResizeObserverMock { callback: ResizeObserverCallback elements: Set constructor(callback: ResizeObserverCallback) { this.callback = callback this.elements = new Set() } observe(target: Element): void { this.elements.add(target) } unobserve(target: Element): void { this.elements.delete(target) } disconnect(): void { this.elements.clear() } // Helper method to trigger resize trigger(width: number): void { const entries: ResizeObserverEntry[] = Array.from(this.elements).map((element) => ({ target: element, contentRect: { width, height: 100, top: 0, left: 0, bottom: 100, right: width, x: 0, y: 0, } as DOMRectReadOnly, borderBoxSize: [ { inlineSize: width, blockSize: 100, }, ], contentBoxSize: [ { inlineSize: width, blockSize: 100, }, ], devicePixelContentBoxSize: [ { inlineSize: width, blockSize: 100, }, ], })) as ResizeObserverEntry[] this.callback(entries, this as unknown as ResizeObserver) } } let resizeObserverInstance: ResizeObserverMock | null = null // Store original values to restore after each test const originalResizeObserver = global.ResizeObserver const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect const setupResizeObserver = (): ResizeObserverMock => { const mockObserver = vi.fn((callback: ResizeObserverCallback) => { resizeObserverInstance = new ResizeObserverMock(callback) return resizeObserverInstance }) global.ResizeObserver = mockObserver as unknown as typeof ResizeObserver return resizeObserverInstance! } describe('useContainerQueries()', () => { beforeEach(() => { resizeObserverInstance = null }) afterEach(() => { vi.restoreAllMocks() // Restore original global/prototype properties global.ResizeObserver = originalResizeObserver Element.prototype.getBoundingClientRect = originalGetBoundingClientRect }) it('shows and hides content based on Tailwind container breakpoints', async () => { setupResizeObserver() // Mock getBoundingClientRect to return a small width initially const mockGetBoundingClientRect = vi.fn(() => ({ width: 300, height: 100, top: 0, left: 0, bottom: 100, right: 300, x: 0, y: 0, // eslint-disable-next-line @typescript-eslint/no-empty-function toJSON: () => {}, })) Element.prototype.getBoundingClientRect = mockGetBoundingClientRect render() // Initially at 300px, should not show sm (384px) or md (448px) content expect(screen.queryByRole('button', { name: /Small query boolean/i })).not.toBeInTheDocument() expect( screen.queryByRole('button', { name: /Medium or larger query/i }), ).not.toBeInTheDocument() // Trigger resize to 400px (sm breakpoint is 384px) await act(async () => { resizeObserverInstance?.trigger(400) // Wait for debounce (1000ms + buffer) await new Promise((resolve) => setTimeout(resolve, 1100)) }) expect(screen.queryByRole('button', { name: /Small query boolean/i })).toBeInTheDocument() expect( screen.queryByRole('button', { name: /Medium or larger query/i }), ).not.toBeInTheDocument() // Trigger resize to 500px (md breakpoint is 448px) await act(async () => { resizeObserverInstance?.trigger(500) // Wait for debounce (1000ms + buffer) await new Promise((resolve) => setTimeout(resolve, 1100)) }) expect(screen.queryByRole('button', { name: /Small query boolean/i })).toBeInTheDocument() expect(screen.queryByRole('button', { name: /Medium or larger query/i })).toBeInTheDocument() }) it('returns SSR-safe defaults when window is undefined', () => { // This test verifies the SSR code path exists // In actual SSR environment, the hook returns safe defaults expect(useContainerQueries).toBeDefined() }) it('cleans up ResizeObserver on unmount', () => { setupResizeObserver() const mockGetBoundingClientRect = vi.fn(() => ({ width: 500, height: 100, top: 0, left: 0, bottom: 100, right: 500, x: 0, y: 0, // eslint-disable-next-line @typescript-eslint/no-empty-function toJSON: () => {}, })) Element.prototype.getBoundingClientRect = mockGetBoundingClientRect const { unmount } = render() const disconnectSpy = vi.spyOn(resizeObserverInstance!, 'disconnect') unmount() expect(disconnectSpy).toHaveBeenCalled() }) it('observes the container element', () => { setupResizeObserver() const mockGetBoundingClientRect = vi.fn(() => ({ width: 500, height: 100, top: 0, left: 0, bottom: 100, right: 500, x: 0, y: 0, // eslint-disable-next-line @typescript-eslint/no-empty-function toJSON: () => {}, })) Element.prototype.getBoundingClientRect = mockGetBoundingClientRect render() // Verify that the ResizeObserver is observing the container expect(resizeObserverInstance?.elements.size).toBe(1) }) })