mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
- 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>
326 lines
9.7 KiB
TypeScript
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);
|
|
});
|
|
});
|