Files
zmo-cli/packages/opencode/test/tool/lsp.test.ts
Dax Raad 1e98167b0e core: move cross-spawn-spawner to root and remove unused types
The cross-spawn-spawner module has been moved from src/effect/ to src/
to simplify the core package structure. The src/types.d.ts file which
contained unused type declarations has also been removed. All imports
throughout the codebase have been updated to reflect the new location.

This change reduces the package's internal complexity by flattening the
module hierarchy and removing dead code, making future maintenance easier.
2026-04-25 14:30:16 -04:00

163 lines
4.9 KiB
TypeScript

import { afterEach, describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import path from "path"
import { Agent } from "../../src/agent/agent"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { LSP } from "../../src/lsp"
import { Permission } from "../../src/permission"
import { Instance } from "../../src/project/instance"
import { MessageID, SessionID } from "../../src/session/schema"
import { Tool, Truncate } from "../../src/tool"
import { LspTool } from "../../src/tool/lsp"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
afterEach(async () => {
await Instance.disposeAll()
})
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),
messages: [],
metadata: () => Effect.void,
ask: () => Effect.void,
}
const lsp = Layer.succeed(
LSP.Service,
LSP.Service.of({
init: () => Effect.void,
status: () => Effect.succeed([]),
hasClients: () => Effect.succeed(true),
touchFile: () => Effect.void,
diagnostics: () => Effect.succeed({}),
hover: () => Effect.succeed([]),
definition: () => Effect.succeed([]),
references: () => Effect.succeed([]),
implementation: () => Effect.succeed([]),
documentSymbol: () => Effect.succeed([]),
workspaceSymbol: () => Effect.succeed([]),
prepareCallHierarchy: () => Effect.succeed([]),
incomingCalls: () => Effect.succeed([]),
outgoingCalls: () => Effect.succeed([]),
}),
)
const it = testEffect(
Layer.mergeAll(
Agent.defaultLayer,
AppFileSystem.defaultLayer,
CrossSpawnSpawner.defaultLayer,
Truncate.defaultLayer,
lsp,
),
)
const init = Effect.fn("LspToolTest.init")(function* () {
const info = yield* LspTool
return yield* info.init()
})
const run = Effect.fn("LspToolTest.run")(function* (
args: Tool.InferParameters<typeof LspTool>,
next: Tool.Context = ctx,
) {
const tool = yield* init()
return yield* tool.execute(args, next)
})
const put = Effect.fn("LspToolTest.put")(function* (file: string) {
const fs = yield* AppFileSystem.Service
yield* fs.writeWithDirs(file, "export const x = 1\n")
})
const asks = () => {
const items: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
return {
items,
next: {
...ctx,
ask: (req: Omit<Permission.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => {
items.push(req)
}),
},
}
}
describe("tool.lsp", () => {
describe("permission metadata", () => {
it.live("keeps cursor details for position-based operations", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const file = path.join(dir, "test.ts")
yield* put(file)
const { items, next } = asks()
const result = yield* run({ operation: "goToDefinition", filePath: file, line: 3, character: 7 }, next)
const req = items.find((item) => item.permission === "lsp")
expect(req).toBeDefined()
expect(req!.metadata).toEqual({
operation: "goToDefinition",
filePath: file,
line: 3,
character: 7,
})
expect(result.title).toBe("goToDefinition test.ts:3:7")
}),
{ git: true },
),
)
it.live("omits cursor details for documentSymbol", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const file = path.join(dir, "test.ts")
yield* put(file)
const { items, next } = asks()
const result = yield* run({ operation: "documentSymbol", filePath: file, line: 3, character: 7 }, next)
const req = items.find((item) => item.permission === "lsp")
expect(req).toBeDefined()
expect(req!.metadata).toEqual({
operation: "documentSymbol",
filePath: file,
})
expect(result.title).toBe("documentSymbol test.ts")
}),
{ git: true },
),
)
it.live("omits file and cursor details for workspaceSymbol", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const file = path.join(dir, "test.ts")
yield* put(file)
const { items, next } = asks()
const result = yield* run({ operation: "workspaceSymbol", filePath: file, line: 3, character: 7 }, next)
const req = items.find((item) => item.permission === "lsp")
expect(req).toBeDefined()
expect(req!.metadata).toEqual({
operation: "workspaceSymbol",
})
expect(result.title).toBe("workspaceSymbol")
}),
{ git: true },
),
)
})
})