Files
superwork/vscode/webview-ui/src/codeReader/TextPreview.test.tsx
T

173 lines
6.9 KiB
TypeScript

// @vitest-environment happy-dom
import type { ReactElement } from 'react'
import type { Root } from 'react-dom/client'
import type { ShownPeek } from './definitionPeek'
import type { DefinitionPeek } from './types'
import { act, useState } from 'react'
import { createRoot } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { DefinitionPeeks, linkPeekSource } from './definitionPeek'
import { DiffText } from './DiffText'
import { MarkdownKindContext, TextPreview } from './TextPreview'
let container: HTMLDivElement
let root: Root
let peeks: DefinitionPeeks<HTMLAnchorElement> | undefined
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
container = document.createElement('div')
document.body.append(container)
root = createRoot(container)
})
afterEach(async () => {
peeks?.dispose()
peeks = undefined
await act(async () => root.unmount())
container.remove()
vi.useRealTimers()
vi.unstubAllGlobals()
})
async function render(element: ReactElement): Promise<void> {
await act(async () => root.render(element))
}
function anchor(): HTMLAnchorElement {
const link = container.querySelector('a')
if (!link)
throw new Error('没有渲染符号链接')
return link
}
describe.each(['TextPreview', 'DiffText'] as const)('%s 的 Markdown 页面元素', (component) => {
function preview(markdown: string): ReactElement {
return component === 'TextPreview'
? <TextPreview text={{ markdown }} className="custom-preview" />
: <DiffText diff={{ markdown: [{ text: markdown }] }} className="custom-preview" />
}
it('父级无关状态更新、传入等值新对象时,保留原链接且仍在页面中', async () => {
function Parent(): ReactElement {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(value => value + 1)}>{count}</button>
{preview('[AsyncOpenAI]')}
</>
)
}
await render(<Parent />)
const original = anchor()
for (let count = 1; count <= 3; count++) {
await act(async () => container.querySelector('button')!.click())
expect(container.querySelector('button')!.textContent).toBe(String(count))
expect(anchor() === original).toBe(true)
expect(original.isConnected).toBe(true)
}
expect(container.querySelector('.cr-markdown.custom-preview')).not.toBeNull()
})
it('正文变化时更新链接和内容', async () => {
await render(preview('[AsyncOpenAI]'))
const original = anchor()
await render(preview('[Client] 新说明'))
expect(anchor().textContent).toBe('Client')
expect(anchor().getAttribute('href')).toBe('symbol:Client')
expect(container.textContent).toContain('新说明')
expect(original.isConnected).toBe(false)
})
it('空 Markdown 与着色代码片段可以来回切换', async () => {
const empty = (): ReactElement => component === 'TextPreview'
? <TextPreview text={{ markdown: '' }} />
: <DiffText diff={{ markdown: [] }} />
await render(preview('[Client]'))
await render(empty())
expect(container.querySelector('.cr-markdown')?.innerHTML).toBe('')
expect(container.querySelector('a')).toBeNull()
await render(component === 'TextPreview'
? <TextPreview text={[{ text: 'client', role: 'variable' }]} />
: <DiffText diff={{ inline: [{ segments: [{ text: 'client', role: 'variable' }], change: 'add' }] }} />)
expect(container.querySelector('.cr-code .cr-role-variable')?.textContent).toBe('client')
expect(container.querySelector('.cr-markdown')).toBeNull()
if (component === 'DiffText')
expect(container.querySelector('[data-change="add"]')?.textContent).toBe('client')
await render(empty())
expect(container.querySelector('.cr-markdown')?.innerHTML).toBe('')
expect(container.querySelector('.cr-code')).toBeNull()
await render(preview('[Client]'))
expect(anchor().textContent).toBe('Client')
})
it('显示定义预览引起父级更新后,继续指向同一链接不重复请求也不关闭', async () => {
vi.useFakeTimers()
const post = vi.fn()
function Parent(): ReactElement {
const [shown, setShown] = useState<ShownPeek<HTMLAnchorElement>>()
const [controller] = useState(() => new DefinitionPeeks<HTMLAnchorElement>(post, setShown))
peeks = controller
return (
<>
{preview('[AsyncOpenAI]')}
<output>{shown ? '预览已显示' : '预览未显示'}</output>
</>
)
}
await render(<Parent />)
const controller = peeks!
controller.setDelays(300, 500)
const point = (): void => {
const link = anchor()
const source = linkPeekSource(link.getAttribute('href')!)
if (!source)
throw new Error('符号链接不能预览')
controller.point({ anchor: link, source })
}
point()
await act(async () => vi.advanceTimersByTime(300))
expect(post).toHaveBeenCalledTimes(1)
expect(post).toHaveBeenLastCalledWith({ type: 'peekLink', id: 1, symbol: 'AsyncOpenAI' })
const peek: DefinitionPeek = {
path: 'client.py',
line: 1,
block: { id: '1', anchor: '1', kind: 'class', startLine: 1, endLine: 2, code: '', codeTokens: [], attributes: { kind: 'class' }, concepts: [], children: [], translation: [] },
names: [],
}
await act(async () => controller.answer(1, peek))
expect(container.querySelector('output')?.textContent).toBe('预览已显示')
point()
await act(async () => vi.advanceTimersByTime(1000))
expect(post).toHaveBeenCalledTimes(1)
expect(container.querySelector('output')?.textContent).toBe('预览已显示')
})
})
it('组件 TextPreview 随注释与定义预览模式切换更新相同正文的链接', async () => {
const preview = (kind: 'comment' | 'peek'): ReactElement => (
<MarkdownKindContext value={kind}>
<TextPreview text={{ markdown: '[Client]' }} />
</MarkdownKindContext>
)
await render(preview('comment'))
const original = anchor()
await render(preview('peek'))
expect(container.querySelector('a')).toBeNull()
expect(container.textContent).toBe('[Client]\n')
expect(original.isConnected).toBe(false)
await render(preview('comment'))
expect(anchor().getAttribute('href')).toBe('symbol:Client')
})
it('组件 DiffText 更新正文时保留 Markdown 排版和增删标记', async () => {
await render(<DiffText diff={{ markdown: [{ text: '[Client] ' }, { text: '旧说明', change: 'del' }, { text: '新说明', change: 'add' }] }} />)
expect(anchor().textContent).toBe('Client')
expect(container.querySelector('del')?.textContent).toBe('旧说明')
expect(container.querySelector('ins')?.textContent).toBe('新说明')
await render(<DiffText diff={{ markdown: [{ text: '[Client] ' }, { text: '**更新**', change: 'add' }] }} />)
expect(container.querySelector('del')).toBeNull()
expect(container.querySelector('ins strong')?.textContent).toBe('更新')
})