Files
bun.sh/test/cli/repl/repl.test.ts
Claude Bot be79d67802 address code review feedback for REPL
- Delete .bun_repl_history and add to .gitignore
- Fix memory leak in getCompletions for property names
- Use vm.waitForPromise instead of manual event loop polling
- Add proper cleanup for stores and arena on init failure
- Remove manual timeout from test helper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 21:11:55 +00:00

326 lines
9.7 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows } from "harness";
// Helper to run REPL with piped input and capture output
async function runRepl(input: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
await using proc = Bun.spawn([bunExe(), "repl"], {
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
// Write input to stdin
proc.stdin.write(input);
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { stdout, stderr, exitCode };
}
describe.todoIf(isWindows)("bun repl", () => {
test("exits cleanly with .exit", async () => {
const { exitCode } = await runRepl(".exit\n");
expect(exitCode).toBe(0);
});
test(".help command shows help text", async () => {
const { stdout, exitCode } = await runRepl(".help\n.exit\n");
expect(stdout).toContain("REPL");
expect(stdout).toContain(".exit");
expect(exitCode).toBe(0);
});
test(".q is an alias for .exit", async () => {
const { exitCode } = await runRepl(".q\n");
expect(exitCode).toBe(0);
});
test("EOF (empty input) exits the REPL", async () => {
// Empty input (immediate EOF) should exit gracefully
const { exitCode } = await runRepl("");
expect(exitCode).toBe(0);
});
test("evaluates simple expression", async () => {
const { stdout, exitCode } = await runRepl("1 + 1\n.exit\n");
expect(stdout).toContain("2");
expect(exitCode).toBe(0);
});
test("evaluates string expression", async () => {
const { stdout, exitCode } = await runRepl('"hello"\n.exit\n');
expect(stdout).toContain("hello");
expect(exitCode).toBe(0);
});
test("evaluates object literal", async () => {
const { stdout, exitCode } = await runRepl("({a: 1, b: 2})\n.exit\n");
// Should show object representation
expect(stdout).toMatch(/a.*:.*1/);
expect(stdout).toMatch(/b.*:.*2/);
expect(exitCode).toBe(0);
});
test("evaluates array literal", async () => {
const { stdout, exitCode } = await runRepl("[1, 2, 3]\n.exit\n");
expect(stdout).toContain("1");
expect(stdout).toContain("2");
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("handles console.log", async () => {
const { stdout, exitCode } = await runRepl('console.log("hello world")\n.exit\n');
expect(stdout).toContain("hello world");
expect(exitCode).toBe(0);
});
test("persists variables across lines", async () => {
const { stdout, exitCode } = await runRepl("const x = 42\nx * 2\n.exit\n");
expect(stdout).toContain("84");
expect(exitCode).toBe(0);
});
test("handles function definition and call", async () => {
const { stdout, exitCode } = await runRepl("function add(a, b) { return a + b; }\nadd(2, 3)\n.exit\n");
expect(stdout).toContain("5");
expect(exitCode).toBe(0);
});
test("handles syntax errors gracefully", async () => {
const { stdout, stderr, exitCode } = await runRepl("const x =\n.exit\n");
// Should show error but continue
const output = stdout + stderr;
expect(output.toLowerCase()).toMatch(/error|parse|syntax/i);
expect(exitCode).toBe(0);
});
test("handles runtime errors gracefully", async () => {
const { stdout, stderr, exitCode } = await runRepl("throw new Error('test error')\n.exit\n");
// Should show error but continue
const output = stdout + stderr;
expect(output).toContain("Error");
expect(output).toContain("test error");
expect(exitCode).toBe(0);
});
test("handles undefined variable error", async () => {
const { stdout, stderr, exitCode } = await runRepl("undefinedVariable\n.exit\n");
// Should show reference error
const output = stdout + stderr;
expect(output.toLowerCase()).toMatch(/error|not defined|undefined/i);
expect(exitCode).toBe(0);
});
test("handles async/await", async () => {
const { stdout, exitCode } = await runRepl("await Promise.resolve(42)\n.exit\n");
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("Bun object is available", async () => {
const { stdout, exitCode } = await runRepl("typeof Bun\n.exit\n");
expect(stdout).toContain("object");
expect(exitCode).toBe(0);
});
test("Bun.version is available", async () => {
const { stdout, exitCode } = await runRepl("Bun.version\n.exit\n");
// Should contain version number pattern
expect(stdout).toMatch(/\d+\.\d+\.\d+/);
expect(exitCode).toBe(0);
});
test("process object is available", async () => {
const { stdout, exitCode } = await runRepl("typeof process\n.exit\n");
expect(stdout).toContain("object");
expect(exitCode).toBe(0);
});
test.todo("handles TypeScript syntax", async () => {
// TypeScript type annotation should be stripped
// Currently not supported in REPL
const { stdout, exitCode } = await runRepl("const x: number = 42; x\n.exit\n");
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("handles arrow functions", async () => {
const { stdout, exitCode } = await runRepl("const double = (x) => x * 2; double(21)\n.exit\n");
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("handles template literals", async () => {
const { stdout, exitCode } = await runRepl("const name = 'world'; `hello ${name}`\n.exit\n");
expect(stdout).toContain("hello world");
expect(exitCode).toBe(0);
});
test("handles destructuring", async () => {
const { stdout, exitCode } = await runRepl("const {a, b} = {a: 1, b: 2}; a + b\n.exit\n");
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("handles spread operator", async () => {
const { stdout, exitCode } = await runRepl("const arr = [1, 2, 3]; [...arr, 4, 5]\n.exit\n");
expect(stdout).toContain("4");
expect(stdout).toContain("5");
expect(exitCode).toBe(0);
});
test("handles class definition", async () => {
const { stdout, exitCode } = await runRepl("class Foo { constructor(x) { this.x = x; } }; new Foo(42).x\n.exit\n");
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("handles Map and Set", async () => {
const { stdout, exitCode } = await runRepl("const m = new Map(); m.set('a', 1); m.get('a')\n.exit\n");
expect(stdout).toContain("1");
expect(exitCode).toBe(0);
});
test("handles BigInt", async () => {
const { stdout, exitCode } = await runRepl("1n + 2n\n.exit\n");
expect(stdout).toContain("3n");
expect(exitCode).toBe(0);
});
test("handles Symbol", async () => {
const { stdout, exitCode } = await runRepl('const s = Symbol("test"); typeof s\n.exit\n');
expect(stdout).toContain("symbol");
expect(exitCode).toBe(0);
});
test("handles JSON operations", async () => {
const { stdout, exitCode } = await runRepl("JSON.parse('{\"a\":1}')\n.exit\n");
expect(stdout).toMatch(/a.*:.*1/);
expect(exitCode).toBe(0);
});
test("handles fetch API availability", async () => {
const { stdout, exitCode } = await runRepl("typeof fetch\n.exit\n");
expect(stdout).toContain("function");
expect(exitCode).toBe(0);
});
test("handles URL API", async () => {
const { stdout, exitCode } = await runRepl('new URL("https://bun.sh").hostname\n.exit\n');
expect(stdout).toContain("bun.sh");
expect(exitCode).toBe(0);
});
test("handles TextEncoder/TextDecoder", async () => {
const { stdout, exitCode } = await runRepl('new TextDecoder().decode(new TextEncoder().encode("hi"))\n.exit\n');
expect(stdout).toContain("hi");
expect(exitCode).toBe(0);
});
test("handles globalThis", async () => {
const { stdout, exitCode } = await runRepl("typeof globalThis\n.exit\n");
expect(stdout).toContain("object");
expect(exitCode).toBe(0);
});
test("null result shows null", async () => {
const { stdout, exitCode } = await runRepl("null\n.exit\n");
expect(stdout).toContain("null");
expect(exitCode).toBe(0);
});
test("boolean results", async () => {
const { stdout, exitCode } = await runRepl("true\nfalse\n.exit\n");
expect(stdout).toContain("true");
expect(stdout).toContain("false");
expect(exitCode).toBe(0);
});
test("handles Infinity and NaN", async () => {
const { stdout, exitCode } = await runRepl("Infinity\nNaN\n.exit\n");
expect(stdout).toContain("Infinity");
expect(stdout).toContain("NaN");
expect(exitCode).toBe(0);
});
test("handles regex", async () => {
const { stdout, exitCode } = await runRepl('/test/.test("testing")\n.exit\n');
expect(stdout).toContain("true");
expect(exitCode).toBe(0);
});
test("handles Date", async () => {
const { stdout, exitCode } = await runRepl("new Date(0).getFullYear()\n.exit\n");
expect(stdout).toContain("1970");
expect(exitCode).toBe(0);
});
test("handles Math functions", async () => {
const { stdout, exitCode } = await runRepl("Math.max(1, 2, 3)\n.exit\n");
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("handles Object methods", async () => {
const { stdout, exitCode } = await runRepl("Object.keys({a: 1, b: 2})\n.exit\n");
expect(stdout).toContain("a");
expect(stdout).toContain("b");
expect(exitCode).toBe(0);
});
test("handles Array methods", async () => {
const { stdout, exitCode } = await runRepl("[1, 2, 3].map(x => x * 2)\n.exit\n");
expect(stdout).toContain("2");
expect(stdout).toContain("4");
expect(stdout).toContain("6");
expect(exitCode).toBe(0);
});
test("handles String methods", async () => {
const { stdout, exitCode } = await runRepl('"hello".toUpperCase()\n.exit\n');
expect(stdout).toContain("HELLO");
expect(exitCode).toBe(0);
});
});