fix(workspace): background vector indexing and correct diagram previews

This commit is contained in:
2026-09-06 02:57:56 +08:00
parent 9e0715f9db
commit a5c44c4ac0
21 changed files with 561 additions and 75 deletions
@@ -82,3 +82,59 @@ it('zooms directly in the viewer with bounded speed even for a large wheel delta
expect(Number(dialog.querySelector('output')!.textContent!.replace('%', ''))).toBeLessThanOrEqual(105)
wrapper.unmount()
})
it('starts wheel zoom from the fitted width instead of the intrinsic SVG width', async () => {
const wrapper = mount(DiagramInteractions, { slots: { default: '<div class="markdown-mermaid"><svg viewBox="0 0 4000 2000"></svg></div>' }, attachTo: document.body })
const svg = wrapper.get('svg').element as SVGSVGElement
vi.spyOn(svg, 'getBoundingClientRect').mockReturnValue({ width: 400, height: 200, left: 0, top: 0 } as DOMRect)
await wrapper.get('svg').trigger('mousedown', { button: 1 })
svg.dispatchEvent(new WheelEvent('wheel', { deltaY: -100, bubbles: true, cancelable: true }))
expect(parseFloat(svg.style.width)).toBeGreaterThan(400)
expect(parseFloat(svg.style.width)).toBeLessThanOrEqual(420)
wrapper.unmount()
})
it('keeps the cursor point fixed by adjusting the scroll container during zoom', async () => {
let frame: FrameRequestCallback | undefined
const raf = vi.spyOn(window, 'requestAnimationFrame').mockImplementation(callback => { frame = callback; return 1 })
const cancel = vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {})
const wrapper = mount(DiagramInteractions, { slots: { default: '<div class="markdown-mermaid" style="overflow-x:auto;overflow-y:auto"><svg viewBox="0 0 400 200"></svg></div>' }, attachTo: document.body })
try {
const container = wrapper.get('.markdown-mermaid').element as HTMLElement
const svg = wrapper.get('svg').element as SVGSVGElement
vi.spyOn(svg, 'getBoundingClientRect').mockImplementation(() => {
const width = parseFloat(svg.style.width) || 400
return { width, height: width / 2, left: -container.scrollLeft, top: -container.scrollTop } as DOMRect
})
await wrapper.get('svg').trigger('mousedown', { button: 1, clientX: 100, clientY: 50 })
svg.dispatchEvent(new WheelEvent('wheel', { clientX: 100, clientY: 50, deltaY: -100, bubbles: true, cancelable: true }))
frame?.(performance.now())
const rect = svg.getBoundingClientRect()
expect(rect.left + rect.width * .25).toBeCloseTo(100)
expect(rect.top + rect.height * .25).toBeCloseTo(50)
expect(container.scrollLeft).toBeGreaterThan(0)
} finally {
wrapper.unmount()
raf.mockRestore(); cancel.mockRestore()
}
})
it('fits the full chart on open and clears the previous viewport scroll', async () => {
const wrapper = mount(DiagramInteractions, { slots: { default: '<div class="markdown-mermaid"><svg viewBox="0 0 2400 200"><text>Final task</text></svg><button data-diagram-action="view">View</button></div>' }, attachTo: document.body })
const dialog = document.querySelector('dialog')!
dialog.showModal = vi.fn()
const viewport = dialog.querySelector('.diagram-viewer-scroll') as HTMLElement
Object.defineProperty(viewport, 'clientWidth', { value: 1000 })
Object.defineProperty(viewport, 'clientHeight', { value: 600 })
viewport.scrollLeft = 900
viewport.scrollTop = 30
await wrapper.get('[data-diagram-action="view"]').trigger('click')
await flushPromises()
expect((dialog.querySelector('.diagram-viewer-image') as HTMLElement).style.width).toBe('1000px')
expect(viewport.scrollLeft).toBe(0)
expect(viewport.scrollTop).toBe(0)
expect(dialog.textContent).toContain('Final task')
wrapper.unmount()
})