562 lines
27 KiB
TypeScript
562 lines
27 KiB
TypeScript
import type { BlockView } from '../blockView'
|
|
import type { DiffBase, OpenedFrom } from '../diff/diffBase'
|
|
import type { ProfileChoice } from '../qa/askProfiles'
|
|
import type { RewriteRequest } from '../qa/codeQa'
|
|
import type { SymbolNode } from '../symbolLinks'
|
|
import type { CodeColors } from '../theme/codeColors'
|
|
import type { ExtensionToWebview, WebviewToExtension } from './messages'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { commands, window, workspace } from 'vscode'
|
|
import { documentMessage } from './documentMessage'
|
|
import { documentSymbolNodes } from './documentSymbols'
|
|
import { OCCURRENCES_CONTEXT } from './occurrenceNavigation'
|
|
import { PREVIEW_VIEW_TYPE, PreviewEditorProvider } from './previewEditor'
|
|
|
|
vi.mock('vscode', () => ({
|
|
commands: { executeCommand: vi.fn() },
|
|
window: { showErrorMessage: vi.fn(), registerCustomEditorProvider: vi.fn() },
|
|
workspace: {
|
|
onDidChangeTextDocument: vi.fn(() => ({ dispose: () => {} })),
|
|
onDidChangeConfiguration: () => ({ dispose: () => {} }),
|
|
getConfiguration: () => ({ get: (_key: string, fallback: unknown) => fallback }),
|
|
openTextDocument: vi.fn(),
|
|
applyEdit: vi.fn(async () => true),
|
|
},
|
|
/** 记下换了哪儿、换成什么 */
|
|
WorkspaceEdit: class {
|
|
readonly replaced: { range: unknown, text: string }[] = []
|
|
replace(_uri: unknown, range: unknown, text: string) { this.replaced.push({ range, text }) }
|
|
},
|
|
Range: class {
|
|
constructor(readonly startLine: number, readonly startCharacter: number, readonly endLine: number, readonly endCharacter: number) {}
|
|
},
|
|
EndOfLine: { LF: 1, CRLF: 2 },
|
|
}))
|
|
vi.mock('../../logging/logger', () => ({ logger: { add: vi.fn() } }))
|
|
vi.mock('../../webview/html', () => ({ webviewHtml: () => '', webviewUiRoot: () => ({}) }))
|
|
vi.mock('../folderStore', () => ({ locate: () => ({ folder: { uri: { fsPath: '/workspace' } }, key: 'web/a.ts' }) }))
|
|
vi.mock('./pathPreview', () => ({ PATH_PREVIEW_SCHEME: 'code-reader-path', topSubjectOf: (uri: unknown) => ({ uri, kind: 'file' }) }))
|
|
vi.mock('./occurrences', () => ({ occurrencesOf: vi.fn() }))
|
|
/** 排视图数据不真的解析:一份没有块的文档 */
|
|
vi.mock('./documentMessage', () => ({
|
|
documentMessage: vi.fn(async (input: { languageId: string, version: number }) => ({
|
|
message: { type: 'document', version: input.version, languageId: input.languageId, blocks: [], attributes: [], translators: [], translatorId: '', names: [] },
|
|
displayNames: [],
|
|
symbols: [],
|
|
imports: [],
|
|
kinds: [],
|
|
})),
|
|
}))
|
|
/** 文档符号由各个测试给 */
|
|
vi.mock('./documentSymbols', () => ({ documentSymbolNodes: vi.fn(async () => []) }))
|
|
|
|
const SOURCE = ['// 旧说明', 'export function load() {}']
|
|
|
|
function preview(rewrite: (uri: unknown, request: RewriteRequest) => Promise<string | undefined>, dirty = false) {
|
|
const subscribe = () => ({ dispose: () => {} })
|
|
const receive = vi.fn<(listener: (message: WebviewToExtension) => Promise<void>) => { dispose: () => void }>(subscribe)
|
|
const post = vi.fn<(message: ExtensionToWebview) => void>()
|
|
const uri = { fsPath: '/workspace/web/a.ts', scheme: 'file', toString: () => 'file:///workspace/web/a.ts' }
|
|
const document = { uri, isDirty: dirty, lineAt: (line: number) => ({ text: SOURCE[line] }), lineCount: SOURCE.length, eol: 1 }
|
|
vi.mocked(workspace.openTextDocument).mockResolvedValue(document as never)
|
|
const qa = { imageRoot: () => undefined, onDidChange: subscribe, onDidAnswer: subscribe, onDidRewrite: subscribe, profiles: { ask: { subscribe }, rewrite: { subscribe } }, rewrite: vi.fn(rewrite) }
|
|
const provider = new PreviewEditorProvider(...[
|
|
{},
|
|
{},
|
|
{},
|
|
{ subscribe },
|
|
{ subscribe },
|
|
async () => [],
|
|
qa,
|
|
async () => [],
|
|
async () => [],
|
|
{ onDidChange: subscribe },
|
|
{ onDidChange: subscribe },
|
|
async () => false,
|
|
async () => undefined,
|
|
] as unknown as ConstructorParameters<typeof PreviewEditorProvider>)
|
|
provider.resolveCustomTextEditor(...[
|
|
document,
|
|
{ webview: { onDidReceiveMessage: receive, postMessage: post }, onDidChangeViewState: subscribe, onDidDispose: subscribe },
|
|
] as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>)
|
|
const opened = (provider as unknown as { previews: Set<{ sent: unknown, dispose: () => void }> }).previews.values().next().value!
|
|
opened.sent = { blocks: [{ anchor: 'block', children: [], commentTarget: { kind: 'replace', startLine: 0, endLine: 0 }, startLine: 0, endLine: 1 }] }
|
|
return { send: receive.mock.calls[0]![0], post, qa, dispose: () => opened.dispose() }
|
|
}
|
|
|
|
describe('预览里重写注释', () => {
|
|
it('点按钮:发技能命令、开新会话,记在这一轮上的就是这句命令;开不了时回一条带 askId 的失败,能再试', async () => {
|
|
const error = '重写注释模型不可用:official。请重新选择。'
|
|
const editor = preview(async () => error)
|
|
try {
|
|
await editor.send({ type: 'rewriteComment', askId: 'r1', anchor: 'block', images: [] })
|
|
expect(editor.qa.rewrite).toHaveBeenCalledWith(expect.objectContaining({ fsPath: '/workspace/web/a.ts' }), expect.objectContaining({
|
|
askId: 'r1',
|
|
anchor: 'block',
|
|
question: '/ecc:writing-code-comments web/a.ts:1-2',
|
|
prompt: '/ecc:writing-code-comments web/a.ts:1-2',
|
|
images: [],
|
|
}))
|
|
expect(editor.qa.rewrite.mock.calls[0][1]).not.toHaveProperty('followUp')
|
|
expect(editor.post).toHaveBeenLastCalledWith({ type: 'rewriteEnd', askId: 'r1', status: 'failed', error })
|
|
await editor.send({ type: 'rewriteComment', askId: 'r2', anchor: 'block', images: [] })
|
|
expect(editor.qa.rewrite).toHaveBeenCalledTimes(2)
|
|
}
|
|
finally {
|
|
editor.dispose()
|
|
}
|
|
})
|
|
|
|
it('输入框里说的话:接着这块的重写会话说,带上块现在在哪;会话找不到时开新会话用的是技能命令加这句话', async () => {
|
|
const editor = preview(async () => undefined)
|
|
try {
|
|
const image = { mediaType: 'image/png', base64: 'iVBORw0KGgo=' }
|
|
await editor.send({ type: 'rewriteComment', askId: 'r1', anchor: 'block', request: '加上原型图', images: [image] })
|
|
expect(editor.qa.rewrite.mock.calls[0][1]).toMatchObject({
|
|
question: '加上原型图',
|
|
prompt: '/ecc:writing-code-comments web/a.ts:1-2\n\n加上原型图',
|
|
followUp: 'web/a.ts:1-2\n\n加上原型图',
|
|
images: [image],
|
|
})
|
|
}
|
|
finally {
|
|
editor.dispose()
|
|
}
|
|
})
|
|
|
|
it('模型答完写回:套回原来的 // 换掉那几行;文件还有没保存的修改时不开始', async () => {
|
|
const editor = preview(async () => undefined)
|
|
try {
|
|
await editor.send({ type: 'rewriteComment', askId: 'r1', anchor: 'block', images: [] })
|
|
expect(await editor.qa.rewrite.mock.calls[0][1].writeBack('新说明')).toBeUndefined()
|
|
const [edit] = vi.mocked(workspace.applyEdit).mock.calls.at(-1)! as unknown as [{ replaced: { text: string }[] }]
|
|
expect(edit.replaced.map(item => item.text)).toEqual(['// 新说明'])
|
|
expect(await editor.qa.rewrite.mock.calls[0][1].writeBack(undefined)).toBe('模型没有按要求交回注释正文,没有改文件。')
|
|
}
|
|
finally {
|
|
editor.dispose()
|
|
}
|
|
const dirty = preview(async () => undefined, true)
|
|
try {
|
|
await dirty.send({ type: 'rewriteComment', askId: 'r1', anchor: 'block', images: [] })
|
|
expect(dirty.qa.rewrite).not.toHaveBeenCalled()
|
|
expect(dirty.post).toHaveBeenLastCalledWith({ type: 'rewriteEnd', askId: 'r1', status: 'failed', error: '文件还有没保存的修改,模型读的是磁盘上的文件:先保存再重写注释。' })
|
|
}
|
|
finally {
|
|
dirty.dispose()
|
|
}
|
|
})
|
|
})
|
|
|
|
const HEAD_BASE: DiffBase = { label: '上一次提交', key: 'head:web/a.ts', read: async () => '// 改之前', missing: '' }
|
|
|
|
const OPTIONS = [{ id: 'offical', label: 'offical' }, { id: 'sonnet', label: 'sonnet' }, { id: 'dsflash', label: 'dsflash' }]
|
|
|
|
/** 一套 profile 选择:眼下选的是 selected,publish 模拟它变了、通知订阅的预览 */
|
|
function profileSet(selected: string) {
|
|
const listeners = new Set<(choice: ProfileChoice) => void>()
|
|
return {
|
|
subscribe: (listener: (choice: ProfileChoice) => void) => {
|
|
listeners.add(listener)
|
|
return { dispose: () => listeners.delete(listener) }
|
|
},
|
|
choice: async (): Promise<ProfileChoice> => ({ options: OPTIONS, selected }),
|
|
select: vi.fn(async (_id: string) => {}),
|
|
publish: (choice: ProfileChoice) => listeners.forEach(listener => listener(choice)),
|
|
}
|
|
}
|
|
|
|
const COLORS = { roles: { keyword: '#C586C0' }, kinds: { loop: '#C586C0' } } as unknown as CodeColors
|
|
|
|
/** 共用的代码配色:眼下是 colors,publish 模拟换了颜色主题、通知订阅的预览 */
|
|
function themeColors(colors: CodeColors) {
|
|
const listeners = new Set<(value: CodeColors) => void>()
|
|
let current = colors
|
|
return {
|
|
current: async () => current,
|
|
subscribe: (listener: (value: CodeColors) => void) => {
|
|
listeners.add(listener)
|
|
return { dispose: () => listeners.delete(listener) }
|
|
},
|
|
publish: (value: CodeColors) => {
|
|
current = value
|
|
listeners.forEach(listener => listener(value))
|
|
},
|
|
}
|
|
}
|
|
|
|
/** 对比基准只认从「未提交的更改」点开的:从那儿点开的对比上一次提交,别处打开的不比 */
|
|
function opening(profiles = { ask: profileSet(''), rewrite: profileSet('') }, colors = themeColors(COLORS)) {
|
|
const subscribe = () => ({ dispose: () => {} })
|
|
const diffBase = vi.fn((_uri: unknown, from: OpenedFrom): DiffBase | undefined => from === 'uncommitted' ? HEAD_BASE : undefined)
|
|
const qa = {
|
|
imageRoot: () => undefined,
|
|
onDidChange: subscribe,
|
|
onDidAnswer: subscribe,
|
|
onDidRewrite: subscribe,
|
|
profiles,
|
|
view: () => ({ type: 'qa' }),
|
|
}
|
|
const provider = new PreviewEditorProvider(...[
|
|
{},
|
|
{},
|
|
{},
|
|
{ subscribe, current: { excluded: {}, onlyChanges: false } },
|
|
colors,
|
|
async () => [],
|
|
qa,
|
|
async () => [],
|
|
async () => [],
|
|
{ diffBase, onDidChange: subscribe },
|
|
{ namesOf: () => [], onDidChange: subscribe },
|
|
async () => false,
|
|
] as unknown as ConstructorParameters<typeof PreviewEditorProvider>)
|
|
const uri = { fsPath: '/workspace/web/a.ts', scheme: 'file', toString: () => 'file:///workspace/web/a.ts' }
|
|
const document = { uri, version: 1, languageId: 'typescript', fileName: '/workspace/web/a.ts', getText: () => SOURCE.join('\n') }
|
|
return { provider, document, diffBase }
|
|
}
|
|
|
|
/** 一个标签页:收发消息;切走、切回来时页面留着,VS Code 只报一声可见性变了 */
|
|
function tab() {
|
|
let receive: ((message: WebviewToExtension) => Promise<void>) | undefined
|
|
const viewStateListeners: (() => void)[] = []
|
|
const post = vi.fn<(message: ExtensionToWebview) => void>()
|
|
const panel = {
|
|
active: true,
|
|
visible: true,
|
|
webview: {
|
|
options: {},
|
|
postMessage: post,
|
|
asWebviewUri: (file: unknown) => file,
|
|
onDidReceiveMessage: (listener: typeof receive) => {
|
|
receive = listener
|
|
return { dispose: () => {} }
|
|
},
|
|
},
|
|
onDidChangeViewState: (listener: () => void) => {
|
|
viewStateListeners.push(listener)
|
|
return { dispose: () => {} }
|
|
},
|
|
onDidDispose: () => ({ dispose: () => {} }),
|
|
}
|
|
const setVisible = (visible: boolean): void => {
|
|
panel.visible = visible
|
|
panel.active = visible
|
|
viewStateListeners.forEach(listener => listener())
|
|
}
|
|
return {
|
|
panel: panel as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>[1],
|
|
send: (message: WebviewToExtension) => receive!(message),
|
|
hide: () => setVisible(false),
|
|
show: () => setVisible(true),
|
|
posted: () => post.mock.calls.map(([message]) => message),
|
|
clear: () => post.mockClear(),
|
|
}
|
|
}
|
|
|
|
/** 别处改了这份文件,文档变成 version 版:交给最近打开的预览听着的那个回调 */
|
|
function edit(document: { version: number }, version: number): void {
|
|
document.version = version
|
|
const changed = vi.mocked(workspace.onDidChangeTextDocument).mock.calls.at(-1)![0]
|
|
changed({ document, contentChanges: [{ rangeOffset: 0, rangeLength: 0, text: '// ' }] } as never)
|
|
}
|
|
|
|
const comparedWithHead = expect.objectContaining({ type: 'diff', diff: expect.objectContaining({ base: '上一次提交' }) })
|
|
|
|
describe('预览对比什么看这个标签页是从哪儿打开的', () => {
|
|
it('从「未提交的更改」点开的新预览对比上一次提交,页面记下是从哪儿打开的', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready' })
|
|
|
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
})
|
|
|
|
it('跳定义、点链接打到开着的预览不改它对比什么、只滚过去;要新开的预览不比', async () => {
|
|
const { provider, document, diffBase } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
|
|
page.clear()
|
|
await provider.open(document.uri as never, { reveal: { line: 1 } }, async () => {})
|
|
expect(page.posted()).toEqual([{ type: 'reveal', line: 1 }])
|
|
expect(provider.openedFrom(document.uri as never)).toBe('uncommitted')
|
|
|
|
diffBase.mockClear()
|
|
const fresh = tab()
|
|
await provider.open(document.uri as never, { reveal: { line: 1 } }, async () => provider.resolveCustomTextEditor(document as never, fresh.panel))
|
|
await fresh.send({ type: 'ready' })
|
|
expect(fresh.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
|
|
expect(diffBase).toHaveBeenCalledWith(document.uri, 'other')
|
|
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
|
|
})
|
|
|
|
it('开着的预览再从主栏点开就不比,再从「未提交的更改」点开又比;页面重新加载后按它记着的照旧比', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
|
|
page.clear()
|
|
await provider.open(document.uri as never, { from: 'other' }, async () => {})
|
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual({ type: 'diff', diff: null }))
|
|
|
|
page.clear()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => {})
|
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
|
|
page.clear()
|
|
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
|
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
})
|
|
|
|
it('没带打开请求的(VS Code 重启后恢复的)按页面记着的来,页面没记过的不比', async () => {
|
|
const { provider, document, diffBase } = opening()
|
|
const restored = tab()
|
|
provider.resolveCustomTextEditor(document as never, restored.panel)
|
|
await restored.send({ type: 'ready', openedFrom: 'uncommitted' })
|
|
await vi.waitFor(() => expect(restored.posted()).toContainEqual(comparedWithHead))
|
|
|
|
diffBase.mockClear()
|
|
const fresh = tab()
|
|
provider.resolveCustomTextEditor(document as never, fresh.panel)
|
|
await fresh.send({ type: 'ready' })
|
|
expect(fresh.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
|
|
expect(diffBase).toHaveBeenCalledWith(document.uri, 'other')
|
|
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
|
|
})
|
|
|
|
it('打开请求说的从哪儿打开比页面记着的优先', async () => {
|
|
const { provider, document, diffBase } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'other' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
|
|
|
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
|
|
expect(diffBase).toHaveBeenCalledWith(document.uri, 'other')
|
|
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
|
|
})
|
|
})
|
|
|
|
describe('标签页切走时页面留着', () => {
|
|
it('注册时就让页面在标签页切走后留着', () => {
|
|
const { provider } = opening()
|
|
provider.register()
|
|
expect(window.registerCustomEditorProvider).toHaveBeenCalledWith(PREVIEW_VIEW_TYPE, provider, { webviewOptions: { retainContextWhenHidden: true } })
|
|
})
|
|
|
|
it('切走再切回来:不重新生成文档、什么都不重发,高亮照旧能用键盘跳', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
await page.send({ type: 'occurrenceNavigation', available: true })
|
|
|
|
page.clear()
|
|
vi.mocked(documentMessage).mockClear()
|
|
vi.mocked(commands.executeCommand).mockClear()
|
|
page.hide()
|
|
page.show()
|
|
// 比连续输入合并重新解析的那一下(150ms)等得久
|
|
await new Promise(resolve => setTimeout(resolve, 200))
|
|
expect(documentMessage).not.toHaveBeenCalled()
|
|
expect(page.posted()).toEqual([])
|
|
expect(commands.executeCommand).not.toHaveBeenCalledWith('setContext', OCCURRENCES_CONTEXT, false)
|
|
})
|
|
|
|
it('切走期间文件在别处改了:不生成、不发文档,切回来生成一次发出去', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
page.hide()
|
|
page.clear()
|
|
vi.mocked(documentMessage).mockClear()
|
|
edit(document, 2)
|
|
edit(document, 3)
|
|
await new Promise(resolve => setTimeout(resolve, 200))
|
|
expect(documentMessage).not.toHaveBeenCalled()
|
|
expect(page.posted()).toEqual([])
|
|
|
|
page.show()
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'document', version: 3 })))
|
|
expect(documentMessage).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('改完还没来得及生成就切走了:排着的那次不在后台跑,切回来再生成', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
page.clear()
|
|
vi.mocked(documentMessage).mockClear()
|
|
edit(document, 2)
|
|
page.hide()
|
|
await new Promise(resolve => setTimeout(resolve, 200))
|
|
expect(documentMessage).not.toHaveBeenCalled()
|
|
|
|
page.show()
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'document', version: 2 })))
|
|
expect(documentMessage).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('切回来时还要滚到别处(跳定义打到它):等补发的文档一起带过去,不先滚旧的', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
page.hide()
|
|
edit(document, 2)
|
|
await new Promise(resolve => setTimeout(resolve, 200))
|
|
page.clear()
|
|
// 补生成要一会儿:跳过来的位置在生成完之前就到了
|
|
const generate = vi.mocked(documentMessage).getMockImplementation()!
|
|
vi.mocked(documentMessage).mockImplementationOnce(async (...args) => {
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
|
return generate(...args)
|
|
})
|
|
await provider.open(document.uri as never, { reveal: { line: 1 } }, async () => page.show())
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'document', version: 2, reveal: { line: 1 } })))
|
|
expect(page.posted()).not.toContainEqual(expect.objectContaining({ type: 'reveal' }))
|
|
})
|
|
|
|
it('分屏同时看着:文件改了照常马上更新', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
page.clear()
|
|
edit(document, 2)
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'document', version: 2 })))
|
|
})
|
|
|
|
it('页面真的重新加载了(webview 进程崩了重建、开发者重载):再发 ready 时照旧整份发一遍', async () => {
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
|
await page.send({ type: 'ready' })
|
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
|
|
|
page.clear()
|
|
vi.mocked(documentMessage).mockClear()
|
|
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
|
|
expect(documentMessage).toHaveBeenCalled()
|
|
expect(page.posted().map(message => message.type)).toEqual(expect.arrayContaining(['openedFrom', 'filter', 'qa', 'codeColors', 'document']))
|
|
await vi.waitFor(() => {
|
|
expect(page.posted()).toContainEqual(comparedWithHead)
|
|
expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'ask' }))
|
|
expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'rewrite' }))
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('代码配色跟着当前颜色主题', () => {
|
|
it('ready 后先于第一份文档发;换了主题发给 ready 了的预览,还没 ready 的等 ready 时发眼下的', async () => {
|
|
const colors = themeColors(COLORS)
|
|
const { provider, document } = opening(undefined, colors)
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
const light = { roles: { keyword: '#AF00DB' }, kinds: { loop: '#AF00DB' } } as unknown as CodeColors
|
|
colors.publish(light)
|
|
expect(page.posted()).toEqual([])
|
|
|
|
await page.send({ type: 'ready' })
|
|
const types = page.posted().map(message => message.type)
|
|
expect(page.posted()).toContainEqual({ type: 'codeColors', ...light })
|
|
expect(types.indexOf('codeColors')).toBeGreaterThanOrEqual(0)
|
|
expect(types.indexOf('codeColors')).toBeLessThan(types.indexOf('document'))
|
|
|
|
page.clear()
|
|
colors.publish(COLORS)
|
|
expect(page.posted()).toEqual([{ type: 'codeColors', ...COLORS }])
|
|
})
|
|
})
|
|
|
|
describe('问 AI 和重写注释的 profile 下拉各管各的', () => {
|
|
it('ready 后两套各发一次;哪一套变了只发哪一套', async () => {
|
|
const profiles = { ask: profileSet('sonnet'), rewrite: profileSet('dsflash') }
|
|
const { provider, document } = opening(profiles)
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
await vi.waitFor(() => {
|
|
expect(page.posted()).toContainEqual({ type: 'profiles', kind: 'ask', options: OPTIONS, selected: 'sonnet' })
|
|
expect(page.posted()).toContainEqual({ type: 'profiles', kind: 'rewrite', options: OPTIONS, selected: 'dsflash' })
|
|
})
|
|
|
|
page.clear()
|
|
profiles.rewrite.publish({ options: OPTIONS, selected: 'offical' })
|
|
expect(page.posted()).toContainEqual({ type: 'profiles', kind: 'rewrite', options: OPTIONS, selected: 'offical' })
|
|
expect(page.posted()).not.toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'ask' }))
|
|
})
|
|
|
|
it('下拉换了哪一套就只改哪一套;换不成时报错,并把那一套眼下的选择发回去', async () => {
|
|
const profiles = { ask: profileSet('sonnet'), rewrite: profileSet('dsflash') }
|
|
const { provider, document } = opening(profiles)
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
await page.send({ type: 'selectProfile', kind: 'rewrite', id: 'offical' })
|
|
expect(profiles.rewrite.select).toHaveBeenCalledWith('offical')
|
|
expect(profiles.ask.select).not.toHaveBeenCalled()
|
|
|
|
profiles.ask.select.mockRejectedValueOnce(new Error('问答模型不可用:gone。请重新选择。'))
|
|
page.clear()
|
|
await page.send({ type: 'selectProfile', kind: 'ask', id: 'gone' })
|
|
expect(vi.mocked(window.showErrorMessage)).toHaveBeenCalledWith('问答模型不可用:gone。请重新选择。')
|
|
expect(page.posted()).toContainEqual({ type: 'profiles', kind: 'ask', options: OPTIONS, selected: 'sonnet' })
|
|
expect(page.posted()).not.toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'rewrite' }))
|
|
})
|
|
})
|
|
|
|
describe('注释里不带路径的引用按它写在哪块的注释里找', () => {
|
|
const METHOD = 'class AgentService > function get_default_agent'
|
|
const HELPER = 'function helper'
|
|
|
|
/** 一块有名字的代码:名字在源码里的下标是 at */
|
|
function declared(anchor: string, at: number, children: BlockView[] = []): BlockView {
|
|
return { id: anchor, anchor, kind: 'function', startLine: 0, endLine: 0, headerEndLine: 0, code: '', codeTokens: [], defines: { startIndex: at, endIndex: at + 1 }, attributes: { kind: 'function' }, concepts: [], children, translation: [] }
|
|
}
|
|
function symbol(name: string, start: number, end: number, children: SymbolNode[] = []): SymbolNode {
|
|
return { name, range: { startIndex: start, endIndex: end }, selection: { startIndex: start, endIndex: start + name.length }, children }
|
|
}
|
|
|
|
it('同一个名字:方法说明里的找得到同类的另一个方法,顶层函数说明里的找不到;标坏链接、点了打开都按各自那块找', async () => {
|
|
const blocks = [declared('class AgentService', 100, [declared(METHOD, 400)]), declared(HELPER, 950)]
|
|
vi.mocked(documentMessage).mockImplementationOnce(async input => ({
|
|
message: { type: 'document', version: input.version, languageId: input.languageId, blocks, attributes: [], translators: [], translatorId: '', names: [] },
|
|
displayNames: [],
|
|
symbols: [],
|
|
imports: [],
|
|
}) as never)
|
|
vi.mocked(documentSymbolNodes).mockResolvedValue([
|
|
symbol('AgentService', 100, 900, [symbol('get_agent_admitted', 200, 300), symbol('get_default_agent', 400, 500)]),
|
|
symbol('helper', 950, 990),
|
|
])
|
|
const { provider, document } = opening()
|
|
const page = tab()
|
|
provider.resolveCustomTextEditor(document as never, page.panel)
|
|
await page.send({ type: 'ready' })
|
|
|
|
await page.send({ type: 'checkRepoLinks', id: 1, paths: [], references: [{ symbol: 'get_agent_admitted', block: METHOD }, { symbol: 'get_agent_admitted', block: HELPER }] })
|
|
expect(page.posted()).toContainEqual({ type: 'repoLinksChecked', id: 1, missing: [], unresolved: [{ symbol: 'get_agent_admitted', block: HELPER }] })
|
|
|
|
await page.send({ type: 'openRepoLink', symbol: 'get_agent_admitted', block: HELPER })
|
|
expect(page.posted()).toContainEqual({ type: 'repoLinkBroken', symbol: 'get_agent_admitted', block: HELPER })
|
|
})
|
|
})
|