Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions integrations/pi-villani/src/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ test("/villani keeps waiting after nonzero tool_finished and renders next model
else process.env.VILLANI_USE_PI_MODEL = oldUsePi;
}
assert.doesNotMatch(notes.join("\n"), /Villani tool finished/);
assert.ok(statuses.includes("Villani is thinking..."));
assert.ok(statuses.some((s) => /^Villani|^Villani/.test(s)));
assert.equal(sent.length, 1);
});

Expand All @@ -600,10 +600,8 @@ test("repeated model_request_started updates status without notify spam", async
};
await renderBridgeEvent({ type: "model_request_started" }, {}, ctx);
await renderBridgeEvent({ type: "model_request_started" }, {}, ctx);
assert.deepEqual(statuses, [
"Villani is thinking...",
"Villani is thinking...",
]);
assert.equal(statuses.length, 2);
assert.ok(statuses.every((s) => /^Villani/.test(s)));
assert.deepEqual(notes, []);
});

Expand Down Expand Up @@ -745,11 +743,9 @@ test("tool and command events render readable English", async () => {
await renderBridgeEvent({ type: "tool_started", tool: "Bash" }, {}, ctx);
await renderBridgeEvent({ type: "command_started", command: "python -m pytest -v" }, {}, ctx);
await renderBridgeEvent({ type: "command_finished", command: "python -m pytest -v", exit_code: 1, stderr_preview: "boom" }, {}, ctx);
assert.ok(notes.includes("Preparing command"));
assert.ok(notes.includes("Running command:\npython -m pytest -v"));
assert.ok(notes.includes("Command finished: exit 1\n\nstderr:\nboom"));
assert.doesNotMatch(notes.join("\n"), /tool_started|command_started|command_finished/);
assert.ok(statuses.includes("Running command..."));
assert.deepEqual(notes, ["Command finished: exit 1\n\nstderr:\nboom"]);
assert.doesNotMatch(notes.join("\n"), /tool_started|command_started|command_finished|Preparing command|Running command:/);
assert.ok(statuses.some((s) => /^Villani/.test(s)));
assert.ok(widgets.some((w) => String(w).includes("python -m pytest -v")));
});

Expand All @@ -771,7 +767,7 @@ test("model request clears stale command widget and final clears widget", async
await renderBridgeEvent({ type: "run_completed", summary: "final assistant summary" }, {}, ctx);
assert.ok(widgets.some((w) => String(w).includes("Command finished: exit 0")));
assert.ok(widgets.some((w) => w === undefined));
assert.ok(statuses.includes("Completed"));
assert.ok(statuses.some((s) => /^Villani/.test(s)));
});

test("final message includes final assistant summary", async () => {
Expand Down
100 changes: 100 additions & 0 deletions integrations/pi-villani/src/render.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
cleanAssistantText,
reduceVillaniUiState,
renderBridgeEvent,
resetVillaniCopyCounters,
resetVillaniUiState,
toolStartedMessage,
villaniCopy,
} from './render.js';
import { approvalMessage, approvalTitle } from './index.js';

function ctxRecorder() {
const statuses: string[] = [];
const widgets: any[] = [];
const notifications: string[] = [];
return {
statuses,
widgets,
notifications,
ctx: { ui: {
setStatus: async (_key: string, value: string) => { statuses.push(value); },
setWidget: async (_key: string, value: any) => { widgets.push(value); },
notify: async (message: string) => { notifications.push(message); },
} },
};
}

test('toolStartedMessage for Read with path does not say unknown', () => {
const msg = toolStartedMessage({ type: 'tool_started', tool: 'Read', path: 'src/foo.py' });
assert.match(msg, /File: src\/foo.py/);
assert.doesNotMatch(msg, /unknown/i);
});

test('toolStartedMessage for Read without path does not say unknown', () => {
const msg = toolStartedMessage({ type: 'tool_started', tool: 'Read' });
assert.equal(msg, 'Villani reads file. File nervous.');
assert.doesNotMatch(msg, /unknown/i);
});

test('Read event sets reading Villani copy', () => {
resetVillaniCopyCounters();
const state = reduceVillaniUiState({ phase: 'x' }, { type: 'tool_started', tool: 'Read', path: 'a.ts' });
assert.equal(state.phase, 'Villani reads file. File nervous.');
});

test('Write/Patch event sets writing Villani copy', () => {
resetVillaniCopyCounters();
assert.equal(reduceVillaniUiState({ phase: 'x' }, { type: 'tool_started', tool: 'Write' }).phase, 'Villani makes file obey...');
assert.equal(reduceVillaniUiState({ phase: 'x' }, { type: 'tool_started', tool: 'Patch' }).phase, 'Villanipatch imposed...');
});

test('Bash command event sets running Villani copy', () => {
resetVillaniCopyCounters();
const state = reduceVillaniUiState({ phase: 'x' }, { type: 'tool_started', tool: 'Bash', input: { command: 'echo hi' } });
assert.equal(state.phase, 'Villani gives command...');
});

test('pytest command event sets testing Villani copy', () => {
resetVillaniCopyCounters();
const state = reduceVillaniUiState({ phase: 'x' }, { type: 'tool_started', tool: 'Bash', input: { command: 'python -m pytest -q' } });
assert.equal(state.phase, 'Villani begins inspection...');
});

test('run_completed uses complete Villani copy', () => {
resetVillaniCopyCounters();
assert.equal(reduceVillaniUiState({ phase: 'x' }, { type: 'run_completed' }).phase, 'Villanified. Accept result.');
});

test('run_failed uses failure Villani copy', () => {
resetVillaniCopyCounters();
assert.equal(reduceVillaniUiState({ phase: 'x' }, { type: 'run_failed', error: 'boom' }).phase, 'Villani sees failure. Unacceptable.');
});

test('villaniCopy rotates deterministically', () => {
resetVillaniCopyCounters();
assert.equal(villaniCopy('thinking'), 'Villani is make plan...');
assert.equal(villaniCopy('thinking'), 'Villaniplan forming...');
assert.equal(villaniCopy('thinking'), 'Villani thinks. Nobody interrupt.');
});

test('stream_text renders clean assistant text unchanged', async () => {
resetVillaniUiState();
const rec = ctxRecorder();
await renderBridgeEvent({ type: 'stream_text', text: "\n\nI'll start by exploring the repository structure and finding the failing tests.\n\n" }, {}, rec.ctx);
assert.deepEqual(rec.notifications, ["I'll start by exploring the repository structure and finding the failing tests."]);
});

test('cleanAssistantText trims without Villani prefix', () => {
assert.equal(cleanAssistantText('\n\nhello\n\n'), 'hello');
});

test('approval message remains literal and readable', () => {
const request = { tool: 'Bash', summary: 'Run command', input: { command: 'python -m pytest -v' } };
assert.equal(approvalTitle(request), 'Villani wants to run a shell command');
const msg = approvalMessage(request);
assert.match(msg, /Command:\npython -m pytest -v/);
assert.doesNotMatch(msg, /\[object Object\]/);
});
Loading
Loading