/** @jest-environment jsdom */ import { installAutoHeightLayout } from '../autoHeightLayout'; class ResizeObserverMock { static instances: ResizeObserverMock[] = []; callback: () => void; constructor(callback: () => void) { this.callback = callback; ResizeObserverMock.instances.push(this); } observe = jest.fn(); disconnect = jest.fn(); emit() { this.callback(); } } describe('installAutoHeightLayout', () => { let contentHeight: number; let contentSizeListener: (() => void) | undefined; let disposeListener: (() => void) | undefined; let contentSizeDisposable: { dispose: jest.Mock }; let editor: any; let container: HTMLElement; let width: number; let scheduledCallback: FrameRequestCallback | undefined; beforeEach(() => { ResizeObserverMock.instances = []; contentHeight = 20; contentSizeListener = undefined; disposeListener = undefined; scheduledCallback = undefined; (window as any).ResizeObserver = ResizeObserverMock; jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback: FrameRequestCallback) => { scheduledCallback = callback; return 1; }); jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => undefined); width = 300; container = document.createElement('div'); Object.defineProperty(container, 'clientWidth', { configurable: true, get: () => width }); document.body.appendChild(container); contentSizeDisposable = { dispose: jest.fn() }; editor = { getContentHeight: jest.fn(() => contentHeight), layout: jest.fn(), onDidContentSizeChange: jest.fn((listener: () => void) => { contentSizeListener = listener; return contentSizeDisposable; }), onDidDispose: jest.fn((listener: () => void) => { disposeListener = listener; return { dispose: jest.fn() }; }), }; }); afterEach(() => { document.body.innerHTML = ''; jest.restoreAllMocks(); delete (window as any).ResizeObserver; }); it('coalesces frequent content-size events into one layout', () => { installAutoHeightLayout(editor, container); expect(editor.layout).toHaveBeenCalledTimes(1); contentHeight = 40; contentSizeListener?.(); contentSizeListener?.(); contentSizeListener?.(); expect(editor.layout).toHaveBeenCalledTimes(1); scheduledCallback?.(0); expect(container.style.height).toBe('40px'); expect(container.style.width).toBe('100%'); expect(editor.layout).toHaveBeenCalledTimes(2); }); it('does not relayout when an unchanged-size ResizeObserver notification fires', () => { installAutoHeightLayout(editor, container); ResizeObserverMock.instances[0].emit(); expect(window.requestAnimationFrame).not.toHaveBeenCalled(); expect(editor.layout).toHaveBeenCalledTimes(1); }); it('relayouts once after an external width change', () => { installAutoHeightLayout(editor, container); width = 240; ResizeObserverMock.instances[0].emit(); scheduledCallback?.(0); expect(editor.layout).toHaveBeenCalledTimes(2); expect(editor.layout).toHaveBeenLastCalledWith({ width: 240, height: 20 }); }); it('waits for a non-zero width before measuring content', () => { width = 0; installAutoHeightLayout(editor, container); expect(editor.getContentHeight).not.toHaveBeenCalled(); expect(editor.layout).not.toHaveBeenCalled(); expect(container.style.height).toBe(''); width = 300; ResizeObserverMock.instances[0].emit(); scheduledCallback?.(0); expect(editor.layout).toHaveBeenCalledWith({ width: 300, height: 20 }); }); it('supports minHeight sizing and disposes all owned resources', () => { const dispose = installAutoHeightLayout(editor, container, { minHeight: 30, heightStyleProperty: 'minHeight' }); expect(container.style.minHeight).toBe('30px'); contentSizeListener?.(); dispose(); expect(contentSizeDisposable.dispose).toHaveBeenCalledTimes(1); expect(ResizeObserverMock.instances[0].disconnect).toHaveBeenCalledTimes(1); expect(window.cancelAnimationFrame).toHaveBeenCalledWith(1); scheduledCallback?.(0); expect(editor.layout).toHaveBeenCalledTimes(1); disposeListener?.(); expect(contentSizeDisposable.dispose).toHaveBeenCalledTimes(1); }); it('falls back to the container window resize event without ResizeObserver', () => { delete (window as any).ResizeObserver; installAutoHeightLayout(editor, container); window.dispatchEvent(new Event('resize')); scheduledCallback?.(0); expect(editor.layout).toHaveBeenCalledTimes(1); }); });