Files
oh-my-pi/packages/coding-agent/test/eval/parse.test.ts
T
can1357 cf60e6df51 feat(coding-agent): implemented eval framework and replaced python tool
- Added a unified eval framework with parser grammar, backend interfaces, and JS/Python execution result types.
- Added eval tool docs and updated prompts for fenced cells, `eval.py`/`eval.js`, and fallback behavior.
- Replaced the built-in `python` tool with `eval` across registry, rendering, interactive modes, and tool settings.
- Migrated Python execution runtime from `src/ipy` to `src/eval/py`, renamed state fields, and removed legacy introspection.
- Refactored browser tooling from in-process VM helpers to worker-managed tab supervisors and protocol transport.
- Added eval parser fallback and JS tool-bridge tests, updated imports, and removed obsolete python-mode suites.
2026-04-30 18:08:37 +02:00

252 lines
5.8 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { parseEvalInput } from "../../src/eval/parse";
describe("parseEvalInput", () => {
it("parses a single fenced cell with positional title and timeout", () => {
const result = parseEvalInput(`\`\`\`py setup 15s
print("hi")
\`\`\`
`);
expect(result.cells).toEqual([
{
index: 0,
title: "setup",
code: 'print("hi")',
language: "python",
languageOrigin: "fence",
timeoutMs: 15_000,
reset: false,
},
]);
});
it("treats rst=true as a per-language kernel wipe for that cell", () => {
const result = parseEvalInput(`\`\`\`py rst=true id="bootstrap"
import json
\`\`\`
\`\`\`js rst=true
const x = 1;
\`\`\`
`);
expect(result.cells.map(cell => [cell.language, cell.reset, cell.title])).toEqual([
["python", true, "bootstrap"],
["js", true, undefined],
]);
});
it("inherits language and runs without reset for empty fence info", () => {
const result = parseEvalInput(`\`\`\`js
const a = 1;
\`\`\`
\`\`\`
const b = a + 1;
\`\`\`
`);
expect(result.cells.map(cell => [cell.language, cell.languageOrigin, cell.code, cell.reset])).toEqual([
["js", "fence", "const a = 1;", false],
["js", "fence", "const b = a + 1;", false],
]);
});
it("supports tilde fences and case-insensitive language tokens including ipython aliases", () => {
const result = parseEvalInput(`~~~TypeScript
const a = 1;
~~~
\`\`\`IPython
print("ipy")
\`\`\`
`);
expect(result.cells.map(cell => [cell.language, cell.languageOrigin])).toEqual([
["js", "fence"],
["python", "fence"],
]);
});
it("uses canonical id and t attributes, with explicit attrs winning over positional", () => {
const result = parseEvalInput(`\`\`\`py 5s some words t=2m id="explicit win"
print(1)
\`\`\`
`);
expect(result.cells[0]).toMatchObject({
title: "explicit win",
timeoutMs: 120_000,
language: "python",
});
});
it("accepts fallback aliases for id, t, and rst keys", () => {
const cases = [
{ key: "title", expectTitle: "alpha" },
{ key: "name", expectTitle: "alpha" },
{ key: "cell", expectTitle: "alpha" },
{ key: "file", expectTitle: "alpha" },
{ key: "label", expectTitle: "alpha" },
];
for (const { key, expectTitle } of cases) {
const result = parseEvalInput(`\`\`\`py ${key}="alpha"\nprint(1)\n\`\`\`\n`);
expect(result.cells[0].title).toBe(expectTitle);
}
const timeoutAliases = ["timeout", "duration", "time"];
for (const key of timeoutAliases) {
const result = parseEvalInput(`\`\`\`py ${key}=2m\nprint(1)\n\`\`\`\n`);
expect(result.cells[0].timeoutMs).toBe(120_000);
}
const resetAliases = ["reset"];
for (const key of resetAliases) {
const result = parseEvalInput(`\`\`\`py ${key}=true\nprint(1)\n\`\`\`\n`);
expect(result.cells[0].reset).toBe(true);
}
});
it("first occurrence wins when canonical and alias collide", () => {
const canonicalFirst = parseEvalInput(`\`\`\`py id="canon" title="alias"
print(1)
\`\`\`
`);
const aliasFirst = parseEvalInput(`\`\`\`py title="alias" id="canon"
print(1)
\`\`\`
`);
expect(canonicalFirst.cells[0].title).toBe("canon");
expect(aliasFirst.cells[0].title).toBe("alias");
});
it("parses millisecond, second, and minute durations", () => {
const result = parseEvalInput(`\`\`\`py 500ms
a = 1
\`\`\`
\`\`\`py 5
a = 2
\`\`\`
\`\`\`py 2m
a = 3
\`\`\`
`);
expect(result.cells.map(cell => cell.timeoutMs)).toEqual([500, 5_000, 120_000]);
});
it("treats unrecognized fence info as title and inherits the language", () => {
const result = parseEvalInput(`\`\`\`ruby
puts "no"
\`\`\`
`);
expect(result.cells[0]).toMatchObject({
title: "ruby",
code: 'puts "no"',
language: "python",
languageOrigin: "default",
});
});
it("joins multiple positional title fragments with spaces", () => {
const result = parseEvalInput(`\`\`\`py compute totals
print(1)
\`\`\`
`);
expect(result.cells[0].title).toBe("compute totals");
});
it("accepts back-to-back fenced cells without blank separators", () => {
const result = parseEvalInput(`\`\`\`py id=a
print("a")
\`\`\`
\`\`\`py id=b
print("b")
\`\`\`
`);
expect(result.cells.map(cell => [cell.title, cell.code])).toEqual([
["a", 'print("a")'],
["b", 'print("b")'],
]);
});
it("wraps bare code with no fences in a single implicit cell", () => {
const result = parseEvalInput(`print("hello")
print("world")
`);
expect(result.cells).toEqual([
{
index: 0,
title: undefined,
code: 'print("hello")\nprint("world")',
language: "python",
languageOrigin: "default",
timeoutMs: 30_000,
reset: false,
},
]);
});
it("surfaces raw inter-fence content as its own implicit cell that inherits language", () => {
const result = parseEvalInput(`\`\`\`js
const x = 1;
\`\`\`
inherited tail
`);
expect(result.cells.map(cell => [cell.language, cell.languageOrigin, cell.code])).toEqual([
["js", "fence", "const x = 1;"],
["js", "fence", "inherited tail"],
]);
});
it("treats unclosed fences leniently and closes them at end of input", () => {
const result = parseEvalInput(`\`\`\`py
print("still typing")`);
expect(result.cells).toHaveLength(1);
expect(result.cells[0]).toMatchObject({
code: 'print("still typing")',
language: "python",
languageOrigin: "fence",
reset: false,
});
});
it("ignores unknown attribute keys without erroring", () => {
const result = parseEvalInput(`\`\`\`py mystery=123 id=ok
print(1)
\`\`\`
`);
expect(result.cells[0]).toMatchObject({ title: "ok", language: "python" });
});
it("rejects an invalid rst value", () => {
expect(() =>
parseEvalInput(`\`\`\`py rst=maybe
print(1)
\`\`\`
`),
).toThrow("invalid rst value");
});
it("rejects an invalid t value", () => {
expect(() =>
parseEvalInput(`\`\`\`py t=forever
print(1)
\`\`\`
`),
).toThrow("invalid duration");
});
});