import React from "react" import { act, render } from "@testing-library/react" import { assert } from "chai" import AutoSizeComponent from "../src/AutoSizeComponent" /** jsdom has no layout engine, so offsetWidth/offsetHeight are always 0. Override them with values * we control so we can drive the component through the size transitions we care about. */ function stubLayout(getSize: () => { width: number; height: number }) { const widthDesc = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth") const heightDesc = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight") Object.defineProperty(HTMLElement.prototype, "offsetWidth", { configurable: true, get: () => getSize().width }) Object.defineProperty(HTMLElement.prototype, "offsetHeight", { configurable: true, get: () => getSize().height }) return () => { Object.defineProperty(HTMLElement.prototype, "offsetWidth", widthDesc!) Object.defineProperty(HTMLElement.prototype, "offsetHeight", heightDesc!) } } /** jsdom implements neither ResizeObserver nor requestAnimationFrame in a way we can drive, so * install stubs that let the test flush measurements synchronously. */ function stubObservers() { const observers: (() => void)[] = [] const rafs: (() => void)[] = [] const origRO = (global as any).ResizeObserver const origRaf = (global as any).requestAnimationFrame const origCancelRaf = (global as any).cancelAnimationFrame ;(global as any).ResizeObserver = class { constructor(private callback: () => void) {} observe() { observers.push(this.callback) } disconnect() {} } ;(global as any).requestAnimationFrame = (fn: () => void) => { rafs.push(fn) return rafs.length } ;(global as any).cancelAnimationFrame = () => {} return { /** Simulate a resize notification followed by the animation frame that services it */ flush: () => { act(() => { observers.forEach((cb) => cb()) const pending = rafs.splice(0) pending.forEach((fn) => fn()) }) }, restore: () => { ;(global as any).ResizeObserver = origRO ;(global as any).requestAnimationFrame = origRaf ;(global as any).cancelAnimationFrame = origCancelRaf } } } describe("AutoSizeComponent", function () { let size = { width: 0, height: 0 } let restoreLayout: () => void let observers: ReturnType beforeEach(function () { size = { width: 0, height: 0 } restoreLayout = stubLayout(() => size) observers = stubObservers() }) afterEach(function () { restoreLayout() observers.restore() }) it("injects the measured width", function () { size = { width: 1200, height: 400 } const result = render( {(s) =>
{`width=${s.width}`}
}
) observers.flush() assert.include(result.container.textContent, "width=1200") }) it("keeps the last known size when hidden, and restores it when shown again", function () { size = { width: 1200, height: 400 } const result = render( {(s) =>
{`width=${s.width}`}
}
) observers.flush() assert.include(result.container.textContent, "width=1200", "should start at the real width") // Hide the subtree (display:none collapses the layout box to 0x0) size = { width: 0, height: 0 } observers.flush() assert.include( result.container.textContent, "width=1200", "a 0x0 layout box means hidden, so the last known width must be kept rather than injecting 0" ) // Show it again size = { width: 1200, height: 400 } observers.flush() assert.include(result.container.textContent, "width=1200") }) it("measures the layout box, so an ancestor transform does not shrink the injected width", function () { // getBoundingClientRect would report the post-transform size here; offsetWidth reports the // untransformed layout box, which is what children rendered inside that transform need. size = { width: 1200, height: 400 } const result = render( {(s) =>
{`width=${s.width}`}
}
) observers.flush() const rect = result.container.querySelector("div")!.getBoundingClientRect() assert.equal(rect.width, 0, "jsdom reports 0 from getBoundingClientRect") assert.include(result.container.textContent, "width=1200", "should not have used getBoundingClientRect") }) })