mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
Merge pull request #6511 from Kilo-Org/mark/contract-tests
test: add UI contract tests for ToolRegistry, getToolInfo, and DataProvider
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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/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 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: KILO_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", () => {
|
||||
// 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") {
|
||||
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 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)
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user