mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-24 06:11:11 -05:00
Prettier backfill
This commit is contained in:
@@ -136,7 +136,7 @@ export class BrowserSession {
|
||||
let screenshotBase64 = await this.page.screenshot({
|
||||
...options,
|
||||
type: "webp",
|
||||
quality: (await this.context.globalState.get("screenshotQuality") as number | undefined) ?? 75,
|
||||
quality: ((await this.context.globalState.get("screenshotQuality")) as number | undefined) ?? 75,
|
||||
})
|
||||
let screenshot = `data:image/webp;base64,${screenshotBase64}`
|
||||
|
||||
@@ -247,7 +247,7 @@ export class BrowserSession {
|
||||
}
|
||||
|
||||
async scrollDown(): Promise<BrowserActionResult> {
|
||||
const size = (await this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600"
|
||||
const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600"
|
||||
const height = parseInt(size.split("x")[1])
|
||||
return this.doAction(async (page) => {
|
||||
await page.evaluate((scrollHeight) => {
|
||||
@@ -261,7 +261,7 @@ export class BrowserSession {
|
||||
}
|
||||
|
||||
async scrollUp(): Promise<BrowserActionResult> {
|
||||
const size = (await this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600"
|
||||
const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600"
|
||||
const height = parseInt(size.split("x")[1])
|
||||
return this.doAction(async (page) => {
|
||||
await page.evaluate((scrollHeight) => {
|
||||
|
||||
@@ -40,11 +40,11 @@ const StdioConfigSchema = z.object({
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.record(z.string()).optional(),
|
||||
alwaysAllow: AlwaysAllowSchema.optional(),
|
||||
disabled: z.boolean().optional()
|
||||
disabled: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const McpSettingsSchema = z.object({
|
||||
mcpServers: z.record(StdioConfigSchema)
|
||||
mcpServers: z.record(StdioConfigSchema),
|
||||
})
|
||||
|
||||
export class McpHub {
|
||||
@@ -63,9 +63,7 @@ export class McpHub {
|
||||
|
||||
getServers(): McpServer[] {
|
||||
// Only return enabled servers
|
||||
return this.connections
|
||||
.filter((conn) => !conn.server.disabled)
|
||||
.map((conn) => conn.server)
|
||||
return this.connections.filter((conn) => !conn.server.disabled).map((conn) => conn.server)
|
||||
}
|
||||
|
||||
async getMcpServersPath(): Promise<string> {
|
||||
@@ -300,9 +298,9 @@ export class McpHub {
|
||||
const alwaysAllowConfig = config.mcpServers[serverName]?.alwaysAllow || []
|
||||
|
||||
// Mark tools as always allowed based on settings
|
||||
const tools = (response?.tools || []).map(tool => ({
|
||||
const tools = (response?.tools || []).map((tool) => ({
|
||||
...tool,
|
||||
alwaysAllow: alwaysAllowConfig.includes(tool.name)
|
||||
alwaysAllow: alwaysAllowConfig.includes(tool.name),
|
||||
}))
|
||||
|
||||
console.log(`[MCP] Fetched tools for ${serverName}:`, tools)
|
||||
@@ -471,28 +469,28 @@ export class McpHub {
|
||||
}
|
||||
|
||||
// Public methods for server management
|
||||
|
||||
|
||||
public async toggleServerDisabled(serverName: string, disabled: boolean): Promise<void> {
|
||||
let settingsPath: string
|
||||
try {
|
||||
settingsPath = await this.getMcpSettingsFilePath()
|
||||
|
||||
|
||||
// Ensure the settings file exists and is accessible
|
||||
try {
|
||||
await fs.access(settingsPath)
|
||||
} catch (error) {
|
||||
console.error('Settings file not accessible:', error)
|
||||
throw new Error('Settings file not accessible')
|
||||
console.error("Settings file not accessible:", error)
|
||||
throw new Error("Settings file not accessible")
|
||||
}
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
|
||||
// Validate the config structure
|
||||
if (!config || typeof config !== 'object') {
|
||||
throw new Error('Invalid config structure')
|
||||
if (!config || typeof config !== "object") {
|
||||
throw new Error("Invalid config structure")
|
||||
}
|
||||
|
||||
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
||||
|
||||
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
||||
config.mcpServers = {}
|
||||
}
|
||||
|
||||
@@ -500,28 +498,28 @@ export class McpHub {
|
||||
// Create a new server config object to ensure clean structure
|
||||
const serverConfig = {
|
||||
...config.mcpServers[serverName],
|
||||
disabled
|
||||
disabled,
|
||||
}
|
||||
|
||||
|
||||
// Ensure required fields exist
|
||||
if (!serverConfig.alwaysAllow) {
|
||||
serverConfig.alwaysAllow = []
|
||||
}
|
||||
|
||||
|
||||
config.mcpServers[serverName] = serverConfig
|
||||
|
||||
|
||||
// Write the entire config back
|
||||
const updatedConfig = {
|
||||
mcpServers: config.mcpServers
|
||||
mcpServers: config.mcpServers,
|
||||
}
|
||||
|
||||
|
||||
await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2))
|
||||
|
||||
const connection = this.connections.find(conn => conn.server.name === serverName)
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
if (connection) {
|
||||
try {
|
||||
connection.server.disabled = disabled
|
||||
|
||||
|
||||
// Only refresh capabilities if connected
|
||||
if (connection.server.status === "connected") {
|
||||
connection.server.tools = await this.fetchToolsList(serverName)
|
||||
@@ -540,7 +538,9 @@ export class McpHub {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
vscode.window.showErrorMessage(`Failed to update server state: ${error instanceof Error ? error.message : String(error)}`)
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to update server state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -617,12 +617,11 @@ export class McpHub {
|
||||
await fs.writeFile(settingsPath, JSON.stringify(config, null, 2))
|
||||
|
||||
// Update the tools list to reflect the change
|
||||
const connection = this.connections.find(conn => conn.server.name === serverName)
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
if (connection) {
|
||||
connection.server.tools = await this.fetchToolsList(serverName)
|
||||
await this.notifyWebviewOfServerChanges()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to update always allow settings:", error)
|
||||
vscode.window.showErrorMessage("Failed to update always allow settings")
|
||||
|
||||
@@ -1,290 +1,292 @@
|
||||
import type { McpHub as McpHubType } from '../McpHub'
|
||||
import type { ClineProvider } from '../../../core/webview/ClineProvider'
|
||||
import type { ExtensionContext, Uri } from 'vscode'
|
||||
import type { McpConnection } from '../McpHub'
|
||||
import type { McpHub as McpHubType } from "../McpHub"
|
||||
import type { ClineProvider } from "../../../core/webview/ClineProvider"
|
||||
import type { ExtensionContext, Uri } from "vscode"
|
||||
import type { McpConnection } from "../McpHub"
|
||||
|
||||
const vscode = require('vscode')
|
||||
const fs = require('fs/promises')
|
||||
const { McpHub } = require('../McpHub')
|
||||
const vscode = require("vscode")
|
||||
const fs = require("fs/promises")
|
||||
const { McpHub } = require("../McpHub")
|
||||
|
||||
jest.mock('vscode')
|
||||
jest.mock('fs/promises')
|
||||
jest.mock('../../../core/webview/ClineProvider')
|
||||
jest.mock("vscode")
|
||||
jest.mock("fs/promises")
|
||||
jest.mock("../../../core/webview/ClineProvider")
|
||||
|
||||
describe('McpHub', () => {
|
||||
let mcpHub: McpHubType
|
||||
let mockProvider: Partial<ClineProvider>
|
||||
const mockSettingsPath = '/mock/settings/path/cline_mcp_settings.json'
|
||||
describe("McpHub", () => {
|
||||
let mcpHub: McpHubType
|
||||
let mockProvider: Partial<ClineProvider>
|
||||
const mockSettingsPath = "/mock/settings/path/cline_mcp_settings.json"
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
const mockUri: Uri = {
|
||||
scheme: 'file',
|
||||
authority: '',
|
||||
path: '/test/path',
|
||||
query: '',
|
||||
fragment: '',
|
||||
fsPath: '/test/path',
|
||||
with: jest.fn(),
|
||||
toJSON: jest.fn()
|
||||
}
|
||||
const mockUri: Uri = {
|
||||
scheme: "file",
|
||||
authority: "",
|
||||
path: "/test/path",
|
||||
query: "",
|
||||
fragment: "",
|
||||
fsPath: "/test/path",
|
||||
with: jest.fn(),
|
||||
toJSON: jest.fn(),
|
||||
}
|
||||
|
||||
mockProvider = {
|
||||
ensureSettingsDirectoryExists: jest.fn().mockResolvedValue('/mock/settings/path'),
|
||||
ensureMcpServersDirectoryExists: jest.fn().mockResolvedValue('/mock/settings/path'),
|
||||
postMessageToWebview: jest.fn(),
|
||||
context: {
|
||||
subscriptions: [],
|
||||
workspaceState: {} as any,
|
||||
globalState: {} as any,
|
||||
secrets: {} as any,
|
||||
extensionUri: mockUri,
|
||||
extensionPath: '/test/path',
|
||||
storagePath: '/test/storage',
|
||||
globalStoragePath: '/test/global-storage',
|
||||
environmentVariableCollection: {} as any,
|
||||
extension: {
|
||||
id: 'test-extension',
|
||||
extensionUri: mockUri,
|
||||
extensionPath: '/test/path',
|
||||
extensionKind: 1,
|
||||
isActive: true,
|
||||
packageJSON: {
|
||||
version: '1.0.0'
|
||||
},
|
||||
activate: jest.fn(),
|
||||
exports: undefined
|
||||
} as any,
|
||||
asAbsolutePath: (path: string) => path,
|
||||
storageUri: mockUri,
|
||||
globalStorageUri: mockUri,
|
||||
logUri: mockUri,
|
||||
extensionMode: 1,
|
||||
logPath: '/test/path',
|
||||
languageModelAccessInformation: {} as any
|
||||
} as ExtensionContext
|
||||
}
|
||||
mockProvider = {
|
||||
ensureSettingsDirectoryExists: jest.fn().mockResolvedValue("/mock/settings/path"),
|
||||
ensureMcpServersDirectoryExists: jest.fn().mockResolvedValue("/mock/settings/path"),
|
||||
postMessageToWebview: jest.fn(),
|
||||
context: {
|
||||
subscriptions: [],
|
||||
workspaceState: {} as any,
|
||||
globalState: {} as any,
|
||||
secrets: {} as any,
|
||||
extensionUri: mockUri,
|
||||
extensionPath: "/test/path",
|
||||
storagePath: "/test/storage",
|
||||
globalStoragePath: "/test/global-storage",
|
||||
environmentVariableCollection: {} as any,
|
||||
extension: {
|
||||
id: "test-extension",
|
||||
extensionUri: mockUri,
|
||||
extensionPath: "/test/path",
|
||||
extensionKind: 1,
|
||||
isActive: true,
|
||||
packageJSON: {
|
||||
version: "1.0.0",
|
||||
},
|
||||
activate: jest.fn(),
|
||||
exports: undefined,
|
||||
} as any,
|
||||
asAbsolutePath: (path: string) => path,
|
||||
storageUri: mockUri,
|
||||
globalStorageUri: mockUri,
|
||||
logUri: mockUri,
|
||||
extensionMode: 1,
|
||||
logPath: "/test/path",
|
||||
languageModelAccessInformation: {} as any,
|
||||
} as ExtensionContext,
|
||||
}
|
||||
|
||||
// Mock fs.readFile for initial settings
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: ['allowed-tool']
|
||||
}
|
||||
}
|
||||
}))
|
||||
// Mock fs.readFile for initial settings
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue(
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
"test-server": {
|
||||
command: "node",
|
||||
args: ["test.js"],
|
||||
alwaysAllow: ["allowed-tool"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
mcpHub = new McpHub(mockProvider as ClineProvider)
|
||||
})
|
||||
mcpHub = new McpHub(mockProvider as ClineProvider)
|
||||
})
|
||||
|
||||
describe('toggleToolAlwaysAllow', () => {
|
||||
it('should add tool to always allow list when enabling', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: []
|
||||
}
|
||||
}
|
||||
}
|
||||
describe("toggleToolAlwaysAllow", () => {
|
||||
it("should add tool to always allow list when enabling", async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
"test-server": {
|
||||
command: "node",
|
||||
args: ["test.js"],
|
||||
alwaysAllow: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'new-tool', true)
|
||||
await mcpHub.toggleToolAlwaysAllow("test-server", "new-tool", true)
|
||||
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toContain('new-tool')
|
||||
})
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toContain("new-tool")
|
||||
})
|
||||
|
||||
it('should remove tool from always allow list when disabling', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: ['existing-tool']
|
||||
}
|
||||
}
|
||||
}
|
||||
it("should remove tool from always allow list when disabling", async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
"test-server": {
|
||||
command: "node",
|
||||
args: ["test.js"],
|
||||
alwaysAllow: ["existing-tool"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'existing-tool', false)
|
||||
await mcpHub.toggleToolAlwaysAllow("test-server", "existing-tool", false)
|
||||
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).not.toContain('existing-tool')
|
||||
})
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).not.toContain("existing-tool")
|
||||
})
|
||||
|
||||
it('should initialize alwaysAllow if it does not exist', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js']
|
||||
}
|
||||
}
|
||||
}
|
||||
it("should initialize alwaysAllow if it does not exist", async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
"test-server": {
|
||||
command: "node",
|
||||
args: ["test.js"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'new-tool', true)
|
||||
await mcpHub.toggleToolAlwaysAllow("test-server", "new-tool", true)
|
||||
|
||||
// Verify the config was updated with initialized alwaysAllow
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toBeDefined()
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toContain('new-tool')
|
||||
})
|
||||
})
|
||||
// Verify the config was updated with initialized alwaysAllow
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toBeDefined()
|
||||
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toContain("new-tool")
|
||||
})
|
||||
})
|
||||
|
||||
describe('server disabled state', () => {
|
||||
it('should toggle server disabled state', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
disabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
describe("server disabled state", () => {
|
||||
it("should toggle server disabled state", async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
"test-server": {
|
||||
command: "node",
|
||||
args: ["test.js"],
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleServerDisabled('test-server', true)
|
||||
await mcpHub.toggleServerDisabled("test-server", true)
|
||||
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].disabled).toBe(true)
|
||||
})
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers["test-server"].disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should filter out disabled servers from getServers', () => {
|
||||
const mockConnections: McpConnection[] = [
|
||||
{
|
||||
server: {
|
||||
name: 'enabled-server',
|
||||
config: '{}',
|
||||
status: 'connected',
|
||||
disabled: false
|
||||
},
|
||||
client: {} as any,
|
||||
transport: {} as any
|
||||
},
|
||||
{
|
||||
server: {
|
||||
name: 'disabled-server',
|
||||
config: '{}',
|
||||
status: 'connected',
|
||||
disabled: true
|
||||
},
|
||||
client: {} as any,
|
||||
transport: {} as any
|
||||
}
|
||||
]
|
||||
it("should filter out disabled servers from getServers", () => {
|
||||
const mockConnections: McpConnection[] = [
|
||||
{
|
||||
server: {
|
||||
name: "enabled-server",
|
||||
config: "{}",
|
||||
status: "connected",
|
||||
disabled: false,
|
||||
},
|
||||
client: {} as any,
|
||||
transport: {} as any,
|
||||
},
|
||||
{
|
||||
server: {
|
||||
name: "disabled-server",
|
||||
config: "{}",
|
||||
status: "connected",
|
||||
disabled: true,
|
||||
},
|
||||
client: {} as any,
|
||||
transport: {} as any,
|
||||
},
|
||||
]
|
||||
|
||||
mcpHub.connections = mockConnections
|
||||
const servers = mcpHub.getServers()
|
||||
mcpHub.connections = mockConnections
|
||||
const servers = mcpHub.getServers()
|
||||
|
||||
expect(servers.length).toBe(1)
|
||||
expect(servers[0].name).toBe('enabled-server')
|
||||
})
|
||||
expect(servers.length).toBe(1)
|
||||
expect(servers[0].name).toBe("enabled-server")
|
||||
})
|
||||
|
||||
it('should prevent calling tools on disabled servers', async () => {
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: 'disabled-server',
|
||||
config: '{}',
|
||||
status: 'connected',
|
||||
disabled: true
|
||||
},
|
||||
client: {
|
||||
request: jest.fn().mockResolvedValue({ result: 'success' })
|
||||
} as any,
|
||||
transport: {} as any
|
||||
}
|
||||
it("should prevent calling tools on disabled servers", async () => {
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: "disabled-server",
|
||||
config: "{}",
|
||||
status: "connected",
|
||||
disabled: true,
|
||||
},
|
||||
client: {
|
||||
request: jest.fn().mockResolvedValue({ result: "success" }),
|
||||
} as any,
|
||||
transport: {} as any,
|
||||
}
|
||||
|
||||
mcpHub.connections = [mockConnection]
|
||||
mcpHub.connections = [mockConnection]
|
||||
|
||||
await expect(mcpHub.callTool('disabled-server', 'some-tool', {}))
|
||||
.rejects
|
||||
.toThrow('Server "disabled-server" is disabled and cannot be used')
|
||||
})
|
||||
await expect(mcpHub.callTool("disabled-server", "some-tool", {})).rejects.toThrow(
|
||||
'Server "disabled-server" is disabled and cannot be used',
|
||||
)
|
||||
})
|
||||
|
||||
it('should prevent reading resources from disabled servers', async () => {
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: 'disabled-server',
|
||||
config: '{}',
|
||||
status: 'connected',
|
||||
disabled: true
|
||||
},
|
||||
client: {
|
||||
request: jest.fn()
|
||||
} as any,
|
||||
transport: {} as any
|
||||
}
|
||||
it("should prevent reading resources from disabled servers", async () => {
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: "disabled-server",
|
||||
config: "{}",
|
||||
status: "connected",
|
||||
disabled: true,
|
||||
},
|
||||
client: {
|
||||
request: jest.fn(),
|
||||
} as any,
|
||||
transport: {} as any,
|
||||
}
|
||||
|
||||
mcpHub.connections = [mockConnection]
|
||||
mcpHub.connections = [mockConnection]
|
||||
|
||||
await expect(mcpHub.readResource('disabled-server', 'some/uri'))
|
||||
.rejects
|
||||
.toThrow('Server "disabled-server" is disabled')
|
||||
})
|
||||
})
|
||||
await expect(mcpHub.readResource("disabled-server", "some/uri")).rejects.toThrow(
|
||||
'Server "disabled-server" is disabled',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('callTool', () => {
|
||||
it('should execute tool successfully', async () => {
|
||||
// Mock the connection with a minimal client implementation
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: 'test-server',
|
||||
config: JSON.stringify({}),
|
||||
status: 'connected' as const
|
||||
},
|
||||
client: {
|
||||
request: jest.fn().mockResolvedValue({ result: 'success' })
|
||||
} as any,
|
||||
transport: {
|
||||
start: jest.fn(),
|
||||
close: jest.fn(),
|
||||
stderr: { on: jest.fn() }
|
||||
} as any
|
||||
}
|
||||
describe("callTool", () => {
|
||||
it("should execute tool successfully", async () => {
|
||||
// Mock the connection with a minimal client implementation
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: "test-server",
|
||||
config: JSON.stringify({}),
|
||||
status: "connected" as const,
|
||||
},
|
||||
client: {
|
||||
request: jest.fn().mockResolvedValue({ result: "success" }),
|
||||
} as any,
|
||||
transport: {
|
||||
start: jest.fn(),
|
||||
close: jest.fn(),
|
||||
stderr: { on: jest.fn() },
|
||||
} as any,
|
||||
}
|
||||
|
||||
mcpHub.connections = [mockConnection]
|
||||
mcpHub.connections = [mockConnection]
|
||||
|
||||
await mcpHub.callTool('test-server', 'some-tool', {})
|
||||
await mcpHub.callTool("test-server", "some-tool", {})
|
||||
|
||||
// Verify the request was made with correct parameters
|
||||
expect(mockConnection.client.request).toHaveBeenCalledWith(
|
||||
{
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'some-tool',
|
||||
arguments: {}
|
||||
}
|
||||
},
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
// Verify the request was made with correct parameters
|
||||
expect(mockConnection.client.request).toHaveBeenCalledWith(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "some-tool",
|
||||
arguments: {},
|
||||
},
|
||||
},
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error if server not found', async () => {
|
||||
await expect(mcpHub.callTool('non-existent-server', 'some-tool', {}))
|
||||
.rejects
|
||||
.toThrow('No connection found for server: non-existent-server')
|
||||
})
|
||||
})
|
||||
})
|
||||
it("should throw error if server not found", async () => {
|
||||
await expect(mcpHub.callTool("non-existent-server", "some-tool", {})).rejects.toThrow(
|
||||
"No connection found for server: non-existent-server",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,254 +1,246 @@
|
||||
import { parseSourceCodeForDefinitionsTopLevel } from '../index';
|
||||
import { listFiles } from '../../glob/list-files';
|
||||
import { loadRequiredLanguageParsers } from '../languageParser';
|
||||
import { fileExistsAtPath } from '../../../utils/fs';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { parseSourceCodeForDefinitionsTopLevel } from "../index"
|
||||
import { listFiles } from "../../glob/list-files"
|
||||
import { loadRequiredLanguageParsers } from "../languageParser"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../glob/list-files');
|
||||
jest.mock('../languageParser');
|
||||
jest.mock('../../../utils/fs');
|
||||
jest.mock('fs/promises');
|
||||
jest.mock("../../glob/list-files")
|
||||
jest.mock("../languageParser")
|
||||
jest.mock("../../../utils/fs")
|
||||
jest.mock("fs/promises")
|
||||
|
||||
describe('Tree-sitter Service', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(fileExistsAtPath as jest.Mock).mockResolvedValue(true);
|
||||
});
|
||||
describe("Tree-sitter Service", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
;(fileExistsAtPath as jest.Mock).mockResolvedValue(true)
|
||||
})
|
||||
|
||||
describe('parseSourceCodeForDefinitionsTopLevel', () => {
|
||||
it('should handle non-existent directory', async () => {
|
||||
(fileExistsAtPath as jest.Mock).mockResolvedValue(false);
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/non/existent/path');
|
||||
expect(result).toBe('This directory does not exist or you do not have permission to access it.');
|
||||
});
|
||||
describe("parseSourceCodeForDefinitionsTopLevel", () => {
|
||||
it("should handle non-existent directory", async () => {
|
||||
;(fileExistsAtPath as jest.Mock).mockResolvedValue(false)
|
||||
|
||||
it('should handle empty directory', async () => {
|
||||
(listFiles as jest.Mock).mockResolvedValue([[], new Set()]);
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
expect(result).toBe('No source code definitions found.');
|
||||
});
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/non/existent/path")
|
||||
expect(result).toBe("This directory does not exist or you do not have permission to access it.")
|
||||
})
|
||||
|
||||
it('should parse TypeScript files correctly', async () => {
|
||||
const mockFiles = [
|
||||
'/test/path/file1.ts',
|
||||
'/test/path/file2.tsx',
|
||||
'/test/path/readme.md'
|
||||
];
|
||||
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: 'mockNode'
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 }
|
||||
},
|
||||
name: 'name.definition'
|
||||
}
|
||||
])
|
||||
};
|
||||
it("should handle empty directory", async () => {
|
||||
;(listFiles as jest.Mock).mockResolvedValue([[], new Set()])
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
tsx: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
expect(result).toBe("No source code definitions found.")
|
||||
})
|
||||
|
||||
(fs.readFile as jest.Mock).mockResolvedValue(
|
||||
'export class TestClass {\n constructor() {}\n}'
|
||||
);
|
||||
it("should parse TypeScript files correctly", async () => {
|
||||
const mockFiles = ["/test/path/file1.ts", "/test/path/file2.tsx", "/test/path/readme.md"]
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
|
||||
expect(result).toContain('file1.ts');
|
||||
expect(result).toContain('file2.tsx');
|
||||
expect(result).not.toContain('readme.md');
|
||||
expect(result).toContain('export class TestClass');
|
||||
});
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
it('should handle multiple definition types', async () => {
|
||||
const mockFiles = ['/test/path/file.ts'];
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: 'mockNode'
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 }
|
||||
},
|
||||
name: 'name.definition.class'
|
||||
},
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 2 },
|
||||
endPosition: { row: 2 }
|
||||
},
|
||||
name: 'name.definition.function'
|
||||
}
|
||||
])
|
||||
};
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: "mockNode",
|
||||
}),
|
||||
}
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 },
|
||||
},
|
||||
name: "name.definition",
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
const fileContent =
|
||||
'class TestClass {\n' +
|
||||
' constructor() {}\n' +
|
||||
' testMethod() {}\n' +
|
||||
'}';
|
||||
|
||||
(fs.readFile as jest.Mock).mockResolvedValue(fileContent);
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
tsx: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue("export class TestClass {\n constructor() {}\n}")
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
|
||||
expect(result).toContain('class TestClass');
|
||||
expect(result).toContain('testMethod()');
|
||||
expect(result).toContain('|----');
|
||||
});
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
|
||||
it('should handle parsing errors gracefully', async () => {
|
||||
const mockFiles = ['/test/path/file.ts'];
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Parsing error');
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn()
|
||||
};
|
||||
expect(result).toContain("file1.ts")
|
||||
expect(result).toContain("file2.tsx")
|
||||
expect(result).not.toContain("readme.md")
|
||||
expect(result).toContain("export class TestClass")
|
||||
})
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
it("should handle multiple definition types", async () => {
|
||||
const mockFiles = ["/test/path/file.ts"]
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
(fs.readFile as jest.Mock).mockResolvedValue('invalid code');
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: "mockNode",
|
||||
}),
|
||||
}
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
expect(result).toBe('No source code definitions found.');
|
||||
});
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 },
|
||||
},
|
||||
name: "name.definition.class",
|
||||
},
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 2 },
|
||||
endPosition: { row: 2 },
|
||||
},
|
||||
name: "name.definition.function",
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
it('should respect file limit', async () => {
|
||||
const mockFiles = Array(100).fill(0).map((_, i) => `/test/path/file${i}.ts`);
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: 'mockNode'
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([])
|
||||
};
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
const fileContent = "class TestClass {\n" + " constructor() {}\n" + " testMethod() {}\n" + "}"
|
||||
|
||||
await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
|
||||
// Should only process first 50 files
|
||||
expect(mockParser.parse).toHaveBeenCalledTimes(50);
|
||||
});
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue(fileContent)
|
||||
|
||||
it('should handle various supported file extensions', async () => {
|
||||
const mockFiles = [
|
||||
'/test/path/script.js',
|
||||
'/test/path/app.py',
|
||||
'/test/path/main.rs',
|
||||
'/test/path/program.cpp',
|
||||
'/test/path/code.go'
|
||||
];
|
||||
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: 'mockNode'
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 }
|
||||
},
|
||||
name: 'name'
|
||||
}])
|
||||
};
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
js: { parser: mockParser, query: mockQuery },
|
||||
py: { parser: mockParser, query: mockQuery },
|
||||
rs: { parser: mockParser, query: mockQuery },
|
||||
cpp: { parser: mockParser, query: mockQuery },
|
||||
go: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
expect(result).toContain("class TestClass")
|
||||
expect(result).toContain("testMethod()")
|
||||
expect(result).toContain("|----")
|
||||
})
|
||||
|
||||
(fs.readFile as jest.Mock).mockResolvedValue('function test() {}');
|
||||
it("should handle parsing errors gracefully", async () => {
|
||||
const mockFiles = ["/test/path/file.ts"]
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
|
||||
expect(result).toContain('script.js');
|
||||
expect(result).toContain('app.py');
|
||||
expect(result).toContain('main.rs');
|
||||
expect(result).toContain('program.cpp');
|
||||
expect(result).toContain('code.go');
|
||||
});
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockImplementation(() => {
|
||||
throw new Error("Parsing error")
|
||||
}),
|
||||
}
|
||||
|
||||
it('should normalize paths in output', async () => {
|
||||
const mockFiles = ['/test/path/dir\\file.ts'];
|
||||
(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]);
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: 'mockNode'
|
||||
})
|
||||
};
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 }
|
||||
},
|
||||
name: 'name'
|
||||
}])
|
||||
};
|
||||
const mockQuery = {
|
||||
captures: jest.fn(),
|
||||
}
|
||||
|
||||
(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery }
|
||||
});
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue("invalid code")
|
||||
|
||||
(fs.readFile as jest.Mock).mockResolvedValue('class Test {}');
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
expect(result).toBe("No source code definitions found.")
|
||||
})
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel('/test/path');
|
||||
|
||||
// Should use forward slashes regardless of platform
|
||||
expect(result).toContain('dir/file.ts');
|
||||
expect(result).not.toContain('dir\\file.ts');
|
||||
});
|
||||
});
|
||||
});
|
||||
it("should respect file limit", async () => {
|
||||
const mockFiles = Array(100)
|
||||
.fill(0)
|
||||
.map((_, i) => `/test/path/file${i}.ts`)
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: "mockNode",
|
||||
}),
|
||||
}
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([]),
|
||||
}
|
||||
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
|
||||
await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
|
||||
// Should only process first 50 files
|
||||
expect(mockParser.parse).toHaveBeenCalledTimes(50)
|
||||
})
|
||||
|
||||
it("should handle various supported file extensions", async () => {
|
||||
const mockFiles = [
|
||||
"/test/path/script.js",
|
||||
"/test/path/app.py",
|
||||
"/test/path/main.rs",
|
||||
"/test/path/program.cpp",
|
||||
"/test/path/code.go",
|
||||
]
|
||||
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: "mockNode",
|
||||
}),
|
||||
}
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 },
|
||||
},
|
||||
name: "name",
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
js: { parser: mockParser, query: mockQuery },
|
||||
py: { parser: mockParser, query: mockQuery },
|
||||
rs: { parser: mockParser, query: mockQuery },
|
||||
cpp: { parser: mockParser, query: mockQuery },
|
||||
go: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue("function test() {}")
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
|
||||
expect(result).toContain("script.js")
|
||||
expect(result).toContain("app.py")
|
||||
expect(result).toContain("main.rs")
|
||||
expect(result).toContain("program.cpp")
|
||||
expect(result).toContain("code.go")
|
||||
})
|
||||
|
||||
it("should normalize paths in output", async () => {
|
||||
const mockFiles = ["/test/path/dir\\file.ts"]
|
||||
;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()])
|
||||
|
||||
const mockParser = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
rootNode: "mockNode",
|
||||
}),
|
||||
}
|
||||
|
||||
const mockQuery = {
|
||||
captures: jest.fn().mockReturnValue([
|
||||
{
|
||||
node: {
|
||||
startPosition: { row: 0 },
|
||||
endPosition: { row: 0 },
|
||||
},
|
||||
name: "name",
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
;(loadRequiredLanguageParsers as jest.Mock).mockResolvedValue({
|
||||
ts: { parser: mockParser, query: mockQuery },
|
||||
})
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue("class Test {}")
|
||||
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel("/test/path")
|
||||
|
||||
// Should use forward slashes regardless of platform
|
||||
expect(result).toContain("dir/file.ts")
|
||||
expect(result).not.toContain("dir\\file.ts")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,128 +1,118 @@
|
||||
import { loadRequiredLanguageParsers } from '../languageParser';
|
||||
import Parser from 'web-tree-sitter';
|
||||
import { loadRequiredLanguageParsers } from "../languageParser"
|
||||
import Parser from "web-tree-sitter"
|
||||
|
||||
// Mock web-tree-sitter
|
||||
const mockSetLanguage = jest.fn();
|
||||
jest.mock('web-tree-sitter', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(() => ({
|
||||
setLanguage: mockSetLanguage
|
||||
}))
|
||||
};
|
||||
});
|
||||
const mockSetLanguage = jest.fn()
|
||||
jest.mock("web-tree-sitter", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(() => ({
|
||||
setLanguage: mockSetLanguage,
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
// Add static methods to Parser mock
|
||||
const ParserMock = Parser as jest.MockedClass<typeof Parser>;
|
||||
ParserMock.init = jest.fn().mockResolvedValue(undefined);
|
||||
const ParserMock = Parser as jest.MockedClass<typeof Parser>
|
||||
ParserMock.init = jest.fn().mockResolvedValue(undefined)
|
||||
ParserMock.Language = {
|
||||
load: jest.fn().mockResolvedValue({
|
||||
query: jest.fn().mockReturnValue('mockQuery')
|
||||
}),
|
||||
prototype: {} // Add required prototype property
|
||||
} as unknown as typeof Parser.Language;
|
||||
load: jest.fn().mockResolvedValue({
|
||||
query: jest.fn().mockReturnValue("mockQuery"),
|
||||
}),
|
||||
prototype: {}, // Add required prototype property
|
||||
} as unknown as typeof Parser.Language
|
||||
|
||||
describe('Language Parser', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe("Language Parser", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('loadRequiredLanguageParsers', () => {
|
||||
it('should initialize parser only once', async () => {
|
||||
const files = ['test.js', 'test2.js'];
|
||||
await loadRequiredLanguageParsers(files);
|
||||
await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.init).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
describe("loadRequiredLanguageParsers", () => {
|
||||
it("should initialize parser only once", async () => {
|
||||
const files = ["test.js", "test2.js"]
|
||||
await loadRequiredLanguageParsers(files)
|
||||
await loadRequiredLanguageParsers(files)
|
||||
|
||||
it('should load JavaScript parser for .js and .jsx files', async () => {
|
||||
const files = ['test.js', 'test.jsx'];
|
||||
const parsers = await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-javascript.wasm')
|
||||
);
|
||||
expect(parsers.js).toBeDefined();
|
||||
expect(parsers.jsx).toBeDefined();
|
||||
expect(parsers.js.query).toBeDefined();
|
||||
expect(parsers.jsx.query).toBeDefined();
|
||||
});
|
||||
expect(ParserMock.init).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should load TypeScript parser for .ts and .tsx files', async () => {
|
||||
const files = ['test.ts', 'test.tsx'];
|
||||
const parsers = await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-typescript.wasm')
|
||||
);
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-tsx.wasm')
|
||||
);
|
||||
expect(parsers.ts).toBeDefined();
|
||||
expect(parsers.tsx).toBeDefined();
|
||||
});
|
||||
it("should load JavaScript parser for .js and .jsx files", async () => {
|
||||
const files = ["test.js", "test.jsx"]
|
||||
const parsers = await loadRequiredLanguageParsers(files)
|
||||
|
||||
it('should load Python parser for .py files', async () => {
|
||||
const files = ['test.py'];
|
||||
const parsers = await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-python.wasm')
|
||||
);
|
||||
expect(parsers.py).toBeDefined();
|
||||
});
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining("tree-sitter-javascript.wasm"),
|
||||
)
|
||||
expect(parsers.js).toBeDefined()
|
||||
expect(parsers.jsx).toBeDefined()
|
||||
expect(parsers.js.query).toBeDefined()
|
||||
expect(parsers.jsx.query).toBeDefined()
|
||||
})
|
||||
|
||||
it('should load multiple language parsers as needed', async () => {
|
||||
const files = ['test.js', 'test.py', 'test.rs', 'test.go'];
|
||||
const parsers = await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledTimes(4);
|
||||
expect(parsers.js).toBeDefined();
|
||||
expect(parsers.py).toBeDefined();
|
||||
expect(parsers.rs).toBeDefined();
|
||||
expect(parsers.go).toBeDefined();
|
||||
});
|
||||
it("should load TypeScript parser for .ts and .tsx files", async () => {
|
||||
const files = ["test.ts", "test.tsx"]
|
||||
const parsers = await loadRequiredLanguageParsers(files)
|
||||
|
||||
it('should handle C/C++ files correctly', async () => {
|
||||
const files = ['test.c', 'test.h', 'test.cpp', 'test.hpp'];
|
||||
const parsers = await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-c.wasm')
|
||||
);
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-cpp.wasm')
|
||||
);
|
||||
expect(parsers.c).toBeDefined();
|
||||
expect(parsers.h).toBeDefined();
|
||||
expect(parsers.cpp).toBeDefined();
|
||||
expect(parsers.hpp).toBeDefined();
|
||||
});
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining("tree-sitter-typescript.wasm"),
|
||||
)
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-tsx.wasm"))
|
||||
expect(parsers.ts).toBeDefined()
|
||||
expect(parsers.tsx).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw error for unsupported file extensions', async () => {
|
||||
const files = ['test.unsupported'];
|
||||
|
||||
await expect(loadRequiredLanguageParsers(files)).rejects.toThrow(
|
||||
'Unsupported language: unsupported'
|
||||
);
|
||||
});
|
||||
it("should load Python parser for .py files", async () => {
|
||||
const files = ["test.py"]
|
||||
const parsers = await loadRequiredLanguageParsers(files)
|
||||
|
||||
it('should load each language only once for multiple files', async () => {
|
||||
const files = ['test1.js', 'test2.js', 'test3.js'];
|
||||
await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledTimes(1);
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tree-sitter-javascript.wasm')
|
||||
);
|
||||
});
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-python.wasm"))
|
||||
expect(parsers.py).toBeDefined()
|
||||
})
|
||||
|
||||
it('should set language for each parser instance', async () => {
|
||||
const files = ['test.js', 'test.py'];
|
||||
await loadRequiredLanguageParsers(files);
|
||||
|
||||
expect(mockSetLanguage).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("should load multiple language parsers as needed", async () => {
|
||||
const files = ["test.js", "test.py", "test.rs", "test.go"]
|
||||
const parsers = await loadRequiredLanguageParsers(files)
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledTimes(4)
|
||||
expect(parsers.js).toBeDefined()
|
||||
expect(parsers.py).toBeDefined()
|
||||
expect(parsers.rs).toBeDefined()
|
||||
expect(parsers.go).toBeDefined()
|
||||
})
|
||||
|
||||
it("should handle C/C++ files correctly", async () => {
|
||||
const files = ["test.c", "test.h", "test.cpp", "test.hpp"]
|
||||
const parsers = await loadRequiredLanguageParsers(files)
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-c.wasm"))
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-cpp.wasm"))
|
||||
expect(parsers.c).toBeDefined()
|
||||
expect(parsers.h).toBeDefined()
|
||||
expect(parsers.cpp).toBeDefined()
|
||||
expect(parsers.hpp).toBeDefined()
|
||||
})
|
||||
|
||||
it("should throw error for unsupported file extensions", async () => {
|
||||
const files = ["test.unsupported"]
|
||||
|
||||
await expect(loadRequiredLanguageParsers(files)).rejects.toThrow("Unsupported language: unsupported")
|
||||
})
|
||||
|
||||
it("should load each language only once for multiple files", async () => {
|
||||
const files = ["test1.js", "test2.js", "test3.js"]
|
||||
await loadRequiredLanguageParsers(files)
|
||||
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledTimes(1)
|
||||
expect(ParserMock.Language.load).toHaveBeenCalledWith(
|
||||
expect.stringContaining("tree-sitter-javascript.wasm"),
|
||||
)
|
||||
})
|
||||
|
||||
it("should set language for each parser instance", async () => {
|
||||
const files = ["test.js", "test.py"]
|
||||
await loadRequiredLanguageParsers(files)
|
||||
|
||||
expect(mockSetLanguage).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user