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)
})
})