mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
248 lines
6.0 KiB
TypeScript
248 lines
6.0 KiB
TypeScript
import { mkdtempSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
|
|
type FakeUri = {
|
|
scheme: string
|
|
fsPath: string
|
|
path: string
|
|
query: string
|
|
toString(): string
|
|
}
|
|
|
|
type FakeDocument = {
|
|
uri: FakeUri
|
|
isDirty: boolean
|
|
lineCount: number
|
|
lineAt(index: number): { text: string }
|
|
getText(): string
|
|
setText(next: string): void
|
|
}
|
|
|
|
function createFakeUri(
|
|
scheme: string,
|
|
fsPath: string,
|
|
query = '',
|
|
): FakeUri {
|
|
const normalizedFsPath = fsPath.replaceAll('\\', '/')
|
|
return {
|
|
scheme,
|
|
fsPath,
|
|
path: fsPath,
|
|
query,
|
|
toString() {
|
|
if (scheme === 'file') {
|
|
return `file://${normalizedFsPath}`
|
|
}
|
|
return `${scheme}:/${normalizedFsPath}${query ? `?${query}` : ''}`
|
|
},
|
|
}
|
|
}
|
|
|
|
function createFakeVscode() {
|
|
const documents = new Map<string, FakeDocument>()
|
|
const saveListeners = new Set<(document: FakeDocument) => void>()
|
|
const visibleEditorListeners = new Set<(editors: any[]) => void>()
|
|
const visibleTextEditors: any[] = []
|
|
|
|
function createDocument(uri: FakeUri, initialText = ''): FakeDocument {
|
|
let text = initialText
|
|
return {
|
|
uri,
|
|
isDirty: false,
|
|
get lineCount() {
|
|
return Math.max(text.split('\n').length, 1)
|
|
},
|
|
lineAt(index: number) {
|
|
return {
|
|
text: text.split('\n')[index] ?? '',
|
|
}
|
|
},
|
|
getText() {
|
|
return text
|
|
},
|
|
setText(next: string) {
|
|
text = next
|
|
this.isDirty = true
|
|
},
|
|
}
|
|
}
|
|
|
|
const vscode = {
|
|
Uri: {
|
|
parse(value: string) {
|
|
const match = value.match(/^([a-z-]+):\/(.+?)(?:\?(.*))?$/i)
|
|
if (!match) {
|
|
throw new Error(`Unsupported URI: ${value}`)
|
|
}
|
|
const [, scheme, path, query = ''] = match
|
|
return createFakeUri(
|
|
scheme,
|
|
decodeURIComponent(path),
|
|
query,
|
|
)
|
|
},
|
|
file(filePath: string) {
|
|
return createFakeUri('file', filePath)
|
|
},
|
|
},
|
|
Range: class {
|
|
constructor(
|
|
public startLine: number,
|
|
public startCharacter: number,
|
|
public endLine: number,
|
|
public endCharacter: number,
|
|
) {}
|
|
},
|
|
workspace: {
|
|
registerTextDocumentContentProvider() {
|
|
return { dispose() {} }
|
|
},
|
|
onDidSaveTextDocument(handler: (document: FakeDocument) => void) {
|
|
saveListeners.add(handler)
|
|
return {
|
|
dispose() {
|
|
saveListeners.delete(handler)
|
|
},
|
|
}
|
|
},
|
|
async openTextDocument(uri: FakeUri) {
|
|
const key = uri.toString()
|
|
const existing = documents.get(key)
|
|
if (existing) {
|
|
return existing
|
|
}
|
|
const doc = createDocument(uri)
|
|
documents.set(key, doc)
|
|
return doc
|
|
},
|
|
},
|
|
window: {
|
|
visibleTextEditors,
|
|
tabGroups: {
|
|
all: [],
|
|
async close() {},
|
|
},
|
|
onDidChangeVisibleTextEditors(handler: (editors: any[]) => void) {
|
|
visibleEditorListeners.add(handler)
|
|
return {
|
|
dispose() {
|
|
visibleEditorListeners.delete(handler)
|
|
},
|
|
}
|
|
},
|
|
async showTextDocument(document: FakeDocument) {
|
|
const editor = {
|
|
document,
|
|
viewColumn: 1,
|
|
async edit(
|
|
callback: (editBuilder: { replace(range: unknown, text: string): void }) => void,
|
|
) {
|
|
callback({
|
|
replace(_range, text) {
|
|
document.setText(text)
|
|
},
|
|
})
|
|
return true
|
|
},
|
|
}
|
|
if (!visibleTextEditors.includes(editor)) {
|
|
visibleTextEditors.splice(0, visibleTextEditors.length, editor)
|
|
for (const listener of visibleEditorListeners) {
|
|
listener([...visibleTextEditors])
|
|
}
|
|
}
|
|
return editor
|
|
},
|
|
async showInformationMessage() {
|
|
return undefined
|
|
},
|
|
},
|
|
commands: {
|
|
async executeCommand() {},
|
|
},
|
|
__documents: documents,
|
|
async __emitSave(document: FakeDocument) {
|
|
document.isDirty = false
|
|
for (const listener of saveListeners) {
|
|
listener(document)
|
|
}
|
|
},
|
|
}
|
|
|
|
return vscode
|
|
}
|
|
|
|
async function waitForDocument(
|
|
filePath: string,
|
|
attempts = 20,
|
|
): Promise<FakeDocument | undefined> {
|
|
for (let i = 0; i < attempts; i++) {
|
|
const document = fakeVscode.__documents.get(
|
|
fakeVscode.Uri.file(filePath).toString(),
|
|
)
|
|
if (document) {
|
|
return document
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
const fakeVscode = createFakeVscode()
|
|
mock.module('vscode', () => fakeVscode)
|
|
|
|
afterEach(() => {
|
|
fakeVscode.__documents.clear()
|
|
fakeVscode.window.visibleTextEditors.splice(
|
|
0,
|
|
fakeVscode.window.visibleTextEditors.length,
|
|
)
|
|
})
|
|
|
|
describe('diff controller', () => {
|
|
test('returns FILE_SAVED with the saved file contents', async () => {
|
|
const { createDiffController } = await import(
|
|
'../src/server/diffController.js'
|
|
)
|
|
|
|
const tempDir = mkdtempSync(join(tmpdir(), 'claude-code-bridge-'))
|
|
const filePath = join(tempDir, 'sample.ts')
|
|
writeFileSync(filePath, 'const before = true\n')
|
|
|
|
const controller = createDiffController({
|
|
appendLine() {},
|
|
})
|
|
|
|
const resultPromise = controller.openDiff({
|
|
old_file_path: filePath,
|
|
new_file_path: filePath,
|
|
new_file_contents: 'const proposed = true\n',
|
|
tab_name: 'sample.ts',
|
|
})
|
|
|
|
const savedDocument = await waitForDocument(filePath)
|
|
expect(savedDocument).toBeDefined()
|
|
|
|
savedDocument?.setText('const saved = true\n')
|
|
await fakeVscode.__emitSave(savedDocument as FakeDocument)
|
|
|
|
const result = await Promise.race([
|
|
resultPromise,
|
|
new Promise(resolve =>
|
|
setTimeout(() => resolve('timed-out'), 200),
|
|
),
|
|
])
|
|
|
|
expect(result).toEqual({
|
|
content: [
|
|
{ type: 'text', text: 'FILE_SAVED' },
|
|
{ type: 'text', text: 'const saved = true\n' },
|
|
],
|
|
})
|
|
|
|
await controller.dispose()
|
|
})
|
|
})
|