Files
weichenandCursor d384a3d778 Make the preview pane the only scrolling preview
Why: The pane pinned .ui-fp to the column height, and the docx fit host then clipped overflow, so the right-hand preview could not scroll. The fullscreen DocViewer was a second shell around the same FilePreview.
Evidence: npx vitest run (17 files, 82 tests) and npx tsc --noEmit passed. Headless Chromium laid out a 1400px docx host inside a 764px pane (body scrollHeight 1400) and a pdf iframe that filled that pane.
Impact: .preview-pane-body is the scroll owner. Docx refit also watches page height. The overlay viewer is gone; expand/restore and text edit/save live on the pane.
Refs: Agent-System-Dev#196, Agent-System-Frontend#44
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-05 04:41:20 +00:00

111 lines
3.7 KiB
TypeScript

import { afterEach, describe, it, expect } from 'vitest'
import { computeFitScale, fitPagesToWidth } from '../src/previewfit'
describe('computeFitScale', () => {
it('keeps the page at 100% when the column is wider than the page', () => {
expect(computeFitScale(1100, 794, 24)).toBe(1)
expect(computeFitScale(818, 794, 24)).toBe(1)
})
it('scales the page down to the column width after subtracting the gutter', () => {
expect(computeFitScale(420, 794, 24)).toBeCloseTo((420 - 24) / 794)
expect(computeFitScale(794, 794, 24)).toBeCloseTo((794 - 24) / 794)
})
it('does not invert or zoom when the width is unusable', () => {
expect(computeFitScale(0, 794, 24)).toBe(1)
expect(computeFitScale(20, 794, 24)).toBe(1)
expect(computeFitScale(420, 0, 24)).toBe(1)
expect(computeFitScale(Number.NaN, 794, 24)).toBe(1)
})
})
// node 测试环境没有 DOM。这里只实现 fitPagesToWidth 会碰到的查询与尺寸。
class FakeEl {
style: { height: string; overflow: string; minHeight: string; transform: string; transformOrigin: string } = {
height: '', overflow: '', minHeight: '', transform: '', transformOrigin: '',
}
clientWidth = 0
offsetWidth = 0
offsetHeight = 0
className = ''
tag = 'div'
children: FakeEl[] = []
querySelector(sel: string): FakeEl | null {
const walk = (nodes: FakeEl[]): FakeEl | null => {
for (const node of nodes) {
if (matches(node, sel)) return node
const hit = walk(node.children)
if (hit) return hit
}
return null
}
return walk(this.children)
}
}
function matches(el: FakeEl, sel: string): boolean {
const classes = el.className.split(/\s+/).filter(Boolean)
if (sel.startsWith('.')) return classes.includes(sel.slice(1))
const [tag, cls] = sel.split('.')
return el.tag === tag && classes.includes(cls)
}
class FakeResizeObserver {
static observed: FakeEl[] = []
static instances: FakeResizeObserver[] = []
constructor(private callback: () => void) {
FakeResizeObserver.instances.push(this)
}
observe(el: FakeEl) { FakeResizeObserver.observed.push(el) }
disconnect() { /* dispose 只要求卸下监听 */ }
static fire() {
for (const instance of FakeResizeObserver.instances) instance.callback()
}
static reset() {
FakeResizeObserver.observed = []
FakeResizeObserver.instances = []
}
}
describe('fitPagesToWidth', () => {
const previous = globalThis.ResizeObserver
afterEach(() => {
FakeResizeObserver.reset()
globalThis.ResizeObserver = previous
})
it('recomputes the host height when the page wrapper grows and clears inline styles on dispose', () => {
globalThis.ResizeObserver = FakeResizeObserver as unknown as typeof ResizeObserver
const host = new FakeEl()
host.clientWidth = 420
const wrapper = new FakeEl()
wrapper.className = 'docx-wrapper'
wrapper.offsetHeight = 1000
const page = new FakeEl()
page.tag = 'section'
page.className = 'docx'
page.offsetWidth = 794
wrapper.children.push(page)
host.children.push(wrapper)
const dispose = fitPagesToWidth(host as unknown as HTMLElement, host as unknown as HTMLElement)
const scale = (420 - 24) / 794
expect(host.style.height).toBe(`${Math.round(1000 * scale)}px`)
expect(host.style.overflow).toBe('hidden')
expect(FakeResizeObserver.observed).toContain(wrapper)
wrapper.offsetHeight = 1500
FakeResizeObserver.fire()
expect(host.style.height).toBe(`${Math.round(1500 * scale)}px`)
dispose()
expect(host.style.height).toBe('')
expect(host.style.overflow).toBe('')
expect(host.style.minHeight).toBe('')
expect(wrapper.style.transform).toBe('')
expect(wrapper.style.transformOrigin).toBe('')
})
})