254 lines
9.4 KiB
TypeScript
254 lines
9.4 KiB
TypeScript
import type { PeekTarget, ShownPeek } from './definitionPeek'
|
|
import type { DefinitionPeek, WebviewToExtension } from './types'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { DefinitionPeeks, linkPeekSource, placePeek } from './definitionPeek'
|
|
import { parseCommentLink } from './repoLink'
|
|
|
|
const DELAY = 300
|
|
const HIDING = 500
|
|
|
|
const PEEK: DefinitionPeek = {
|
|
path: 'app/devices.py',
|
|
line: 41,
|
|
block: { id: '3', anchor: '3', kind: 'class', startLine: 41, endLine: 60, attributes: { kind: 'class' }, concepts: [], children: [], translation: [] },
|
|
names: [],
|
|
}
|
|
|
|
interface Anchor { name: string }
|
|
|
|
function setup(version: number | undefined = 7): {
|
|
peeks: DefinitionPeeks<Anchor>
|
|
sent: WebviewToExtension[]
|
|
shown: () => ShownPeek<Anchor> | undefined
|
|
asks: () => { id: number }[]
|
|
} {
|
|
const sent: WebviewToExtension[] = []
|
|
let current: ShownPeek<Anchor> | undefined
|
|
const peeks = new DefinitionPeeks<Anchor>(msg => sent.push(msg), (next) => {
|
|
current = next
|
|
})
|
|
peeks.setDelays(DELAY, HIDING)
|
|
peeks.setVersion(version)
|
|
const asks = (): { id: number }[] => sent.flatMap(msg => msg.type === 'peekDefinition' || msg.type === 'peekLink' ? [{ id: msg.id }] : [])
|
|
return { peeks, sent, shown: () => current, asks }
|
|
}
|
|
|
|
function name(offset: number): PeekTarget<Anchor> {
|
|
return { anchor: { name: `n${offset}` }, source: { offset } }
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
describe('定义卡片什么时候弹', () => {
|
|
it('停够 editor.hover.delay 才问,问的是这份文档版本里这个下标;回答带卡片才弹', () => {
|
|
const { peeks, sent, shown } = setup()
|
|
const target = name(120)
|
|
peeks.point(target)
|
|
vi.advanceTimersByTime(DELAY - 1)
|
|
expect(sent).toEqual([])
|
|
vi.advanceTimersByTime(1)
|
|
expect(sent).toEqual([{ type: 'peekDefinition', id: 1, version: 7, offset: 120 }])
|
|
peeks.answer(1, PEEK)
|
|
expect(shown()).toEqual({ anchor: target.anchor, peek: PEEK, jump: { type: 'goToDefinition', version: 7, offset: 120 } })
|
|
})
|
|
|
|
it('没到时间就移开:不问', () => {
|
|
const { peeks, sent } = setup()
|
|
peeks.point(name(120))
|
|
vi.advanceTimersByTime(DELAY / 2)
|
|
peeks.point(null)
|
|
vi.advanceTimersByTime(DELAY * 2)
|
|
expect(sent).toEqual([])
|
|
})
|
|
|
|
it('回答没有卡片:不弹', () => {
|
|
const { peeks, shown } = setup()
|
|
peeks.point(name(120))
|
|
vi.advanceTimersByTime(DELAY)
|
|
peeks.answer(1, undefined)
|
|
expect(shown()).toBeUndefined()
|
|
})
|
|
|
|
it('问了之后移开、或换了名字:迟到的回答丢掉', () => {
|
|
const { peeks, shown, asks } = setup()
|
|
peeks.point(name(120))
|
|
vi.advanceTimersByTime(DELAY)
|
|
peeks.point(name(200))
|
|
vi.advanceTimersByTime(DELAY)
|
|
const [first, second] = asks()
|
|
peeks.answer(first.id, PEEK)
|
|
expect(shown()).toBeUndefined()
|
|
peeks.answer(second.id, PEEK)
|
|
expect(shown()?.anchor).toEqual({ name: 'n200' })
|
|
})
|
|
|
|
it('注释里指到符号的链接:问 peekLink,点标题发的是点这个链接时发的 openRepoLink', () => {
|
|
const { peeks, sent, shown } = setup()
|
|
const anchor = { name: 'link' }
|
|
peeks.point({ anchor, source: { path: 'app/devices.py', symbol: 'Repo.find' } })
|
|
vi.advanceTimersByTime(DELAY)
|
|
expect(sent).toEqual([{ type: 'peekLink', id: 1, path: 'app/devices.py', symbol: 'Repo.find' }])
|
|
peeks.answer(1, PEEK)
|
|
peeks.jump()
|
|
expect(sent.at(-1)).toEqual({ type: 'openRepoLink', path: 'app/devices.py', symbol: 'Repo.find' })
|
|
expect(shown()).toBeUndefined()
|
|
})
|
|
|
|
it('不带路径的引用:问 peekLink 和点标题时都带着它写在哪块', () => {
|
|
const { peeks, sent } = setup()
|
|
const anchor = { name: 'reference' }
|
|
peeks.point({ anchor, source: { symbol: 'get_agent_admitted', block: 'class AgentService > function get_default_agent' } })
|
|
vi.advanceTimersByTime(DELAY)
|
|
expect(sent).toEqual([{ type: 'peekLink', id: 1, symbol: 'get_agent_admitted', block: 'class AgentService > function get_default_agent' }])
|
|
peeks.answer(1, PEEK)
|
|
peeks.jump()
|
|
expect(sent.at(-1)).toEqual({ type: 'openRepoLink', symbol: 'get_agent_admitted', block: 'class AgentService > function get_default_agent' })
|
|
})
|
|
|
|
it('还没收到 VS Code 的悬停延迟:不弹', () => {
|
|
const sent: WebviewToExtension[] = []
|
|
const peeks = new DefinitionPeeks<Anchor>(msg => sent.push(msg), () => {})
|
|
peeks.setVersion(1)
|
|
peeks.point(name(5))
|
|
vi.advanceTimersByTime(10_000)
|
|
expect(sent).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('定义卡片什么时候收', () => {
|
|
function showing(): ReturnType<typeof setup> & { target: PeekTarget<Anchor> } {
|
|
const context = setup()
|
|
const target = name(120)
|
|
context.peeks.point(target)
|
|
vi.advanceTimersByTime(DELAY)
|
|
context.peeks.answer(1, PEEK)
|
|
return { ...context, target }
|
|
}
|
|
|
|
it('移开名字:过 editor.hover.hidingDelay 才收,期间移进浮窗就不收', () => {
|
|
const { peeks, shown } = showing()
|
|
peeks.point(null)
|
|
vi.advanceTimersByTime(HIDING - 1)
|
|
expect(shown()).toBeDefined()
|
|
peeks.enterPopup()
|
|
vi.advanceTimersByTime(HIDING * 3)
|
|
expect(shown()).toBeDefined()
|
|
})
|
|
|
|
it('移出浮窗到空白处:过一会儿收;移回名字就不收', () => {
|
|
const { peeks, shown, target } = showing()
|
|
peeks.enterPopup()
|
|
peeks.point(null)
|
|
vi.advanceTimersByTime(HIDING / 2)
|
|
peeks.point(target)
|
|
vi.advanceTimersByTime(HIDING * 3)
|
|
expect(shown()).toBeDefined()
|
|
peeks.point(null)
|
|
vi.advanceTimersByTime(HIDING)
|
|
expect(shown()).toBeUndefined()
|
|
})
|
|
|
|
it('从名字移向浮窗时扫过别的名字:不把收的时间往后推', () => {
|
|
const { peeks, shown } = showing()
|
|
peeks.point(name(300))
|
|
vi.advanceTimersByTime(HIDING / 2)
|
|
peeks.point(name(400))
|
|
vi.advanceTimersByTime(HIDING / 2)
|
|
expect(shown()).toBeUndefined()
|
|
})
|
|
|
|
it('扫过别的名字后进了浮窗:不收,也不弹那个名字的卡片', () => {
|
|
const { peeks, shown, asks } = showing()
|
|
peeks.point(name(300))
|
|
vi.advanceTimersByTime(DELAY / 2)
|
|
peeks.enterPopup()
|
|
vi.advanceTimersByTime(HIDING * 3)
|
|
expect(shown()?.anchor).toEqual({ name: 'n120' })
|
|
expect(asks()).toHaveLength(1)
|
|
})
|
|
|
|
it('指着浮窗里的东西不弹别的卡片', () => {
|
|
const { peeks, asks } = showing()
|
|
peeks.enterPopup()
|
|
vi.advanceTimersByTime(DELAY * 3)
|
|
expect(asks()).toHaveLength(1)
|
|
})
|
|
|
|
it('移到另一个名字、它有卡片:换成它的', () => {
|
|
const { peeks, shown, asks } = showing()
|
|
peeks.point(name(300))
|
|
vi.advanceTimersByTime(DELAY)
|
|
peeks.answer(asks()[1].id, PEEK)
|
|
expect(shown()?.anchor).toEqual({ name: 'n300' })
|
|
vi.advanceTimersByTime(HIDING * 3)
|
|
expect(shown()?.anchor).toEqual({ name: 'n300' })
|
|
})
|
|
|
|
it('移到另一个名字、它没有卡片:原来的照常过一会儿收', () => {
|
|
const { peeks, shown, asks } = showing()
|
|
peeks.point(name(300))
|
|
vi.advanceTimersByTime(DELAY)
|
|
peeks.answer(asks()[1].id, undefined)
|
|
expect(shown()?.anchor).toEqual({ name: 'n120' })
|
|
vi.advanceTimersByTime(HIDING - DELAY)
|
|
expect(shown()).toBeUndefined()
|
|
})
|
|
|
|
it('按 Esc、滚动、按下鼠标(dismiss)立刻收;指针不离开这个名字不再弹', () => {
|
|
const { peeks, shown, asks, target } = showing()
|
|
peeks.dismiss()
|
|
expect(shown()).toBeUndefined()
|
|
peeks.point(target)
|
|
vi.advanceTimersByTime(DELAY * 3)
|
|
expect(asks()).toHaveLength(1)
|
|
})
|
|
|
|
it('换了文档版本:收起,作罢没问完的', () => {
|
|
const { peeks, shown, asks } = showing()
|
|
peeks.point(name(300))
|
|
peeks.setVersion(8)
|
|
expect(shown()).toBeUndefined()
|
|
vi.advanceTimersByTime(DELAY * 3)
|
|
expect(asks()).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('链接能不能弹定义卡片', () => {
|
|
it('指到符号的才能:带 #符号 的仓库链接、不带路径的引用(带着写在哪块)', () => {
|
|
expect(linkPeekSource(parseCommentLink('app/devices.py#Repo.find'))).toEqual({ path: 'app/devices.py', symbol: 'Repo.find' })
|
|
expect(linkPeekSource(parseCommentLink('symbol:DesktopOnly'))).toEqual({ symbol: 'DesktopOnly' })
|
|
expect(linkPeekSource({ symbol: 'DesktopOnly', block: 'function gate' })).toEqual({ symbol: 'DesktopOnly', block: 'function gate' })
|
|
})
|
|
|
|
it('只指到文件、目录的,网址:不能', () => {
|
|
expect(linkPeekSource(parseCommentLink('app/devices.py'))).toBeUndefined()
|
|
expect(linkPeekSource(parseCommentLink('app/'))).toBeUndefined()
|
|
expect(linkPeekSource(parseCommentLink('https://example.com#x'))).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('浮窗放在哪', () => {
|
|
const viewport = { width: 1000, height: 800 }
|
|
|
|
it('下方放得下:贴着名字下边往下长,左边对齐名字', () => {
|
|
expect(placePeek({ top: 100, bottom: 120, left: 300 }, { width: 400, height: 300 }, viewport))
|
|
.toEqual({ top: 124, left: 300, maxWidth: 692, maxHeight: 668 })
|
|
})
|
|
|
|
it('下方放不下、上方空间更大:底边贴着名字上边往上长', () => {
|
|
expect(placePeek({ top: 600, bottom: 620, left: 300 }, { width: 400, height: 300 }, viewport))
|
|
.toEqual({ bottom: 204, left: 300, maxWidth: 692, maxHeight: 588 })
|
|
})
|
|
|
|
it('两边都放不下:放空间大的那边;右边放不下往左挪,钳在视口内', () => {
|
|
expect(placePeek({ top: 300, bottom: 320, left: 900 }, { width: 400, height: 900 }, viewport))
|
|
.toEqual({ top: 324, left: 592, maxWidth: 400, maxHeight: 468 })
|
|
})
|
|
})
|