Files
bun.sh/test/cli/repl.test.ts
Claude Bot aa5ea829a2 feat(repl): add shell command support and fix async event loop handling
- Add $`command` syntax for running shell commands in REPL
- Transform $`...` to await Bun.$`...` for shell execution
- Fix event loop handling for async operations by calling autoTick()
- Now properly awaits Bun.sleep(), setTimeout(), and Bun.$ operations
- Add tests for shell commands and Bun.sleep

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

363 lines
9.4 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
describe("bun repl", () => {
test("evaluates simple expressions", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("1 + 2\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("supports Bun globals", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("typeof Bun\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("object");
expect(exitCode).toBe(0);
});
test("Bun.version is available", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("Bun.version\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should contain a version string
expect(stdout).toMatch(/\d+\.\d+\.\d+/);
expect(exitCode).toBe(0);
});
test("let declarations persist across lines", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("let x = 5\n");
proc.stdin.write("x * 2\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("const declarations persist across lines", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("const y = 7\n");
proc.stdin.write("y + 3\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("function declarations persist", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("function add(a, b) { return a + b }\n");
proc.stdin.write("add(2, 3)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("5");
expect(exitCode).toBe(0);
});
test(".help command works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write(".help\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("REPL Commands");
expect(stdout).toContain(".exit");
expect(stdout).toContain(".clear");
expect(exitCode).toBe(0);
});
test(".timing command toggles timing display", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write(".timing\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("Timing");
expect(exitCode).toBe(0);
});
test("error handling shows error message", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("throw new Error('test error')\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Error should be displayed in stderr
const output = stdout + stderr;
expect(output).toContain("test error");
expect(exitCode).toBe(0);
});
test("object literals are displayed", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("({ foo: 'bar' })\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("foo");
expect(stdout).toContain("bar");
expect(exitCode).toBe(0);
});
test("arrays are displayed", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("[1, 2, 3]\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("1");
expect(stdout).toContain("2");
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("undefined is not printed for statements", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("let z = 10\n");
proc.stdin.write("z\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The second line should show 10
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("multiline input with semicolons", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("var a = 1; var b = 2; a + b\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("async/await works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("await Promise.resolve(42)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("class declarations persist", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("class Calculator { add(a, b) { return a + b } }\n");
proc.stdin.write("new Calculator().add(3, 7)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("destructuring works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("const { a, b } = { a: 1, b: 2 }\n");
proc.stdin.write("a + b\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("shell command syntax works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("$`echo hello from shell`\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("hello from shell");
expect(exitCode).toBe(0);
});
test("Bun.$ template literal works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("await Bun.$`echo test output`\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("test output");
expect(exitCode).toBe(0);
});
test("Bun.sleep works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("await Bun.sleep(10)\n");
proc.stdin.write("'slept'\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("slept");
expect(exitCode).toBe(0);
});
});