Files
oh-my-pi/packages/coding-agent/test/edit-streaming-preview.test.ts
T
can1357 21d50d5703 feat(coding-agent): added mode-aware streaming preview for edit outputs
- Added mode-aware streaming mode resolution and strategy registration in tool execution flow.
- Changed edit rendering to propagate mode and file-scoped diff previews across edit/vim tool outputs.
- Added chunk-mode streaming helpers (`loadChunkSource`, `computeChunkDiff`, `dropIncompleteLastEdit`) with abort-aware error fallbacks.
- Added strategy-specific streaming preview support for replace, patch, hashline, apply_patch, and vim modes.
- Fixed streaming previews by dropping incomplete trailing edits and handling invalid chunk or aborted inputs.
- Expanded streaming and chunk-diff tests for partial JSON, open edits, empty paths, abort signals, and file loading checks.
2026-04-24 08:24:30 +02:00

89 lines
3.4 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { dropIncompleteLastEdit, EDIT_MODE_STRATEGIES } from "@oh-my-pi/pi-coding-agent/edit";
describe("dropIncompleteLastEdit", () => {
test("keeps all entries when partialJson is undefined", () => {
const edits = [{ path: "a" }, { path: "b" }];
expect(dropIncompleteLastEdit(edits, undefined, "edits")).toEqual(edits);
});
test("keeps all entries when the trailing object is closed", () => {
const edits = [{ path: "a" }, { path: "b" }];
const partial = '{"edits":[{"path":"a"},{"path":"b"}]}';
expect(dropIncompleteLastEdit(edits, partial, "edits")).toEqual(edits);
});
test("drops the last entry when its closing } has not arrived", () => {
const edits = [{ path: "a" }, { path: "b" }];
const partial = '{"edits":[{"path":"a"},{"path":"b"';
expect(dropIncompleteLastEdit(edits, partial, "edits")).toEqual([{ path: "a" }]);
});
test("drops the last entry when a new {} has opened after the last close", () => {
const edits = [{ path: "a" }, { path: "b" }];
const partial = '{"edits":[{"path":"a"},{"pat';
expect(dropIncompleteLastEdit(edits, partial, "edits")).toEqual([{ path: "a" }]);
});
test("leaves empty edits alone", () => {
expect(dropIncompleteLastEdit([], '{"edits":[', "edits")).toEqual([]);
});
});
describe("chunk extractCompleteEdits", () => {
const strategy = EDIT_MODE_STRATEGIES.chunk;
test("passes through a single complete entry", () => {
const args = {
edits: [{ path: "a.ts", write: "foo" }],
__partialJson: '{"edits":[{"path":"a.ts","write":"foo"}]}',
};
const out = strategy.extractCompleteEdits(args, args.__partialJson) as typeof args;
expect(out.edits).toHaveLength(1);
});
test("drops trailing entry when partial JSON has open-brace after last close", () => {
const args = {
edits: [{ path: "a.ts", write: "foo" }, { path: "b.ts" }],
__partialJson: '{"edits":[{"path":"a.ts","write":"foo"},{"path":"b.ts"',
};
const out = strategy.extractCompleteEdits(args, args.__partialJson) as typeof args;
expect(out.edits).toHaveLength(1);
expect(out.edits[0].path).toBe("a.ts");
});
test("drops trailing entry when partial JSON ends in ':nu' (write: null guard)", () => {
const args = {
edits: [
{ path: "a.ts", write: "foo" },
{ path: "b.ts", write: null },
],
// simulates partial-json coercing the in-flight `nu` to `null`
__partialJson: '{"edits":[{"path":"a.ts","write":"foo"},{"path":"b.ts","write":nu',
};
const out = strategy.extractCompleteEdits(args, args.__partialJson) as typeof args;
// Last entry should be dropped because its `}` hasn't arrived yet — so
// the "delete" rendering is suppressed while streaming.
expect(out.edits).toHaveLength(1);
expect(out.edits[0].path).toBe("a.ts");
});
});
describe("apply_patch extractCompleteEdits", () => {
const strategy = EDIT_MODE_STRATEGIES.apply_patch;
test("returns args unchanged (payload is plain text)", () => {
const args = { input: "*** Begin Patch\n*** Update File: a.ts\n@@\n-x\n+y\n*** End Patch\n" };
expect(strategy.extractCompleteEdits(args, undefined)).toEqual(args);
});
});
describe("vim extractCompleteEdits", () => {
const strategy = EDIT_MODE_STRATEGIES.vim;
test("returns args unchanged (vim stream handled elsewhere)", () => {
const args = { file: "a.ts", steps: [] };
expect(strategy.extractCompleteEdits(args, undefined)).toEqual(args);
});
});