175 lines
6.9 KiB
TypeScript
175 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 { commentLinkOf } from './repoLink'
|
|
import { hashed, NO_SPAN } from './testBlocks'
|
|
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(commentLinkOf(link))
|
|
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: hashed([{ id: '1', anchor: '1', kind: 'class', span: NO_SPAN, attributes: { kind: 'class' }, concepts: [], children: [], translation: [] }])[0],
|
|
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('更新')
|
|
})
|