From 97b67928f0919242d074bc6722f64d3c2746baf2 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Feb 2025 11:14:52 -0500 Subject: [PATCH] Add tests --- .../webview/__tests__/ClineProvider.test.ts | 39 ++++++- src/shared/__tests__/experiments.test.ts | 47 ++++++++ src/shared/__tests__/modes.test.ts | 101 +++++++++++++++++- 3 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 src/shared/__tests__/experiments.test.ts diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 0a8f73308f..13a2e6d84f 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -9,12 +9,41 @@ import { setSoundEnabled } from "../../../utils/sound" import { defaultModeSlug } from "../../../shared/modes" import { experimentDefault } from "../../../shared/experiments" -// Mock custom-instructions module -const mockAddCustomInstructions = jest.fn() +// Mock setup must come before imports +jest.mock("../../prompts/sections/custom-instructions") -jest.mock("../../prompts/sections/custom-instructions", () => ({ - addCustomInstructions: mockAddCustomInstructions, -})) +// Mock dependencies +jest.mock("vscode") +jest.mock("delay") +jest.mock( + "@modelcontextprotocol/sdk/types.js", + () => ({ + CallToolResultSchema: {}, + ListResourcesResultSchema: {}, + ListResourceTemplatesResultSchema: {}, + ListToolsResultSchema: {}, + ReadResourceResultSchema: {}, + ErrorCode: { + InvalidRequest: "InvalidRequest", + MethodNotFound: "MethodNotFound", + InternalError: "InternalError", + }, + McpError: class McpError extends Error { + code: string + constructor(code: string, message: string) { + super(message) + this.code = code + this.name = "McpError" + } + }, + }), + { virtual: true }, +) + +// Initialize mocks +const mockAddCustomInstructions = jest.fn().mockResolvedValue("Combined instructions") +;(jest.requireMock("../../prompts/sections/custom-instructions") as any).addCustomInstructions = + mockAddCustomInstructions // Mock delay module jest.mock("delay", () => { diff --git a/src/shared/__tests__/experiments.test.ts b/src/shared/__tests__/experiments.test.ts new file mode 100644 index 0000000000..cd3828193f --- /dev/null +++ b/src/shared/__tests__/experiments.test.ts @@ -0,0 +1,47 @@ +import { EXPERIMENT_IDS, experimentConfigsMap, experiments as Experiments, ExperimentId } from "../experiments" + +describe("experiments", () => { + describe("POWER_STEERING", () => { + it("is configured correctly", () => { + expect(EXPERIMENT_IDS.POWER_STEERING).toBe("powerSteering") + expect(experimentConfigsMap.POWER_STEERING).toMatchObject({ + name: 'Use experimental "power steering" mode', + description: + "When enabled, Roo will remind the model about the details of its current mode definition more frequently. This will lead to stronger adherence to role definitions and custom instructions, but will use additional tokens.", + enabled: false, + }) + }) + }) + + describe("isEnabled", () => { + it("returns false when experiment is not enabled", () => { + const experiments: Record = { + powerSteering: false, + experimentalDiffStrategy: false, + search_and_replace: false, + insert_content: false, + } + expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) + }) + + it("returns true when experiment is enabled", () => { + const experiments: Record = { + powerSteering: true, + experimentalDiffStrategy: false, + search_and_replace: false, + insert_content: false, + } + expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) + }) + + it("returns false when experiment is not present", () => { + const experiments: Record = { + experimentalDiffStrategy: false, + search_and_replace: false, + insert_content: false, + powerSteering: false, + } + expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) + }) + }) +}) diff --git a/src/shared/__tests__/modes.test.ts b/src/shared/__tests__/modes.test.ts index aaf7338b5e..62c8480805 100644 --- a/src/shared/__tests__/modes.test.ts +++ b/src/shared/__tests__/modes.test.ts @@ -1,4 +1,13 @@ -import { isToolAllowedForMode, FileRestrictionError, ModeConfig } from "../modes" +// Mock setup must come before imports +jest.mock("vscode") +const mockAddCustomInstructions = jest.fn().mockResolvedValue("Combined instructions") +jest.mock("../../core/prompts/sections/custom-instructions", () => ({ + addCustomInstructions: mockAddCustomInstructions, +})) + +import { isToolAllowedForMode, FileRestrictionError, ModeConfig, getFullModeDetails, modes } from "../modes" +import * as vscode from "vscode" +import { addCustomInstructions } from "../../core/prompts/sections/custom-instructions" describe("isToolAllowedForMode", () => { const customModes: ModeConfig[] = [ @@ -324,6 +333,96 @@ describe("FileRestrictionError", () => { expect(error.name).toBe("FileRestrictionError") }) + describe("debug mode", () => { + it("is configured correctly", () => { + const debugMode = modes.find((mode) => mode.slug === "debug") + expect(debugMode).toBeDefined() + expect(debugMode).toMatchObject({ + slug: "debug", + name: "Debug", + roleDefinition: + "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.", + groups: ["read", "edit", "browser", "command", "mcp"], + }) + expect(debugMode?.customInstructions).toContain("Reflect on 5-7 different possible sources of the problem") + }) + }) + + describe("getFullModeDetails", () => { + beforeEach(() => { + jest.clearAllMocks() + ;(addCustomInstructions as jest.Mock).mockResolvedValue("Combined instructions") + }) + + it("returns base mode when no overrides exist", async () => { + const result = await getFullModeDetails("debug") + expect(result).toMatchObject({ + slug: "debug", + name: "Debug", + roleDefinition: + "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.", + }) + }) + + it("applies custom mode overrides", async () => { + const customModes = [ + { + slug: "debug", + name: "Custom Debug", + roleDefinition: "Custom debug role", + groups: ["read"], + }, + ] + + const result = await getFullModeDetails("debug", customModes) + expect(result).toMatchObject({ + slug: "debug", + name: "Custom Debug", + roleDefinition: "Custom debug role", + groups: ["read"], + }) + }) + + it("applies prompt component overrides", async () => { + const customModePrompts = { + debug: { + roleDefinition: "Overridden role", + customInstructions: "Overridden instructions", + }, + } + + const result = await getFullModeDetails("debug", undefined, customModePrompts) + expect(result.roleDefinition).toBe("Overridden role") + expect(result.customInstructions).toBe("Overridden instructions") + }) + + it("combines custom instructions when cwd provided", async () => { + const options = { + cwd: "/test/path", + globalCustomInstructions: "Global instructions", + preferredLanguage: "en", + } + + await getFullModeDetails("debug", undefined, undefined, options) + + expect(addCustomInstructions).toHaveBeenCalledWith( + expect.any(String), + "Global instructions", + "/test/path", + "debug", + { preferredLanguage: "en" }, + ) + }) + + it("falls back to first mode for non-existent mode", async () => { + const result = await getFullModeDetails("non-existent") + expect(result).toMatchObject({ + ...modes[0], + customInstructions: "", + }) + }) + }) + it("formats error message with description when provided", () => { const error = new FileRestrictionError("Markdown Editor", "\\.md$", "Markdown files only", "test.js") expect(error.message).toBe(