diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 429b79bcca5f5..39116054e875a 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -777,7 +777,9 @@ pub const DescribeScope = struct { tag: Tag = .pass, pub fn isAllSkipped(this: *const DescribeScope) bool { - return this.is_skip or @as(usize, this.skip_count) >= this.tests.items.len; + if (this.is_skip) return true; + const total = this.tests.items.len; + return total > 0 and @as(usize, this.skip_count) >= total; } pub fn push(new: *DescribeScope) void { @@ -1080,13 +1082,8 @@ pub const DescribeScope = struct { const end = @as(TestRunner.Test.ID, @truncate(tests.len)); this.pending_tests = std.DynamicBitSetUnmanaged.initFull(allocator, end) catch unreachable; - if (end == 0) { - // TODO: print the describe label when there are no tests - return; - } - // Step 2. Update the runner with the count of how many tests we have for this block - this.test_id_start = Jest.runner.?.addTestCount(end); + if (end > 0) this.test_id_start = Jest.runner.?.addTestCount(end); const source: logger.Source = Jest.runner.?.files.items(.source)[file]; @@ -1102,6 +1099,19 @@ pub const DescribeScope = struct { this.pending_tests.deinit(allocator); return; } + if (end == 0) { + var runner = allocator.create(TestRunnerTask) catch unreachable; + runner.* = .{ + .test_id = std.math.maxInt(TestRunner.Test.ID), + .describe = this, + .globalThis = globalObject, + .source_file_path = source.path.text, + }; + runner.ref.ref(globalObject.bunVM()); + + Jest.runner.?.enqueue(runner); + return; + } } while (i < end) : (i += 1) { @@ -1121,7 +1131,7 @@ pub const DescribeScope = struct { pub fn onTestComplete(this: *DescribeScope, globalThis: *JSC.JSGlobalObject, test_id: TestRunner.Test.ID, skipped: bool) void { // invalidate it this.current_test_id = std.math.maxInt(TestRunner.Test.ID); - this.pending_tests.unset(test_id); + if (test_id != std.math.maxInt(TestRunner.Test.ID)) this.pending_tests.unset(test_id); if (!skipped) { if (this.runCallback(globalThis, .afterEach)) |err| { @@ -1221,6 +1231,14 @@ pub const TestRunnerTask = struct { jsc_vm.last_reported_error_for_dedupe = .zero; const test_id = this.test_id; + + if (test_id == std.math.maxInt(TestRunner.Test.ID)) { + describe.onTestComplete(globalThis, test_id, true); + Jest.runner.?.runNextTest(); + this.deinit(); + return false; + } + var test_: TestScope = this.describe.tests.items[test_id]; describe.current_test_id = test_id; diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts index 2f824a9d05611..af2a143b52a3e 100644 --- a/test/js/bun/test/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -27,7 +27,7 @@ it("shouldn't crash when async test runner callback throws", async () => { }) `; - const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test"))); + const test_dir = await mkdtemp(join(tmp, "test")); try { await writeFile(join(test_dir, "bad.test.js"), code); const { stdout, stderr, exited } = spawn({ @@ -247,7 +247,7 @@ test("test async exceptions fail tests", () => { }); `; - const dir = join(tmpdir(), "test-throwing-bun"); + const dir = join(tmp, "test-throwing-bun"); const filepath = join(dir, "test-throwing-eventemitter.test.js"); rmSync(filepath, { force: true, @@ -259,7 +259,7 @@ test("test async exceptions fail tests", () => { writeFileSync(filepath, code); const { stderr, exitCode } = spawnSync([bunExe(), "test", "test-throwing-eventemitter"], { - cwd: realpathSync(dir), + cwd: dir, env: bunEnv, }); @@ -277,7 +277,7 @@ test("test async exceptions fail tests", () => { }); it("should return non-zero exit code for invalid syntax", async () => { - const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test"))); + const test_dir = await mkdtemp(join(tmp, "test")); try { await writeFile(join(test_dir, "bad.test.js"), "!!!"); const { stdout, stderr, exited } = spawn({ @@ -302,7 +302,7 @@ it("should return non-zero exit code for invalid syntax", async () => { }); it("invalid syntax counts towards bail", async () => { - const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test"))); + const test_dir = await mkdtemp(join(tmp, "test")); try { await writeFile(join(test_dir, "bad1.test.js"), "!!!"); await writeFile(join(test_dir, "bad2.test.js"), "!!!"); @@ -483,7 +483,7 @@ it("test.todo", () => { stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); const err = stderr!.toString(); expect(err).toContain("this test is marked as todo but passes"); @@ -504,7 +504,7 @@ it("test.todo doesnt cause exit code 1", () => { stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); const err = stderr!.toString(); @@ -519,7 +519,7 @@ it("test timeouts when expected", () => { stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); const err = stderr!.toString(); @@ -535,7 +535,7 @@ it("expect().toEqual() on objects with property indices doesn't print undefined" stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); let err = stderr!.toString(); @@ -555,7 +555,7 @@ it("test --preload supports global lifecycle hooks", () => { stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); expect(stdout.toString().trim()).toBe( ` @@ -595,10 +595,52 @@ it("skip() and skipIf()", () => { stdout: "pipe", stderr: "pipe", env: bunEnv, - cwd: realpathSync(dirname(path)), + cwd: dirname(path), }); const result = stdout!.toString(); expect(result).not.toContain("unreachable"); expect(result).toMatch(/reachable/); expect(result.match(/reachable/g)).toHaveLength(6); }); + +it("should run beforeAll() & afterAll() even without tests", async () => { + const test_dir = await mkdtemp(join(tmp, "test-hooks-empty")); + try { + await writeFile( + join(test_dir, "empty.test.js"), + ` +beforeAll(() => console.log("???BEFORE ALL???")); +afterAll(() => console.log("!!!AFTER ALL!!!")); + +describe("empty", () => { + beforeAll(() => console.log(">>>BEFORE ALL>>>")); + afterAll(() => console.log("<<>>BEFORE ALL>>>", + "<<