Files
kilocode/packages/kilo-vscode/tests/unit/databridge-shape.test.ts
T
2026-04-27 14:52:29 +02:00

75 lines
3.2 KiB
TypeScript

import { describe, it, expect } from "bun:test"
import { readFileSync } from "node:fs"
import { join } from "node:path"
const APP_FILE = join(__dirname, "..", "..", "webview-ui", "src", "App.tsx")
const src = readFileSync(APP_FILE, "utf8")
/**
* Static guard against the perf regression fixed in this PR.
*
* PROBLEM (original bug):
* The webview's DataBridge wrapped the entire Data shape in
* `createMemo(() => ({ session, message, part, ... }))`, which
* re-runs whenever any dependency changes. Because the memo body
* read `store.parts[msg.id]` for every message in the session family
* (via `sessionFamily()`), a single token delta invalidated the memo
* → produced a fresh POJO → invalidated every downstream consumer
* that read `data.store.*`, including O(N) scans in every mounted
* SessionTurn. CPU profile showed ~46% of streaming main-thread time
* in Solid reactive work with this pattern.
*
* FIX:
* Expose `data` as a plain object with reactive getters over the
* underlying Solid stores. Consumers reading `data.store.part[Y]`
* now subscribe to only that specific key, so a text-delta on
* message Y only invalidates consumers that read part[Y] — not the
* whole tree.
*
* This static test catches any future change that re-introduces the
* buggy pattern. For the matching runtime reactivity assertion, see
* `tests/webview-reactivity/databridge-reactivity.test.ts`.
*/
describe("DataBridge shape (perf regression guard)", () => {
it("DataBridge exists in App.tsx", () => {
expect(src).toMatch(/export const DataBridge/)
})
it("`data` is not wrapped in createMemo(() => ({ ...message, ...part }))", () => {
// Extract the DataBridge function body up to its return statement.
const match = src.match(/export const DataBridge[\s\S]*?return \(\s*<DataProvider/)
expect(match).toBeTruthy()
const body = match![0]
// Buggy pattern: a createMemo whose body returns an object containing
// both `message:` and `part:` keys. That breaks per-key reactivity
// because the memo itself invalidates on any store mutation.
const badPattern = /const\s+data\s*=\s*createMemo\s*\([^)]*\)\s*=>\s*\(\s*\{[\s\S]*?\bmessage\s*:[\s\S]*?\bpart\s*:/
expect(body).not.toMatch(badPattern)
})
it("`data` uses reactive getters, not value-returning props", () => {
const match = src.match(/const\s+data\s*=\s*\{[\s\S]*?\n\s*\}\s*\n/)
expect(match).toBeTruthy()
const block = match![0]
// The fix relies on per-field getters so each consumer access is
// reactive independently. Require getters for `message` and `part`.
expect(block).toMatch(/get\s+message\s*\(\s*\)\s*\{/)
expect(block).toMatch(/get\s+part\s*\(\s*\)\s*\{/)
})
})
describe("DataBridge openDiff wiring (regression guard)", () => {
const openDiffBlock = () => {
const match = src.match(/const\s+openDiff\s*=\s*\(diff:\s*\{[\s\S]*?\n\s*\}\n\n\s*const\s+openUrl/)
expect(match).toBeTruthy()
return match![0]
}
it("wires openDiff to the openDiffVirtual webview message", () => {
expect(openDiffBlock()).toMatch(/postMessage\(\{\s*type:\s*["']openDiffVirtual["']\s*,\s*diff\s*\}\)/)
expect(src).toContain("onOpenDiff={openDiff}")
})
})