From 5716e842c8128d665043ffae8a1727fa1da31b28 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 12:54:57 +0100 Subject: [PATCH 1/7] test: add ToolRegistry tool name contract test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies that tool names kilo-vscode depends on (bash, task, read, write, glob, edit, todowrite) are still registered in the upstream ToolRegistry in packages/ui/src/components/message-part.tsx. Ref: testing-gaps-and-visual-testing-plan.md §1.2 --- .../tests/unit/tool-registry-contract.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts diff --git a/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts new file mode 100644 index 00000000000..9c34ad5aa11 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts @@ -0,0 +1,36 @@ +/** + * ToolRegistry tool name contract test. + * + * The kilo-vscode webview overrides specific tool names in + * VscodeToolOverrides.tsx (e.g. "bash") and TaskToolExpanded.tsx (e.g. "task"). + * VscodeSessionTurn also calls getToolInfo() from @kilocode/kilo-ui/message-part. + * + * This test verifies that the tool names we depend on are still registered + * in the upstream ToolRegistry (message-part.tsx). If upstream removes or + * renames a tool, this test will fail. + */ + +import { describe, it, expect } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") +const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") + +/** + * Tool names that kilo-vscode overrides or uses directly. + * Sources: + * - VscodeToolOverrides.tsx: "bash" + * - TaskToolExpanded.tsx: "task" + * - VscodeSessionTurn.tsx uses getToolInfo() which handles all of these + */ +const TOOL_NAMES_WE_DEPEND_ON = ["bash", "task", "read", "write", "glob", "edit", "todowrite"] + +describe("ToolRegistry tool name contract", () => { + it("all tools overridden or used by kilo-vscode are still registered in ToolRegistry", () => { + const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") + for (const name of TOOL_NAMES_WE_DEPEND_ON) { + expect(src, `Tool "${name}" no longer registered in message-part.tsx`).toContain(`name: "${name}"`) + } + }) +}) From 742fb03afc60b00f8e11fb2ceb0247fe2b327022 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 13:04:00 +0100 Subject: [PATCH 2/7] test: add getToolInfo() return shape contract test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies that the ToolInfo type and getToolInfo() function exported from message-part.tsx still have the icon and title fields that VscodeSessionTurn depends on. Ref: testing-gaps-and-visual-testing-plan.md §1.3 --- .../tests/unit/tool-info-contract.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/kilo-vscode/tests/unit/tool-info-contract.test.ts diff --git a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts new file mode 100644 index 00000000000..d7c76c28adf --- /dev/null +++ b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts @@ -0,0 +1,35 @@ +/** + * getToolInfo() return shape contract test. + * + * VscodeSessionTurn calls getToolInfo() from @kilocode/kilo-ui/message-part + * and relies on the returned ToolInfo having `icon` and `title` fields. + * + * This test verifies that the ToolInfo type exported from message-part.tsx + * still declares those fields. If upstream changes the shape, this will fail. + */ + +import { describe, it, expect } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") +const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") + +describe("getToolInfo() return shape contract", () => { + it("getToolInfo still exports expected shape fields", () => { + const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") + // ToolInfo type must still have these fields + expect(src).toMatch(/icon\s*:/) + expect(src).toMatch(/title\s*:/) + }) + + it("getToolInfo function is exported", () => { + const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") + expect(src).toContain("export function getToolInfo") + }) + + it("ToolInfo type is exported", () => { + const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") + expect(src).toContain("export type ToolInfo") + }) +}) From 3ebff2daf6fd4be7b430f9f6cb636573d2471c78 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 13:04:20 +0100 Subject: [PATCH 3/7] test: add DataProvider props contract test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies that the DataProvider in packages/ui/src/context/data.tsx still accepts the onOpenFile prop and exports OpenFileFn. These are kilocode additions that the extension webview depends on. Ref: testing-gaps-and-visual-testing-plan.md §1.4 --- .../tests/unit/data-provider-contract.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/kilo-vscode/tests/unit/data-provider-contract.test.ts diff --git a/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts b/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts new file mode 100644 index 00000000000..4a254e5eb5f --- /dev/null +++ b/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts @@ -0,0 +1,29 @@ +/** + * DataProvider props contract test. + * + * The kilo-vscode webview passes an onOpenFile callback to the DataProvider + * from @kilocode/kilo-ui. This test verifies that the DataProvider still + * accepts the onOpenFile prop and that the OpenFileFn type is still defined. + * + * If upstream removes the kilocode_change additions in data.tsx, this will fail. + */ + +import { describe, it, expect } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") +const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data.tsx") + +describe("DataProvider props contract", () => { + it("DataProvider still accepts onOpenFile prop", () => { + const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") + expect(src).toContain("onOpenFile") + expect(src).toContain("OpenFileFn") + }) + + it("openFile is exposed on the context return value", () => { + const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") + expect(src).toContain("openFile:") + }) +}) From 020012b3aa09115be4dba2397ee1a61238bdc857 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 13:52:36 +0100 Subject: [PATCH 4/7] fix: tighten contract test regex patterns per review feedback - tool-info-contract: match icon/title within the ToolInfo type definition specifically, not any occurrence in the file - data-provider-contract: match openFile: props.onOpenFile pattern instead of bare substring match --- .../kilo-vscode/tests/unit/data-provider-contract.test.ts | 2 +- packages/kilo-vscode/tests/unit/tool-info-contract.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts b/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts index 4a254e5eb5f..3b3a966c509 100644 --- a/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts @@ -24,6 +24,6 @@ describe("DataProvider props contract", () => { it("openFile is exposed on the context return value", () => { const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") - expect(src).toContain("openFile:") + expect(src).toMatch(/openFile:\s*props\.onOpenFile/) }) }) diff --git a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts index d7c76c28adf..cceca3251f9 100644 --- a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts @@ -18,9 +18,9 @@ const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/m describe("getToolInfo() return shape contract", () => { it("getToolInfo still exports expected shape fields", () => { const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") - // ToolInfo type must still have these fields - expect(src).toMatch(/icon\s*:/) - expect(src).toMatch(/title\s*:/) + // ToolInfo type definition must still have these fields + expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*icon\s*:/s) + expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*title\s*:/s) }) it("getToolInfo function is exported", () => { From 9f6b227de9dde60e9ae31b063244aa61fa31dac2 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 13:58:18 +0100 Subject: [PATCH 5/7] fix: correct JSDoc - TaskToolExpanded calls getToolInfo, not VscodeSessionTurn Per review: VscodeSessionTurn imports Message/UserMessageDisplay, while TaskToolExpanded is the actual consumer of getToolInfo() and ToolInfo. --- packages/kilo-vscode/tests/unit/tool-info-contract.test.ts | 2 +- .../kilo-vscode/tests/unit/tool-registry-contract.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts index cceca3251f9..5f94f63285a 100644 --- a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts @@ -1,7 +1,7 @@ /** * getToolInfo() return shape contract test. * - * VscodeSessionTurn calls getToolInfo() from @kilocode/kilo-ui/message-part + * TaskToolExpanded calls getToolInfo() from @kilocode/kilo-ui/message-part * and relies on the returned ToolInfo having `icon` and `title` fields. * * This test verifies that the ToolInfo type exported from message-part.tsx diff --git a/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts index 9c34ad5aa11..ecadf08f35e 100644 --- a/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts @@ -3,7 +3,7 @@ * * The kilo-vscode webview overrides specific tool names in * VscodeToolOverrides.tsx (e.g. "bash") and TaskToolExpanded.tsx (e.g. "task"). - * VscodeSessionTurn also calls getToolInfo() from @kilocode/kilo-ui/message-part. + * VscodeSessionTurn imports Message/UserMessageDisplay from @kilocode/kilo-ui/message-part. * * This test verifies that the tool names we depend on are still registered * in the upstream ToolRegistry (message-part.tsx). If upstream removes or @@ -22,7 +22,7 @@ const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/m * Sources: * - VscodeToolOverrides.tsx: "bash" * - TaskToolExpanded.tsx: "task" - * - VscodeSessionTurn.tsx uses getToolInfo() which handles all of these + * - TaskToolExpanded.tsx uses getToolInfo() which handles all of these */ const TOOL_NAMES_WE_DEPEND_ON = ["bash", "task", "read", "write", "glob", "edit", "todowrite"] From 9aa08b0d27d6a6c677b860ae947581c34a1344e3 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 15:50:05 +0100 Subject: [PATCH 6/7] refactor: replace source-reading contract tests with runtime imports Instead of reading source files and matching regexes, actually import the upstream modules and verify exports at runtime. Uses Bun.spawnSync to run from packages/ui/ where the SolidJS JSX context is available. - ToolRegistry.render(name) returns a function for each depended-on tool - getToolInfo is an exported function - DataProvider and useData are exported functions This is more robust than regex matching and catches real breakage. --- .../tests/unit/data-provider-contract.test.ts | 29 ----- .../tests/unit/kilo-ui-contract.test.ts | 100 ++++++++++++++++++ .../tests/unit/tool-info-contract.test.ts | 35 ------ .../tests/unit/tool-registry-contract.test.ts | 36 ------- 4 files changed, 100 insertions(+), 100 deletions(-) delete mode 100644 packages/kilo-vscode/tests/unit/data-provider-contract.test.ts create mode 100644 packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts delete mode 100644 packages/kilo-vscode/tests/unit/tool-info-contract.test.ts delete mode 100644 packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts diff --git a/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts b/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts deleted file mode 100644 index 3b3a966c509..00000000000 --- a/packages/kilo-vscode/tests/unit/data-provider-contract.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * DataProvider props contract test. - * - * The kilo-vscode webview passes an onOpenFile callback to the DataProvider - * from @kilocode/kilo-ui. This test verifies that the DataProvider still - * accepts the onOpenFile prop and that the OpenFileFn type is still defined. - * - * If upstream removes the kilocode_change additions in data.tsx, this will fail. - */ - -import { describe, it, expect } from "bun:test" -import fs from "node:fs" -import path from "node:path" - -const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") -const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data.tsx") - -describe("DataProvider props contract", () => { - it("DataProvider still accepts onOpenFile prop", () => { - const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") - expect(src).toContain("onOpenFile") - expect(src).toContain("OpenFileFn") - }) - - it("openFile is exposed on the context return value", () => { - const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") - expect(src).toMatch(/openFile:\s*props\.onOpenFile/) - }) -}) diff --git a/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts new file mode 100644 index 00000000000..6bed425b60e --- /dev/null +++ b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts @@ -0,0 +1,100 @@ +/** + * Runtime contract tests for kilo-vscode's dependencies on @kilocode/kilo-ui. + * + * These tests import the upstream UI modules directly and verify at runtime + * that the exports kilo-vscode depends on still exist with the expected shape. + * + * Because the upstream modules use SolidJS JSX (jsxImportSource: "solid-js"), + * they must be loaded from within packages/ui/ where bun picks up the correct + * tsconfig. We use Bun.spawnSync to run a small check script in that context. + */ + +import { describe, it, expect } from "bun:test" +import path from "node:path" + +const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") +const UI_DIR = path.join(MONOREPO_ROOT, "packages/ui") + +function check(code: string): { ok: boolean; output: string } { + const result = Bun.spawnSync(["bun", "--conditions=browser", "-e", code], { + cwd: UI_DIR, + stdout: "pipe", + stderr: "pipe", + }) + const stdout = result.stdout.toString() + const stderr = result.stderr.toString() + return { + ok: result.exitCode === 0, + output: stdout + stderr, + } +} + +/** + * Tool names that kilo-vscode overrides or uses directly. + * Sources: + * - VscodeToolOverrides.tsx: "bash" + * - TaskToolExpanded.tsx: "task" + * - TaskToolExpanded.tsx uses getToolInfo() which handles all of these + */ +const TOOL_NAMES_WE_DEPEND_ON = ["bash", "task", "read", "write", "glob", "edit", "todowrite"] + +describe("ToolRegistry tool name contract (runtime)", () => { + it("all tools used by kilo-vscode are registered in ToolRegistry", () => { + const names = JSON.stringify(TOOL_NAMES_WE_DEPEND_ON) + const result = check(` + import { ToolRegistry } from "./src/components/message-part.tsx" + const names = ${names} + const missing = names.filter(n => typeof ToolRegistry.render(n) !== "function") + if (missing.length) { + console.error("Missing tools: " + missing.join(", ")) + process.exit(1) + } + console.log("ok") + `) + expect(result.ok, `ToolRegistry check failed: ${result.output}`).toBe(true) + }) +}) + +describe("getToolInfo() export contract (runtime)", () => { + it("getToolInfo is an exported function", () => { + const result = check(` + import { getToolInfo } from "./src/components/message-part.tsx" + if (typeof getToolInfo !== "function") { + console.error("getToolInfo is " + typeof getToolInfo) + process.exit(1) + } + console.log("ok") + `) + expect(result.ok, `getToolInfo check failed: ${result.output}`).toBe(true) + }) + + it("ToolInfo type is exported (type re-exported as value via ToolRegistry)", () => { + const result = check(` + import { ToolRegistry } from "./src/components/message-part.tsx" + if (typeof ToolRegistry !== "object" || typeof ToolRegistry.register !== "function") { + console.error("ToolRegistry shape wrong") + process.exit(1) + } + console.log("ok") + `) + expect(result.ok, `ToolRegistry shape check failed: ${result.output}`).toBe(true) + }) +}) + +describe("DataProvider contract (runtime)", () => { + it("DataProvider and useData are exported functions", () => { + const result = check(` + import { DataProvider, useData } from "./src/context/data.tsx" + if (typeof DataProvider !== "function") { + console.error("DataProvider is " + typeof DataProvider) + process.exit(1) + } + if (typeof useData !== "function") { + console.error("useData is " + typeof useData) + process.exit(1) + } + console.log("ok") + `) + expect(result.ok, `DataProvider check failed: ${result.output}`).toBe(true) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts deleted file mode 100644 index 5f94f63285a..00000000000 --- a/packages/kilo-vscode/tests/unit/tool-info-contract.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * getToolInfo() return shape contract test. - * - * TaskToolExpanded calls getToolInfo() from @kilocode/kilo-ui/message-part - * and relies on the returned ToolInfo having `icon` and `title` fields. - * - * This test verifies that the ToolInfo type exported from message-part.tsx - * still declares those fields. If upstream changes the shape, this will fail. - */ - -import { describe, it, expect } from "bun:test" -import fs from "node:fs" -import path from "node:path" - -const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") -const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") - -describe("getToolInfo() return shape contract", () => { - it("getToolInfo still exports expected shape fields", () => { - const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") - // ToolInfo type definition must still have these fields - expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*icon\s*:/s) - expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*title\s*:/s) - }) - - it("getToolInfo function is exported", () => { - const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") - expect(src).toContain("export function getToolInfo") - }) - - it("ToolInfo type is exported", () => { - const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") - expect(src).toContain("export type ToolInfo") - }) -}) diff --git a/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts b/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts deleted file mode 100644 index ecadf08f35e..00000000000 --- a/packages/kilo-vscode/tests/unit/tool-registry-contract.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * ToolRegistry tool name contract test. - * - * The kilo-vscode webview overrides specific tool names in - * VscodeToolOverrides.tsx (e.g. "bash") and TaskToolExpanded.tsx (e.g. "task"). - * VscodeSessionTurn imports Message/UserMessageDisplay from @kilocode/kilo-ui/message-part. - * - * This test verifies that the tool names we depend on are still registered - * in the upstream ToolRegistry (message-part.tsx). If upstream removes or - * renames a tool, this test will fail. - */ - -import { describe, it, expect } from "bun:test" -import fs from "node:fs" -import path from "node:path" - -const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") -const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") - -/** - * Tool names that kilo-vscode overrides or uses directly. - * Sources: - * - VscodeToolOverrides.tsx: "bash" - * - TaskToolExpanded.tsx: "task" - * - TaskToolExpanded.tsx uses getToolInfo() which handles all of these - */ -const TOOL_NAMES_WE_DEPEND_ON = ["bash", "task", "read", "write", "glob", "edit", "todowrite"] - -describe("ToolRegistry tool name contract", () => { - it("all tools overridden or used by kilo-vscode are still registered in ToolRegistry", () => { - const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") - for (const name of TOOL_NAMES_WE_DEPEND_ON) { - expect(src, `Tool "${name}" no longer registered in message-part.tsx`).toContain(`name: "${name}"`) - } - }) -}) From a81653425a7cc4c6ead21b7d9537758f4899bc28 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 2 Mar 2026 16:02:28 +0100 Subject: [PATCH 7/7] fix: address review - use kilo-ui path, add onOpenFile/ToolInfo source checks Per review feedback: - Run runtime checks from packages/kilo-ui/ (the actual import path kilo-vscode uses) instead of packages/ui/ - Add source-level check for onOpenFile prop and OpenFileFn type in DataProvider (TypeScript types erased at runtime, so source analysis needed) - Add source-level check for ToolInfo type icon/title fields - Document why getToolInfo() can't be invoked (needs SolidJS context) - ToolProps is a TS interface caught by typecheck, not runtime-testable --- .../tests/unit/kilo-ui-contract.test.ts | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts index 6bed425b60e..bebbac55310 100644 --- a/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-ui-contract.test.ts @@ -5,19 +5,26 @@ * that the exports kilo-vscode depends on still exist with the expected shape. * * Because the upstream modules use SolidJS JSX (jsxImportSource: "solid-js"), - * they must be loaded from within packages/ui/ where bun picks up the correct - * tsconfig. We use Bun.spawnSync to run a small check script in that context. + * they must be loaded from within packages/kilo-ui/ where bun picks up the + * correct tsconfig. We use Bun.spawnSync to run a small check script in that + * context. + * + * TypeScript types (OpenFileFn, ToolProps, ToolInfo) are erased at runtime, + * so those are verified via source analysis on the upstream file. */ import { describe, it, expect } from "bun:test" +import fs from "node:fs" import path from "node:path" const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..") -const UI_DIR = path.join(MONOREPO_ROOT, "packages/ui") +const KILO_UI_DIR = path.join(MONOREPO_ROOT, "packages/kilo-ui") +const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data.tsx") +const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx") function check(code: string): { ok: boolean; output: string } { const result = Bun.spawnSync(["bun", "--conditions=browser", "-e", code], { - cwd: UI_DIR, + cwd: KILO_UI_DIR, stdout: "pipe", stderr: "pipe", }) @@ -57,6 +64,8 @@ describe("ToolRegistry tool name contract (runtime)", () => { describe("getToolInfo() export contract (runtime)", () => { it("getToolInfo is an exported function", () => { + // Note: getToolInfo() calls useI18n() internally, so we cannot invoke it + // outside a SolidJS rendering context. We verify it exists as a function. const result = check(` import { getToolInfo } from "./src/components/message-part.tsx" if (typeof getToolInfo !== "function") { @@ -68,16 +77,11 @@ describe("getToolInfo() export contract (runtime)", () => { expect(result.ok, `getToolInfo check failed: ${result.output}`).toBe(true) }) - it("ToolInfo type is exported (type re-exported as value via ToolRegistry)", () => { - const result = check(` - import { ToolRegistry } from "./src/components/message-part.tsx" - if (typeof ToolRegistry !== "object" || typeof ToolRegistry.register !== "function") { - console.error("ToolRegistry shape wrong") - process.exit(1) - } - console.log("ok") - `) - expect(result.ok, `ToolRegistry shape check failed: ${result.output}`).toBe(true) + it("ToolInfo type still declares icon and title fields (source)", () => { + // ToolInfo is a TypeScript type erased at runtime, so we verify via source + const src = fs.readFileSync(MESSAGE_PART_FILE, "utf-8") + expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*icon\s*:/s) + expect(src).toMatch(/export type ToolInfo\s*=\s*\{[^}]*title\s*:/s) }) }) @@ -97,4 +101,13 @@ describe("DataProvider contract (runtime)", () => { `) expect(result.ok, `DataProvider check failed: ${result.output}`).toBe(true) }) + + it("DataProvider accepts onOpenFile prop and exports OpenFileFn (source)", () => { + // onOpenFile and OpenFileFn are kilocode_change additions — TypeScript types + // erased at runtime, so we verify via source analysis + const src = fs.readFileSync(DATA_CONTEXT_FILE, "utf-8") + expect(src).toContain("onOpenFile") + expect(src).toContain("OpenFileFn") + expect(src).toMatch(/openFile:\s*props\.onOpenFile/) + }) })