import { useRef } from 'react'; import { act, cleanup, render, screen } from '@testing-library/react'; import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { usePreviewClearance } from '@/pages/Chat/use-preview-clearance'; let resize: () => void; let bubbleRect: DOMRect; let pageRect: DOMRect; const bubble = document.createElement('div'); const rect = (x: number, y: number, width: number, height: number) => new DOMRect(x, y, width, height); function Harness({ visible = true, open = true }: { visible?: boolean; open?: boolean }) { const panel = useRef(null); const viewport = useRef(null); usePreviewClearance(panel, viewport, visible ? bubble : null, open); return
; } beforeEach(() => { bubbleRect = rect(960, 48, 340, 164); pageRect = rect(192, 184, 1200, 600); vi.stubGlobal('ResizeObserver', class { constructor(callback: () => void) { resize = callback; } observe() {} disconnect() {} }); vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (this: HTMLElement) { if (this === bubble) return bubbleRect; const offset = Number.parseFloat(this.parentElement?.style.marginTop ?? '') || 0; return rect(pageRect.x, pageRect.y + offset, pageRect.width, pageRect.height - offset); }); }); afterEach(() => { cleanup(); vi.restoreAllMocks(); vi.unstubAllGlobals(); }); it('keeps the work at the top when the bubble is beside it in the teacher pane', () => { pageRect = rect(192, 184, 650, 600); render(); expect(screen.getByTestId('panel').style.marginTop).toBe(''); }); it('reserves only the missing native-page clearance and stays stable across repeated resize notifications', () => { render(); expect(screen.getByTestId('panel').style.marginTop).toBe('36px'); act(() => { resize(); resize(); resize(); }); expect(screen.getByTestId('panel').style.marginTop).toBe('36px'); bubbleRect = rect(960, 48, 340, 110); act(() => resize()); expect(screen.getByTestId('panel').style.marginTop).toBe(''); }); it('returns space when the side pane opens and recalculates when it closes', () => { render(); pageRect = rect(192, 184, 650, 600); act(() => resize()); expect(screen.getByTestId('panel').style.marginTop).toBe(''); pageRect = rect(192, 184, 1200, 600); act(() => window.dispatchEvent(new Event('resize'))); expect(screen.getByTestId('panel').style.marginTop).toBe('36px'); }); it('needs no extra space when the existing toolbar already clears the bubble', () => { bubbleRect = rect(960, 48, 340, 110); render(); expect(screen.getByTestId('panel').style.marginTop).toBe(''); }); it('removes clearance when the bubble is dismissed or Work closes', () => { const view = render(); view.rerender(); expect(screen.getByTestId('panel').style.marginTop).toBe(''); view.rerender(); expect(screen.getByTestId('panel').style.marginTop).toBe('36px'); view.rerender(); expect(screen.getByTestId('panel').style.marginTop).toBe(''); });