mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
1027 lines
28 KiB
TypeScript
1027 lines
28 KiB
TypeScript
import { spawnSync } from "bun";
|
|
import { beforeAll, describe, expect, it, test } from "bun:test";
|
|
import { bunEnv, bunExe, tempDirWithFiles, tmpdirSync } from "harness";
|
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { dirname, join, resolve } from "node:path";
|
|
|
|
describe("bun test", () => {
|
|
test("running a non-existent absolute file path is a 1 exit code", () => {
|
|
const spawn = Bun.spawnSync({
|
|
cmd: [bunExe(), "test", join(import.meta.dirname, "non-existent.test.ts")],
|
|
env: bunEnv,
|
|
stdin: "ignore",
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
});
|
|
expect(spawn.exitCode).toBe(1);
|
|
});
|
|
test("can provide no arguments", () => {
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: [
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("test #1", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test.todo("test #2");
|
|
`,
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("test #3", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
`,
|
|
],
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
expect(stderr).toContain("test #2");
|
|
expect(stderr).toContain("test #3");
|
|
});
|
|
test("can provide a relative file", () => {
|
|
const path = join("path", "to", "relative.test.ts");
|
|
const cwd = createTest(
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("${path}", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
path,
|
|
);
|
|
const stderr = runTest({
|
|
cwd,
|
|
args: [path],
|
|
});
|
|
expect(stderr).toContain(path);
|
|
});
|
|
// This fails on macOS because /private/var symlinks to /var
|
|
test.todo("can provide an absolute file", () => {
|
|
const path = join("path", "to", "absolute.test.ts");
|
|
const cwd = createTest(
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("${path}", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
path,
|
|
);
|
|
const absolutePath = resolve(cwd, path);
|
|
const stderr = runTest({
|
|
cwd,
|
|
args: [absolutePath],
|
|
});
|
|
expect(stderr).toContain(path);
|
|
});
|
|
test("can provide a relative directory", () => {
|
|
const path = join("path", "to", "relative.test.ts");
|
|
const dir = dirname(path);
|
|
const cwd = createTest(
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("${dir}", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
path,
|
|
);
|
|
const stderr = runTest({
|
|
cwd,
|
|
args: [dir],
|
|
});
|
|
expect(stderr).toContain(dir);
|
|
});
|
|
test.todo("can provide an absolute directory", () => {
|
|
const path = join("path", "to", "absolute.test.ts");
|
|
const cwd = createTest(
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("${path}", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
path,
|
|
);
|
|
const absoluteDir = resolve(cwd, dirname(path));
|
|
const stderr = runTest({
|
|
cwd,
|
|
args: [absoluteDir],
|
|
});
|
|
expect(stderr).toContain(path);
|
|
});
|
|
|
|
describe("when filters are provided", () => {
|
|
let dir: string;
|
|
beforeAll(() => {
|
|
const makeTest = (name: string, pass = true) => `
|
|
import { test, expect } from "bun:test";
|
|
test("${name}", () => {
|
|
expect(1).toBe(${pass ? 1 : 0});
|
|
});
|
|
`;
|
|
dir = tempDirWithFiles("bun-test-filtering", {
|
|
"foo.test.js": makeTest("foo"),
|
|
bar: {
|
|
"bar1.spec.tsx": makeTest("bar1"),
|
|
"bar2.spec.ts": makeTest("bar2"),
|
|
},
|
|
});
|
|
});
|
|
|
|
it("if that filter is a path to a directory, will run all tests in that directory", () => {
|
|
const stderr = runTest({ cwd: dir, args: ["./bar"] });
|
|
expect(stderr).toContain("2 pass");
|
|
expect(stderr).not.toContain("foo");
|
|
});
|
|
});
|
|
|
|
test("works with require", () => {
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: [
|
|
`
|
|
const { test, expect } = require("bun:test");
|
|
test("test #1", () => {
|
|
expect().pass();
|
|
})
|
|
`,
|
|
],
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
});
|
|
test("works with dynamic import", () => {
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
const { test, expect } = await import("bun:test");
|
|
test("test #1", () => {
|
|
expect().pass();
|
|
})
|
|
`,
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
});
|
|
test("works with cjs require", () => {
|
|
const cwd = createTest(
|
|
`
|
|
const { test, expect } = require("bun:test");
|
|
test("test #1", () => {
|
|
expect().pass();
|
|
})
|
|
`,
|
|
"test.test.cjs",
|
|
);
|
|
const stderr = runTest({
|
|
cwd,
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
});
|
|
test("works with cjs dynamic import", () => {
|
|
const cwd = createTest(
|
|
`
|
|
const { test, expect } = await import("bun:test");
|
|
test("test #1", () => {
|
|
expect().pass();
|
|
})
|
|
`,
|
|
"test.test.cjs",
|
|
);
|
|
const stderr = runTest({
|
|
cwd,
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
});
|
|
test.todo("can provide a mix of files and directories");
|
|
describe("--rerun-each", () => {
|
|
test.todo("can rerun with a default value");
|
|
test.todo("can rerun with a provided value");
|
|
});
|
|
describe("--todo", () => {
|
|
test("should not run todo by default", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test.todo("todo", async () => {
|
|
console.error("should not run");
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).not.toContain("should not run");
|
|
});
|
|
test("should run todo when enabled", () => {
|
|
const stderr = runTest({
|
|
args: ["--todo"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test.todo("todo", async () => {
|
|
console.error("should run");
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain("should run");
|
|
});
|
|
});
|
|
describe("--only", () => {
|
|
test("should run nested describe.only when enabled", () => {
|
|
const stderr = runTest({
|
|
args: ["--only"],
|
|
input: `
|
|
import { test, describe } from "bun:test";
|
|
describe("outer", () => {
|
|
describe.only("inner (nested)", () => {
|
|
test("test", () => {
|
|
console.error("reachable");
|
|
})
|
|
})
|
|
describe("inner (skipped)", () => {
|
|
test("test", () => {
|
|
console.error("unreachable");
|
|
})
|
|
})
|
|
})
|
|
`,
|
|
});
|
|
expect(stderr).toContain("reachable");
|
|
expect(stderr).not.toContain("unreachable");
|
|
expect(stderr.match(/reachable/g)).toHaveLength(1);
|
|
});
|
|
test("should skip non-only tests when enabled", () => {
|
|
const stderr = runTest({
|
|
args: ["--only"],
|
|
input: `
|
|
import { test, describe } from "bun:test";
|
|
test("test #1", () => {
|
|
console.error("unreachable");
|
|
});
|
|
test.only("test #2", () => {
|
|
console.error("reachable");
|
|
});
|
|
test("test #3", () => {
|
|
console.error("unreachable");
|
|
});
|
|
test.skip("test #4", () => {
|
|
console.error("unreachable");
|
|
});
|
|
test.todo("test #5");
|
|
describe("describe #1", () => {
|
|
test("test #6", () => {
|
|
console.error("unreachable");
|
|
});
|
|
test.only("test #7", () => {
|
|
console.error("reachable");
|
|
});
|
|
});
|
|
describe.only("describe #2", () => {
|
|
test("test #8", () => {
|
|
console.error("reachable");
|
|
});
|
|
test.skip("test #9", () => {
|
|
console.error("unreachable");
|
|
});
|
|
test.only("test #10", () => {
|
|
console.error("reachable");
|
|
});
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain("reachable");
|
|
expect(stderr).not.toContain("unreachable");
|
|
expect(stderr.match(/reachable/g)).toHaveLength(4);
|
|
});
|
|
});
|
|
describe("--bail", () => {
|
|
test("must provide a number bail", () => {
|
|
const stderr = runTest({
|
|
args: ["--bail=foo"],
|
|
});
|
|
expect(stderr).toContain("expects a number");
|
|
});
|
|
|
|
test("must provide non-negative bail", () => {
|
|
const stderr = runTest({
|
|
args: ["--bail=-1"],
|
|
});
|
|
expect(stderr).toContain("expects a number");
|
|
});
|
|
|
|
test("should not be 0", () => {
|
|
const stderr = runTest({
|
|
args: ["--bail=0"],
|
|
});
|
|
expect(stderr).toContain("expects a number");
|
|
});
|
|
|
|
test("bail should be 1 by default", () => {
|
|
const stderr = runTest({
|
|
args: ["--bail"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("test #1", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
test("test #2", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain("Bailed out after 1 failure");
|
|
expect(stderr).not.toContain("test #2");
|
|
});
|
|
|
|
test("should bail out after 3 failures", () => {
|
|
const stderr = runTest({
|
|
args: ["--bail=3"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("test #1", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
test("test #2", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
test("test #3", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
test("test #4", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain("Bailed out after 3 failures");
|
|
expect(stderr).not.toContain("test #4");
|
|
});
|
|
});
|
|
describe("--timeout", () => {
|
|
test("must provide a number timeout", () => {
|
|
const stderr = runTest({
|
|
args: ["--timeout", "foo"],
|
|
});
|
|
expect(stderr).toContain("Invalid timeout");
|
|
});
|
|
test("must provide non-negative timeout", () => {
|
|
const stderr = runTest({
|
|
args: ["--timeout", "-1"],
|
|
});
|
|
expect(stderr).toContain("Invalid timeout");
|
|
});
|
|
// TODO: https://github.com/oven-sh/bun/issues/8069
|
|
// This test crashes, which will pass because stderr contains "timed out"
|
|
// but the crash can also mean it hangs, which will end up failing.
|
|
// Possibly fixed by https://github.com/oven-sh/bun/pull/8076/files
|
|
test("timeout can be set to 30ms", () => {
|
|
const stderr = runTest({
|
|
args: ["--timeout", "30"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
import { sleep } from "bun";
|
|
test("ok", async () => {
|
|
await expect(sleep(1)).resolves.toBeUndefined();
|
|
});
|
|
test("timeout", async () => {
|
|
await expect(sleep(64)).resolves.toBeUndefined();
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toHaveTestTimedOutAfter(30);
|
|
});
|
|
test("timeout should default to 5000ms", () => {
|
|
const time = process.platform === "linux" ? 5005 : 5500;
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
import { sleep } from "bun";
|
|
test("timeout", async () => {
|
|
await sleep(${time});
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toHaveTestTimedOutAfter(5000);
|
|
}, 10000);
|
|
});
|
|
describe("support for Github Actions", () => {
|
|
test("should not group logs by default", () => {
|
|
const stderr = runTest({
|
|
env: {
|
|
GITHUB_ACTIONS: undefined,
|
|
},
|
|
});
|
|
expect(stderr).not.toContain("::group::");
|
|
expect(stderr).not.toContain("::endgroup::");
|
|
});
|
|
test("should not group logs when disabled", () => {
|
|
const stderr = runTest({
|
|
env: {
|
|
GITHUB_ACTIONS: "false",
|
|
},
|
|
});
|
|
expect(stderr).not.toContain("::group::");
|
|
expect(stderr).not.toContain("::endgroup::");
|
|
});
|
|
test("should group logs when enabled", () => {
|
|
const stderr = runTest({
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toContain("::group::");
|
|
expect(stderr.match(/::group::/g)).toHaveLength(1);
|
|
expect(stderr).toContain("::endgroup::");
|
|
expect(stderr.match(/::endgroup::/g)).toHaveLength(1);
|
|
});
|
|
test("should group logs with multiple files", () => {
|
|
const stderr = runTest({
|
|
input: [
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("pass", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test.skip("skip", () => {});
|
|
`,
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("fail", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
`,
|
|
],
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toContain("::group::");
|
|
expect(stderr.match(/::group::/g)).toHaveLength(3);
|
|
expect(stderr).toContain("::endgroup::");
|
|
expect(stderr.match(/::endgroup::/g)).toHaveLength(3);
|
|
});
|
|
test("should group logs with --rerun-each", () => {
|
|
const stderr = runTest({
|
|
args: ["--rerun-each", "3"],
|
|
input: [
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("pass", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
`
|
|
import { test, expect } from "bun:test";
|
|
test("fail", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
`,
|
|
],
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toContain("::group::");
|
|
expect(stderr.match(/::group::/g)).toHaveLength(6);
|
|
expect(stderr).toContain("::endgroup::");
|
|
expect(stderr.match(/::endgroup::/g)).toHaveLength(6);
|
|
});
|
|
test("should not annotate errors by default", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("fail", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: undefined,
|
|
},
|
|
});
|
|
expect(stderr).not.toContain("::error");
|
|
});
|
|
test("should not annotate errors with inspect() by default", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test } from "bun:test";
|
|
import { inspect } from "bun";
|
|
test("inspect", () => {
|
|
inspect(new TypeError());
|
|
console.error(inspect(new TypeError()));
|
|
});
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: undefined,
|
|
},
|
|
});
|
|
expect(stderr).not.toContain("::error");
|
|
});
|
|
test("should not annotate errors with inspect() when enabled", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test } from "bun:test";
|
|
import { inspect } from "bun";
|
|
test("inspect", () => {
|
|
inspect(new TypeError());
|
|
console.error(inspect(new TypeError()));
|
|
});
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).not.toContain("::error");
|
|
});
|
|
test("should annotate errors in the global scope", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
throw new Error();
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=error::/);
|
|
});
|
|
test.each(["test", "describe"])("should annotate errors in a %s scope", type => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { ${type} } from "bun:test";
|
|
${type}("fail", () => {
|
|
throw new Error();
|
|
});
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=error::/);
|
|
});
|
|
test.each(["beforeAll", "beforeEach", "afterEach", "afterAll"])("should annotate errors in a %s callback", type => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, ${type} } from "bun:test";
|
|
${type}(() => {
|
|
throw new Error();
|
|
});
|
|
test("test", () => {});
|
|
`,
|
|
env: {
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=error::/);
|
|
});
|
|
test("should annotate errors with escaped strings", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("fail", () => {
|
|
expect(true).toBe(false);
|
|
});
|
|
`,
|
|
env: {
|
|
FORCE_COLOR: "1",
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=.*::/);
|
|
expect(stderr).toMatch(/error: expect\(received\)\.toBe\(expected\)/); // stripped ansi
|
|
expect(stderr).toMatch(/Expected: false%0AReceived: true%0A/); // escaped newlines
|
|
});
|
|
test("should annotate errors without a stack", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("fail", () => {
|
|
throw "Oops!";
|
|
});
|
|
`,
|
|
env: {
|
|
FORCE_COLOR: "1",
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=error: Oops!::/m);
|
|
});
|
|
test("should annotate a test timeout", () => {
|
|
const stderr = runTest({
|
|
input: `
|
|
import { test } from "bun:test";
|
|
test("time out", async () => {
|
|
await Bun.sleep(1000);
|
|
}, { timeout: 1 });
|
|
`,
|
|
env: {
|
|
FORCE_COLOR: "1",
|
|
GITHUB_ACTIONS: "true",
|
|
},
|
|
});
|
|
expect(stderr).toMatch(/::error title=error: Test \"time out\" timed out after \d+ms::/);
|
|
});
|
|
});
|
|
describe(".each", () => {
|
|
test("should run tests with test.each", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("should allow tests run with test.each to be skipped", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: ["-t", "$a"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).not.toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("should allow tests run with test.each to be matched", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: ["-t", "1 \\+"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
if (numbers[0] === 1) {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
} else {
|
|
expect(stderr).not.toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
}
|
|
});
|
|
});
|
|
test("should run tests with describe.each", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect, describe } from "bun:test";
|
|
|
|
describe.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {\
|
|
test("addition", () => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("check formatting for %i", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%i + %i = %i", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("check formatting for %f", () => {
|
|
const numbers = [
|
|
[1.4, 2.9, 4.3],
|
|
[1, 1, 2],
|
|
[3.1, 4.5, 7.6],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%f + %f = %d", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("check formatting for %d", () => {
|
|
const numbers = [
|
|
[1.4, 2.9, 4.3],
|
|
[1, 1, 2],
|
|
[3.1, 4.5, 7.6],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("%f + %f = %d", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach(numbers => {
|
|
expect(stderr).toContain(`${numbers[0]} + ${numbers[1]} = ${numbers[2]}`);
|
|
});
|
|
});
|
|
test("check formatting for %s", () => {
|
|
const strings = ["hello", "world", "foo"];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(strings)})("with a string: %s", (s) => {
|
|
expect(s).toBeType("string");
|
|
});
|
|
`,
|
|
});
|
|
strings.forEach(s => {
|
|
expect(stderr).toContain(`with a string: ${s}`);
|
|
});
|
|
});
|
|
test("check formatting for %j", () => {
|
|
const input = [
|
|
{
|
|
foo: "bar",
|
|
nested: {
|
|
again: {
|
|
a: 2,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(input)})("with an object: %o", (o) => {
|
|
expect(o).toBe(o);
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain(`with an object: ${JSON.stringify(input[0])}`);
|
|
});
|
|
test("check formatting for %o", () => {
|
|
const input = [
|
|
{
|
|
foo: "bar",
|
|
nested: {
|
|
again: {
|
|
a: 2,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(input)})("with an object: %o", (o) => {
|
|
expect(o).toBe(o);
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain(`with an object: ${JSON.stringify(input[0])}`);
|
|
});
|
|
test("check formatting for %#", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("test number %#: %i + %i = %i", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
numbers.forEach((_, idx) => {
|
|
expect(stderr).toContain(`test number ${idx}:`);
|
|
});
|
|
});
|
|
test("check formatting for %%", () => {
|
|
const numbers = [
|
|
[1, 2, 3],
|
|
[1, 1, 2],
|
|
[3, 4, 7],
|
|
];
|
|
|
|
const stderr = runTest({
|
|
args: [],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
|
|
test.each(${JSON.stringify(numbers)})("test number %#: %i + %i = %i %%", (a, b, e) => {
|
|
expect(a + b).toBe(e);
|
|
});
|
|
`,
|
|
});
|
|
expect(stderr).toContain(`%`);
|
|
});
|
|
test.todo("check formatting for %p", () => {});
|
|
});
|
|
|
|
test("Prints error when no test matches", () => {
|
|
const stderr = runTest({
|
|
args: ["-t", "not-a-test"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("test", () => {});
|
|
`,
|
|
expectExitCode: 1,
|
|
});
|
|
expect(
|
|
stderr
|
|
.replace(/bun-test-(.*)\.test\.ts/, "bun-test-*.test.ts")
|
|
.trim()
|
|
.replace(/\[.*\ms\]/, "[xx ms]"),
|
|
).toMatchInlineSnapshot(`
|
|
"bun-test-*.test.ts:
|
|
|
|
error: regex "not-a-test" matched 0 tests. Searched 1 file (skipping 1 test) [xx ms]"
|
|
`);
|
|
});
|
|
|
|
test("Does not print the regex error when a test fails", () => {
|
|
const stderr = runTest({
|
|
args: ["-t", "not-a-test"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("not-a-test", () => {
|
|
expect(false).toBe(true);
|
|
});
|
|
`,
|
|
expectExitCode: 1,
|
|
});
|
|
expect(stderr).not.toContain("error: regex");
|
|
expect(stderr).toContain("1 fail");
|
|
});
|
|
|
|
test("Does not print the regex error when a test matches and a test passes", () => {
|
|
const stderr = runTest({
|
|
args: ["-t", "not-a-test"],
|
|
input: `
|
|
import { test, expect } from "bun:test";
|
|
test("not-a-test", () => {
|
|
expect(false).toBe(true);
|
|
});
|
|
test("not-a-test", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
expectExitCode: 1,
|
|
});
|
|
expect(stderr).not.toContain("error: regex");
|
|
expect(stderr).toContain("1 fail");
|
|
expect(stderr).toContain("1 pass");
|
|
});
|
|
|
|
test("path to a non-test.ts file will work", () => {
|
|
const stderr = runTest({
|
|
args: ["./index.ts"],
|
|
input: [
|
|
{
|
|
filename: "index.ts",
|
|
contents: `
|
|
import { test, expect } from "bun:test";
|
|
test("test #1", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
},
|
|
],
|
|
});
|
|
expect(stderr).toContain("test #1");
|
|
});
|
|
|
|
test("path to a non-test.ts without ./ will print a helpful hint", () => {
|
|
const stderr = runTest({
|
|
args: ["index.ts"],
|
|
input: [
|
|
{
|
|
filename: "index.ts",
|
|
contents: `
|
|
import { test, expect } from "bun:test";
|
|
test("test #1", () => {
|
|
expect(true).toBe(true);
|
|
});
|
|
`,
|
|
},
|
|
],
|
|
});
|
|
expect(stderr).not.toContain("test #1");
|
|
expect(stderr).toContain("index.ts");
|
|
});
|
|
});
|
|
|
|
function createTest(input?: string | (string | { filename: string; contents: string })[], filename?: string): string {
|
|
const cwd = tmpdirSync();
|
|
const inputs = Array.isArray(input) ? input : [input ?? ""];
|
|
for (const input of inputs) {
|
|
const contents = typeof input === "string" ? input : input.contents;
|
|
const name = typeof input === "string" ? (filename ?? `bun-test-${Math.random()}.test.ts`) : input.filename;
|
|
|
|
const path = join(cwd, name);
|
|
try {
|
|
writeFileSync(path, contents);
|
|
} catch {
|
|
mkdirSync(dirname(path), { recursive: true });
|
|
writeFileSync(path, contents);
|
|
}
|
|
}
|
|
return cwd;
|
|
}
|
|
|
|
function runTest({
|
|
input = "",
|
|
cwd,
|
|
args = [],
|
|
env = {},
|
|
expectExitCode = undefined,
|
|
}: {
|
|
input?: string | (string | { filename: string; contents: string })[];
|
|
cwd?: string;
|
|
args?: string[];
|
|
env?: Record<string, string | undefined>;
|
|
expectExitCode?: number;
|
|
} = {}): string {
|
|
cwd ??= createTest(input);
|
|
try {
|
|
const { stderr, exitCode } = spawnSync({
|
|
cwd,
|
|
cmd: [bunExe(), "test", ...args],
|
|
env: { ...bunEnv, ...env },
|
|
stderr: "pipe",
|
|
stdout: "ignore",
|
|
});
|
|
if (expectExitCode !== undefined) {
|
|
expect(exitCode).toBe(expectExitCode);
|
|
}
|
|
return stderr.toString();
|
|
} finally {
|
|
rmSync(cwd, { recursive: true });
|
|
}
|
|
}
|