test: increase strategic coverage
This commit is contained in:
161
src/extensions/zoomCursorFix.behavior.test.ts
Normal file
161
src/extensions/zoomCursorFix.behavior.test.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { defineMock } = vi.hoisted(() => ({
|
||||
defineMock: vi.fn((factory: (view: unknown) => unknown) => factory),
|
||||
}))
|
||||
|
||||
vi.mock('@codemirror/view', () => ({
|
||||
EditorView: class EditorView {},
|
||||
ViewPlugin: {
|
||||
define: defineMock,
|
||||
},
|
||||
}))
|
||||
|
||||
import { getDocumentZoom, zoomCursorFix } from './zoomCursorFix'
|
||||
|
||||
function mockComputedZoom(value: string) {
|
||||
const real = window.getComputedStyle.bind(window)
|
||||
return vi.spyOn(window, 'getComputedStyle').mockImplementation((elt, pseudo) => {
|
||||
const style = real(elt, pseudo)
|
||||
if (elt === document.documentElement) {
|
||||
return new Proxy(style, {
|
||||
get(target, prop) {
|
||||
if (prop === 'zoom') return value
|
||||
const next = Reflect.get(target, prop)
|
||||
return typeof next === 'function' ? next.bind(target) : next
|
||||
},
|
||||
})
|
||||
}
|
||||
return style
|
||||
})
|
||||
}
|
||||
|
||||
function mockInlineZoom(value: string) {
|
||||
return vi.spyOn(document.documentElement.style, 'getPropertyValue').mockImplementation((name: string) => {
|
||||
if (name === 'zoom') return value
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
function createView() {
|
||||
const contentDOM = document.createElement('div')
|
||||
const textNode = document.createTextNode('hello')
|
||||
contentDOM.appendChild(textNode)
|
||||
|
||||
const origPosAtCoords = vi.fn(() => 11)
|
||||
const origPosAndSideAtCoords = vi.fn(() => ({ pos: 13, assoc: -1 as const }))
|
||||
const prototype = {
|
||||
posAtCoords: origPosAtCoords,
|
||||
posAndSideAtCoords: origPosAndSideAtCoords,
|
||||
}
|
||||
|
||||
const view = Object.assign(Object.create(prototype), {
|
||||
contentDOM,
|
||||
posAtDOM: vi.fn(() => 17),
|
||||
})
|
||||
|
||||
return {
|
||||
view,
|
||||
textNode,
|
||||
origPosAtCoords,
|
||||
origPosAndSideAtCoords,
|
||||
}
|
||||
}
|
||||
|
||||
describe('zoomCursorFix behavior', () => {
|
||||
beforeEach(() => {
|
||||
document.documentElement.style.removeProperty('zoom')
|
||||
delete (document as Document & {
|
||||
caretRangeFromPoint?: (x: number, y: number) => Range | null
|
||||
}).caretRangeFromPoint
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('reads inline percentage zoom values when computed zoom is normal', () => {
|
||||
const computedSpy = mockComputedZoom('normal')
|
||||
const inlineSpy = mockInlineZoom('125%')
|
||||
|
||||
expect(getDocumentZoom()).toBe(1.25)
|
||||
|
||||
inlineSpy.mockRestore()
|
||||
computedSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('falls back to 1 for invalid inline zoom values', () => {
|
||||
const computedSpy = mockComputedZoom('normal')
|
||||
const inlineSpy = mockInlineZoom('banana')
|
||||
|
||||
expect(getDocumentZoom()).toBe(1)
|
||||
|
||||
inlineSpy.mockRestore()
|
||||
computedSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('uses caretRangeFromPoint when CSS zoom is active and restores prototype methods on destroy', () => {
|
||||
const computedSpy = mockComputedZoom('normal')
|
||||
const inlineSpy = mockInlineZoom('150%')
|
||||
const { view, textNode } = createView()
|
||||
|
||||
Object.defineProperty(document, 'caretRangeFromPoint', {
|
||||
configurable: true,
|
||||
value: vi.fn(() => ({ startContainer: textNode, startOffset: 2 })),
|
||||
})
|
||||
|
||||
const pluginFactory = zoomCursorFix() as unknown as (view: typeof view) => { destroy: () => void }
|
||||
const plugin = pluginFactory(view)
|
||||
|
||||
expect(view.posAtCoords({ x: 30, y: 40 }, true)).toBe(17)
|
||||
expect(view.posAndSideAtCoords({ x: 30, y: 40 }, false)).toEqual({ pos: 17, assoc: 1 })
|
||||
expect(view.posAtDOM).toHaveBeenCalledWith(textNode, 2)
|
||||
expect(Object.hasOwn(view, 'posAtCoords')).toBe(true)
|
||||
|
||||
plugin.destroy()
|
||||
|
||||
expect(Object.hasOwn(view, 'posAtCoords')).toBe(false)
|
||||
expect(view.posAtCoords({ x: 5, y: 6 }, false)).toBe(11)
|
||||
|
||||
inlineSpy.mockRestore()
|
||||
computedSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('falls back to adjusted coordinates when the browser API is unavailable or returns an unusable range', () => {
|
||||
const computedSpy = mockComputedZoom('normal')
|
||||
const inlineSpy = mockInlineZoom('200%')
|
||||
|
||||
const pluginFactory = zoomCursorFix() as unknown as (view: ReturnType<typeof createView>['view']) => { destroy: () => void }
|
||||
|
||||
const first = createView()
|
||||
pluginFactory(first.view)
|
||||
expect(first.view.posAtCoords({ x: 40, y: 60 }, true)).toBe(11)
|
||||
expect(first.origPosAtCoords).toHaveBeenCalledWith({ x: 20, y: 30 }, true)
|
||||
expect(first.view.posAndSideAtCoords({ x: 40, y: 60 }, false)).toEqual({ pos: 13, assoc: -1 })
|
||||
expect(first.origPosAndSideAtCoords).toHaveBeenCalledWith({ x: 20, y: 30 }, false)
|
||||
|
||||
const second = createView()
|
||||
Object.defineProperty(document, 'caretRangeFromPoint', {
|
||||
configurable: true,
|
||||
value: vi.fn(() => ({ startContainer: document.createTextNode('outside'), startOffset: 0 })),
|
||||
})
|
||||
pluginFactory(second.view)
|
||||
expect(second.view.posAtCoords({ x: 50, y: 70 }, false)).toBe(11)
|
||||
expect(second.origPosAtCoords).toHaveBeenCalledWith({ x: 25, y: 35 }, false)
|
||||
|
||||
const third = createView()
|
||||
third.view.posAtDOM.mockImplementation(() => {
|
||||
throw new Error('boom')
|
||||
})
|
||||
Object.defineProperty(document, 'caretRangeFromPoint', {
|
||||
configurable: true,
|
||||
value: vi.fn(() => ({ startContainer: third.textNode, startOffset: 1 })),
|
||||
})
|
||||
pluginFactory(third.view)
|
||||
expect(third.view.posAtCoords({ x: 60, y: 80 }, false)).toBe(11)
|
||||
expect(third.origPosAtCoords).toHaveBeenCalledWith({ x: 30, y: 40 }, false)
|
||||
|
||||
inlineSpy.mockRestore()
|
||||
computedSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user