diff --git a/extensions/vscode/src/activation/InlineTipManager.ts b/extensions/vscode/src/activation/InlineTipManager.ts index 4a544147cc3..0921732c99b 100644 --- a/extensions/vscode/src/activation/InlineTipManager.ts +++ b/extensions/vscode/src/activation/InlineTipManager.ts @@ -236,7 +236,7 @@ export class InlineTipManager { private createSvgTooltipDecoration() { var backgroundColour = "#333333"; - if (this.theme) { + if (this.theme?.colors?.["editor.background"]) { backgroundColour = this.theme.colors["editor.background"]; } return vscode.window.createTextEditorDecorationType({ @@ -274,7 +274,8 @@ export class InlineTipManager { { ...baseTextConfig, x: SVG_CONFIG.chatLabelX, - fill: this.theme?.colors["editor.foreground"] ?? SVG_CONFIG.stroke, + fill: + this.theme?.colors?.["editor.foreground"] ?? SVG_CONFIG.stroke, }, SVG_CONFIG.chatLabel, ) @@ -291,7 +292,8 @@ export class InlineTipManager { { ...baseTextConfig, x: SVG_CONFIG.editLabelX, - fill: this.theme?.colors["editor.foreground"] ?? SVG_CONFIG.stroke, + fill: + this.theme?.colors?.["editor.foreground"] ?? SVG_CONFIG.stroke, }, SVG_CONFIG.editLabel, ) diff --git a/extensions/vscode/src/activation/InlineTipManager.vitest.ts b/extensions/vscode/src/activation/InlineTipManager.vitest.ts new file mode 100644 index 00000000000..65ee72ba204 --- /dev/null +++ b/extensions/vscode/src/activation/InlineTipManager.vitest.ts @@ -0,0 +1,59 @@ +import { describe, expect, it, vi } from "vitest"; + +// createSvgTooltipDecoration only calls window.createTextEditorDecorationType, +// but importing the module pulls in helpers that read the workspace config, so +// the mock covers that surface too. +vi.mock("vscode", () => ({ + window: { + createTextEditorDecorationType: vi + .fn() + .mockReturnValue({ dispose: vi.fn() }), + }, + workspace: { + getConfiguration: vi.fn().mockReturnValue({ get: vi.fn() }), + onDidChangeConfiguration: vi.fn().mockReturnValue({ dispose: vi.fn() }), + }, + ThemeColor: class { + constructor(public id: string) {} + }, + Uri: { file: vi.fn(), parse: vi.fn() }, +})); +vi.mock("core/control-plane/env", () => ({ EXTENSION_NAME: "continue" })); +vi.mock("../util/util", () => ({ + getMetaKeyLabel: () => "Cmd", + getMetaKeyName: () => "metaKey", +})); +vi.mock("../util/getTheme", () => ({ + getTheme: vi.fn().mockReturnValue({ colors: {} }), +})); +vi.mock("svg-builder", () => ({ + default: { newInstance: () => ({ width: () => ({ height: () => ({}) }) }) }, +})); + +import { InlineTipManager } from "./InlineTipManager"; + +describe("InlineTipManager.createSvgTooltipDecoration", () => { + it("does not throw when the active theme exposes no colors map", () => { + // A non-standard color theme can produce a Monaco theme whose `colors` is + // undefined. Previously the guard only checked `this.theme` was truthy and + // then read `this.theme.colors["editor.background"]`, throwing a TypeError + // that killed extension activation (issue #12947). + const instance = Object.create(InlineTipManager.prototype) as { + createSvgTooltipDecoration: () => unknown; + theme: unknown; + }; + instance.theme = { colors: undefined }; + + expect(() => instance.createSvgTooltipDecoration()).not.toThrow(); + }); + + it("uses the theme background when the colors map is present", () => { + const instance = Object.create(InlineTipManager.prototype) as { + createSvgTooltipDecoration: () => unknown; + theme: unknown; + }; + instance.theme = { colors: { "editor.background": "#101010" } }; + + expect(() => instance.createSvgTooltipDecoration()).not.toThrow(); + }); +});