const std = @import("std"); const Api = @import("../../../api/schema.zig").Api; const RequestContext = @import("../../../http.zig").RequestContext; const MimeType = @import("../../../http.zig").MimeType; const ZigURL = @import("../../../url.zig").URL; const HTTPClient = @import("http"); const NetworkThread = HTTPClient.NetworkThread; const JSC = @import("../../../jsc.zig"); const js = JSC.C; const logger = @import("../../../logger.zig"); const Method = @import("../../../http/method.zig").Method; const ObjectPool = @import("../../../pool.zig").ObjectPool; const Output = @import("../../../global.zig").Output; const MutableString = @import("../../../global.zig").MutableString; const strings = @import("../../../global.zig").strings; const string = @import("../../../global.zig").string; const default_allocator = @import("../../../global.zig").default_allocator; const FeatureFlags = @import("../../../global.zig").FeatureFlags; const ArrayBuffer = @import("../base.zig").ArrayBuffer; const Properties = @import("../base.zig").Properties; const NewClass = @import("../base.zig").NewClass; const d = @import("../base.zig").d; const castObj = @import("../base.zig").castObj; const getAllocator = @import("../base.zig").getAllocator; const JSPrivateDataPtr = @import("../base.zig").JSPrivateDataPtr; const GetJSPrivateData = @import("../base.zig").GetJSPrivateData; const ZigString = JSC.ZigString; const JSInternalPromise = JSC.JSInternalPromise; const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const VirtualMachine = @import("../javascript.zig").VirtualMachine; const Task = @import("../javascript.zig").Task; const Fs = @import("../../../fs.zig"); const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false; fn notImplementedFn(_: *anyopaque, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, _: []const js.JSValueRef, exception: js.ExceptionRef) js.JSValueRef { JSError(getAllocator(ctx), "Not implemented yet!", .{}, ctx, exception); return null; } fn notImplementedProp( _: anytype, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSStringRef, exception: js.ExceptionRef, ) js.JSValueRef { JSError(getAllocator(ctx), "Property not implemented yet!", .{}, ctx, exception); return null; } const ArrayIdentityContext = @import("../../../identity_context.zig").ArrayIdentityContext; pub const TestRunner = struct { tests: TestRunner.Test.List = .{}, log: *logger.Log, files: File.List = .{}, index: File.Map = File.Map{}, only: bool = false, timeout_seconds: f64 = 5.0, allocator: std.mem.Allocator, callback: *Callback = undefined, pub fn setOnly(this: *TestRunner) void { if (this.only) { return; } this.only = true; this.tests.shrinkRetainingCapacity(0); this.callback.onUpdateCount(this.callback, 0, 0); } pub const Callback = struct { pub const OnUpdateCount = fn (this: *Callback, delta: u32, total: u32) void; pub const OnTestStart = fn (this: *Callback, test_id: Test.ID) void; pub const OnTestPass = fn (this: *Callback, test_id: Test.ID, expectations: u32) void; pub const OnTestFail = fn (this: *Callback, test_id: Test.ID, file: string, label: string, expectations: u32) void; onUpdateCount: OnUpdateCount, onTestStart: OnTestStart, onTestPass: OnTestPass, onTestFail: OnTestFail, }; pub fn reportPass(this: *TestRunner, test_id: Test.ID, expectations: u32) void { this.tests.items(.status)[test_id] = .pass; this.callback.onTestPass(this.callback, test_id, expectations); } pub fn reportFailure(this: *TestRunner, test_id: Test.ID, file: string, label: string, expectations: u32) void { this.tests.items(.status)[test_id] = .fail; this.callback.onTestFail(this.callback, test_id, file, label, expectations); } pub fn addTestCount(this: *TestRunner, count: u32) u32 { this.tests.ensureUnusedCapacity(this.allocator, count) catch unreachable; const start = @truncate(Test.ID, this.tests.len); this.tests.len += count; var statuses = this.tests.items(.status)[start..][0..count]; std.mem.set(Test.Status, statuses, Test.Status.pending); this.callback.onUpdateCount(this.callback, count, count + start); return start; } pub fn getOrPutFile(this: *TestRunner, file_path: string) *DescribeScope { var entry = this.index.getOrPut(this.allocator, @truncate(u32, std.hash.Wyhash.hash(0, file_path))) catch unreachable; if (entry.found_existing) { return this.files.items(.module_scope)[entry.value_ptr.*]; } var scope = this.allocator.create(DescribeScope) catch unreachable; const file_id = @truncate(File.ID, this.files.len); scope.* = DescribeScope{ .file_id = file_id, .test_id_start = @truncate(Test.ID, this.tests.len), }; this.files.append(this.allocator, .{ .module_scope = scope, .source = logger.Source.initEmptyFile(file_path) }) catch unreachable; entry.value_ptr.* = file_id; return scope; } pub const File = struct { source: logger.Source = logger.Source.initEmptyFile(""), log: logger.Log = logger.Log.initComptime(default_allocator), module_scope: *DescribeScope = undefined, pub const List = std.MultiArrayList(File); pub const ID = u32; pub const Map = std.ArrayHashMapUnmanaged(u32, u32, ArrayIdentityContext, false); }; pub const Test = struct { status: Status = Status.pending, pub const ID = u32; pub const List = std.MultiArrayList(Test); pub const Status = enum(u3) { pending, pass, fail, }; }; }; pub const Jest = struct { pub var runner: ?*TestRunner = null; pub fn call( _: void, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSValueRef { var runner_ = runner orelse { JSError(getAllocator(ctx), "Run bun test to run a test", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); }; if (arguments.len < 1 or !js.JSValueIsString(ctx, arguments[0])) { JSError(getAllocator(ctx), "Bun.jest() expects a string filename", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); } var str = js.JSValueToStringCopy(ctx, arguments[0], exception); defer js.JSStringRelease(str); var ptr = js.JSStringGetCharacters8Ptr(str); const len = js.JSStringGetLength(str); if (len == 0 or ptr[0] != '/') { JSError(getAllocator(ctx), "Bun.jest() expects an absolute file path", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); } var str_value = ptr[0..len]; var filepath = Fs.FileSystem.instance.filename_store.append([]const u8, str_value) catch unreachable; var scope = runner_.getOrPutFile(filepath); DescribeScope.active = scope; return DescribeScope.Class.make(ctx, scope); } }; /// https://jestjs.io/docs/expect // To support async tests, we need to track the test ID pub const Expect = struct { test_id: TestRunner.Test.ID, scope: *DescribeScope, value: js.JSValueRef, op: Op.Set = Op.Set.init(.{}), pub const Op = enum(u3) { resolves, rejects, not, pub const Set = std.EnumSet(Op); }; pub fn finalize( this: *Expect, ) void { js.JSValueUnprotect(VirtualMachine.vm.global.ref(), this.value); VirtualMachine.vm.allocator.destroy(this); } pub const Class = NewClass( Expect, .{ .name = "Expect" }, .{ .toBe = .{ .rfn = Expect.toBe, .name = "toBe", }, .toHaveBeenCalledTimes = .{ .rfn = Expect.toHaveBeenCalledTimes, .name = "toHaveBeenCalledTimes", }, .finalize = .{ .rfn = Expect.finalize, .name = "finalize" }, .toHaveBeenCalledWith = .{ .rfn = Expect.toHaveBeenCalledWith, .name = "toHaveBeenCalledWith", }, .toHaveBeenLastCalledWith = .{ .rfn = Expect.toHaveBeenLastCalledWith, .name = "toHaveBeenLastCalledWith", }, .toHaveBeenNthCalledWith = .{ .rfn = Expect.toHaveBeenNthCalledWith, .name = "toHaveBeenNthCalledWith", }, .toHaveReturnedTimes = .{ .rfn = Expect.toHaveReturnedTimes, .name = "toHaveReturnedTimes", }, .toHaveReturnedWith = .{ .rfn = Expect.toHaveReturnedWith, .name = "toHaveReturnedWith", }, .toHaveLastReturnedWith = .{ .rfn = Expect.toHaveLastReturnedWith, .name = "toHaveLastReturnedWith", }, .toHaveNthReturnedWith = .{ .rfn = Expect.toHaveNthReturnedWith, .name = "toHaveNthReturnedWith", }, .toHaveLength = .{ .rfn = Expect.toHaveLength, .name = "toHaveLength", }, .toHaveProperty = .{ .rfn = Expect.toHaveProperty, .name = "toHaveProperty", }, .toBeCloseTo = .{ .rfn = Expect.toBeCloseTo, .name = "toBeCloseTo", }, .toBeGreaterThan = .{ .rfn = Expect.toBeGreaterThan, .name = "toBeGreaterThan", }, .toBeGreaterThanOrEqual = .{ .rfn = Expect.toBeGreaterThanOrEqual, .name = "toBeGreaterThanOrEqual", }, .toBeLessThan = .{ .rfn = Expect.toBeLessThan, .name = "toBeLessThan", }, .toBeLessThanOrEqual = .{ .rfn = Expect.toBeLessThanOrEqual, .name = "toBeLessThanOrEqual", }, .toBeInstanceOf = .{ .rfn = Expect.toBeInstanceOf, .name = "toBeInstanceOf", }, .toContain = .{ .rfn = Expect.toContain, .name = "toContain", }, .toContainEqual = .{ .rfn = Expect.toContainEqual, .name = "toContainEqual", }, .toEqual = .{ .rfn = Expect.toEqual, .name = "toEqual", }, .toMatch = .{ .rfn = Expect.toMatch, .name = "toMatch", }, .toMatchObject = .{ .rfn = Expect.toMatchObject, .name = "toMatchObject", }, .toMatchSnapshot = .{ .rfn = Expect.toMatchSnapshot, .name = "toMatchSnapshot", }, .toMatchInlineSnapshot = .{ .rfn = Expect.toMatchInlineSnapshot, .name = "toMatchInlineSnapshot", }, .toStrictEqual = .{ .rfn = Expect.toStrictEqual, .name = "toStrictEqual", }, .toThrow = .{ .rfn = Expect.toThrow, .name = "toThrow", }, .toThrowErrorMatchingSnapshot = .{ .rfn = Expect.toThrowErrorMatchingSnapshot, .name = "toThrowErrorMatchingSnapshot", }, .toThrowErrorMatchingInlineSnapshot = .{ .rfn = Expect.toThrowErrorMatchingInlineSnapshot, .name = "toThrowErrorMatchingInlineSnapshot", }, }, .{ .not = .{ .get = Expect.not, .name = "not", }, .resolves = .{ .get = Expect.resolves, .name = "resolves", }, .rejects = .{ .get = Expect.rejects, .name = "rejects", }, }, ); /// Object.is() pub fn toBe( this: *Expect, ctx: js.JSContextRef, _: js.JSObjectRef, thisObject: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSValueRef { if (arguments.len != 1) { JSC.JSError( getAllocator(ctx), ".toBe() takes 1 argument", .{}, ctx, exception, ); return js.JSValueMakeUndefined(ctx); } this.scope.tests.items[this.test_id].counter.actual += 1; const left = JSValue.fromRef(arguments[0]); const right = JSValue.fromRef(this.value); if (!left.isSameValue(right, ctx.ptr())) { var lhs_formatter: JSC.ZigConsoleClient.Formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = ctx.ptr() }; var rhs_formatter: JSC.ZigConsoleClient.Formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = ctx.ptr() }; JSC.JSError( getAllocator(ctx), "Expected: {}\n\tReceived: {}", .{ left.toFmt(ctx.ptr(), &lhs_formatter), right.toFmt(ctx.ptr(), &rhs_formatter), }, ctx, exception, ); return null; } return thisObject; } pub fn toHaveLength( this: *Expect, ctx: js.JSContextRef, _: js.JSObjectRef, thisObject: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSValueRef { if (arguments.len != 1) { JSC.JSError( getAllocator(ctx), ".toHaveLength() takes 1 argument", .{}, ctx, exception, ); return js.JSValueMakeUndefined(ctx); } this.scope.tests.items[this.test_id].counter.actual += 1; const expected = JSC.JSValue.fromRef(arguments[0]).toU32(); const actual = JSC.JSValue.fromRef(this.value).getLengthOfArray(ctx.ptr()); if (expected != actual) { JSC.JSError( getAllocator(ctx), "Expected length to equal {d} but received {d}\n Expected: {d}\n Actual: {d}\n", .{ expected, actual, expected, actual, }, ctx, exception, ); return null; } return thisObject; } pub const toHaveBeenCalledTimes = notImplementedFn; pub const toHaveBeenCalledWith = notImplementedFn; pub const toHaveBeenLastCalledWith = notImplementedFn; pub const toHaveBeenNthCalledWith = notImplementedFn; pub const toHaveReturnedTimes = notImplementedFn; pub const toHaveReturnedWith = notImplementedFn; pub const toHaveLastReturnedWith = notImplementedFn; pub const toHaveNthReturnedWith = notImplementedFn; pub const toHaveProperty = notImplementedFn; pub const toBeCloseTo = notImplementedFn; pub const toBeGreaterThan = notImplementedFn; pub const toBeGreaterThanOrEqual = notImplementedFn; pub const toBeLessThan = notImplementedFn; pub const toBeLessThanOrEqual = notImplementedFn; pub const toBeInstanceOf = notImplementedFn; pub const toContain = notImplementedFn; pub const toContainEqual = notImplementedFn; pub const toEqual = notImplementedFn; pub const toMatch = notImplementedFn; pub const toMatchObject = notImplementedFn; pub const toMatchSnapshot = notImplementedFn; pub const toMatchInlineSnapshot = notImplementedFn; pub const toStrictEqual = notImplementedFn; pub const toThrow = notImplementedFn; pub const toThrowErrorMatchingSnapshot = notImplementedFn; pub const toThrowErrorMatchingInlineSnapshot = notImplementedFn; pub const not = notImplementedProp; pub const resolves = notImplementedProp; pub const rejects = notImplementedProp; }; pub const ExpectPrototype = struct { scope: *DescribeScope, test_id: TestRunner.Test.ID, op: Expect.Op.Set = Expect.Op.Set.init(.{}), pub const Class = NewClass( ExpectPrototype, .{ .name = "ExpectPrototype", .read_only = true, }, .{ .call = .{ .rfn = ExpectPrototype.call, }, .extend = .{ .name = "extend", .rfn = ExpectPrototype.extend, }, .anything = .{ .name = "anything", .rfn = ExpectPrototype.anything, }, .any = .{ .name = "any", .rfn = ExpectPrototype.any, }, .arrayContaining = .{ .name = "arrayContaining", .rfn = ExpectPrototype.arrayContaining, }, .assertions = .{ .name = "assertions", .rfn = ExpectPrototype.assertions, }, .hasAssertions = .{ .name = "hasAssertions", .rfn = ExpectPrototype.hasAssertions, }, .objectContaining = .{ .name = "objectContaining", .rfn = ExpectPrototype.objectContaining, }, .stringContaining = .{ .name = "stringContaining", .rfn = ExpectPrototype.stringContaining, }, .stringMatching = .{ .name = "stringMatching", .rfn = ExpectPrototype.stringMatching, }, .addSnapshotSerializer = .{ .name = "addSnapshotSerializer", .rfn = ExpectPrototype.addSnapshotSerializer, }, }, .{ .not = .{ .name = "not", .get = ExpectPrototype.not, }, .resolves = .{ .name = "resolves", .get = ExpectPrototype.resolves, }, .rejects = .{ .name = "rejects", .get = ExpectPrototype.rejects, }, }, ); pub const extend = notImplementedFn; pub const anything = notImplementedFn; pub const any = notImplementedFn; pub const arrayContaining = notImplementedFn; pub const assertions = notImplementedFn; pub const hasAssertions = notImplementedFn; pub const objectContaining = notImplementedFn; pub const stringContaining = notImplementedFn; pub const stringMatching = notImplementedFn; pub const addSnapshotSerializer = notImplementedFn; pub const not = notImplementedProp; pub const resolves = notImplementedProp; pub const rejects = notImplementedProp; pub fn call( _: *ExpectPrototype, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { if (arguments.len != 1) { JSError(getAllocator(ctx), "expect() requires one argument", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); } var expect_ = getAllocator(ctx).create(Expect) catch unreachable; js.JSValueProtect(ctx, arguments[0]); expect_.* = .{ .value = arguments[0], .scope = DescribeScope.active, .test_id = DescribeScope.active.current_test_id, }; return Expect.Class.make(ctx, expect_); } }; pub const TestScope = struct { counter: Counter = Counter{}, label: string = "", parent: *DescribeScope, callback: js.JSValueRef, id: TestRunner.Test.ID = 0, promise: ?*JSInternalPromise = null, pub const Class = NewClass(void, .{ .name = "test" }, .{ .call = call, .only = only }, .{}); pub const Counter = struct { expected: u32 = 0, actual: u32 = 0, }; pub fn only( // the DescribeScope here is the top of the file, not the real one _: void, ctx: js.JSContextRef, this: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { return callMaybeOnly(this, ctx, arguments, exception, true); } pub fn call( // the DescribeScope here is the top of the file, not the real one _: void, ctx: js.JSContextRef, this: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { return callMaybeOnly(this, ctx, arguments, exception, false); } fn callMaybeOnly( this: js.JSObjectRef, ctx: js.JSContextRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, is_only: bool, ) js.JSObjectRef { var args = arguments[0..@minimum(arguments.len, 2)]; var label: string = ""; if (args.len == 0) { return this; } if (js.JSValueIsString(ctx, args[0])) { var label_ = ZigString.init(""); JSC.JSValue.fromRef(arguments[0]).toZigString(&label_, ctx.ptr()); label = if (label_.is16Bit()) (strings.toUTF8AllocWithType(getAllocator(ctx), @TypeOf(label_.utf16Slice()), label_.utf16Slice()) catch unreachable) else label_.full(); args = args[1..]; } var function = args[0]; if (!js.JSValueIsObject(ctx, function) or !js.JSObjectIsFunction(ctx, function)) { JSError(getAllocator(ctx), "test() expects a function", .{}, ctx, exception); return this; } if (is_only) { Jest.runner.?.setOnly(); } if (!is_only and Jest.runner.?.only) return this; js.JSValueProtect(ctx, function); DescribeScope.active.tests.append(getAllocator(ctx), TestScope{ .label = label, .callback = function, .parent = DescribeScope.active, }) catch unreachable; return this; } pub const Result = union(TestRunner.Test.Status) { fail: u32, pass: u32, // assertion count pending: void, }; pub fn run( this: *TestScope, ) Result { if (comptime is_bindgen) return undefined; var vm = VirtualMachine.vm; defer { js.JSValueUnprotect(vm.global.ref(), this.callback); this.callback = null; } const initial_value = js.JSObjectCallAsFunctionReturnValue(vm.global.ref(), this.callback, null, 0, null); if (initial_value.isException(vm.global.vm()) or initial_value.isError() or initial_value.isAggregateError(vm.global)) { vm.defaultErrorHandler(initial_value, null); return .{ .fail = this.counter.actual }; } if (!initial_value.isUndefinedOrNull()) { if (this.promise != null) { return .{ .pending = .{} }; } this.promise = JSC.JSInternalPromise.resolvedPromise(vm.global, initial_value); defer { this.promise = null; } var status = JSC.JSPromise.Status.Pending; var vm_ptr = vm.global.vm(); while (this.promise != null and status == JSC.JSPromise.Status.Pending) : (status = this.promise.?.status(vm_ptr)) { vm.tick(); } switch (status) { .Rejected => { vm.defaultErrorHandler(this.promise.?.result(vm.global.vm()), null); return .{ .fail = this.counter.actual }; }, else => { if (this.promise != null) // don't care about the result _ = this.promise.?.result(vm.global.vm()); }, } } this.callback = null; if (this.counter.expected > 0 and this.counter.expected < this.counter.actual) { Output.prettyErrorln("Test fail: {d} / {d} expectations\n (make this better!)", .{ this.counter.actual, this.counter.expected, }); return .{ .fail = this.counter.actual }; } return .{ .pass = this.counter.actual }; } }; pub const DescribeScope = struct { label: string = "", parent: ?*DescribeScope = null, beforeAll: std.ArrayListUnmanaged(js.JSValueRef) = .{}, beforeEach: std.ArrayListUnmanaged(js.JSValueRef) = .{}, afterEach: std.ArrayListUnmanaged(js.JSValueRef) = .{}, afterAll: std.ArrayListUnmanaged(js.JSValueRef) = .{}, test_id_start: TestRunner.Test.ID = 0, test_id_len: TestRunner.Test.ID = 0, tests: std.ArrayListUnmanaged(TestScope) = .{}, file_id: TestRunner.File.ID, current_test_id: TestRunner.Test.ID = 0, pub const TestEntry = struct { label: string, callback: js.JSValueRef, pub const List = std.MultiArrayList(TestEntry); }; pub threadlocal var active: *DescribeScope = undefined; pub const Class = NewClass( DescribeScope, .{ .name = "describe", .read_only = true, }, .{ .call = describe, .afterAll = .{ .rfn = callAfterAll, .name = "afterAll" }, .beforeAll = .{ .rfn = callAfterAll, .name = "beforeAll" }, .beforeEach = .{ .rfn = callAfterAll, .name = "beforeEach" }, }, .{ .expect = .{ .get = createExpect, .name = "expect" }, // kind of a mindfuck but // describe("foo", () => {}).describe("bar") will wrok .describe = .{ .get = createDescribe, .name = "describe" }, .it = .{ .get = createTest, .name = "it" }, .@"test" = .{ .get = createTest, .name = "test" }, }, ); pub fn describe( this: *DescribeScope, ctx: js.JSContextRef, _: js.JSObjectRef, _: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { if (arguments.len == 0 or arguments.len > 2) { JSError(getAllocator(ctx), "describe() requires 1-2 arguments", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); } var label = ZigString.init(""); var args = arguments; if (js.JSValueIsString(ctx, arguments[0])) { JSC.JSValue.fromRef(arguments[0]).toZigString(&label, ctx.ptr()); args = args[1..]; } if (args.len == 0 or !js.JSObjectIsFunction(ctx, args[0])) { JSError(getAllocator(ctx), "describe() requires a callback function", .{}, ctx, exception); return js.JSValueMakeUndefined(ctx); } var callback = args[0]; var scope = getAllocator(ctx).create(DescribeScope) catch unreachable; scope.* = .{ .label = if (label.is16Bit()) (strings.toUTF8AllocWithType(getAllocator(ctx), @TypeOf(label.utf16Slice()), label.utf16Slice()) catch unreachable) else label.full(), .parent = this, .file_id = this.file_id, }; var new_this = DescribeScope.Class.make(ctx, scope); return scope.run(new_this, ctx, callback, exception); } pub fn run(this: *DescribeScope, thisObject: js.JSObjectRef, ctx: js.JSContextRef, callback: js.JSObjectRef, exception: js.ExceptionRef) js.JSObjectRef { if (comptime is_bindgen) return undefined; js.JSValueProtect(ctx, callback); defer js.JSValueUnprotect(ctx, callback); var original_active = active; defer active = original_active; active = this; { var result = js.JSObjectCallAsFunctionReturnValue(ctx, callback, thisObject, 0, null); var vm = JSC.VirtualMachine.vm; const promise = JSInternalPromise.resolvedPromise(ctx.ptr(), result); while (promise.status(ctx.ptr().vm()) == JSPromise.Status.Pending) { vm.tick(); } switch (promise.status(ctx.ptr().vm())) { JSPromise.Status.Fulfilled => {}, else => { exception.* = promise.result(ctx.ptr().vm()).asObjectRef(); return null; }, } } this.runTests(ctx); return js.JSValueMakeUndefined(ctx); } pub fn runTests(this: *DescribeScope, ctx: js.JSContextRef) void { // Step 1. Initialize the test block const file = this.file_id; var tests: []TestScope = this.tests.items; const end = @truncate(TestRunner.Test.ID, tests.len); if (end == 0) 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); // Step 3. Run the beforeAll callbacks, in reverse order // TODO: const source: logger.Source = Jest.runner.?.files.items(.source)[file]; var i: TestRunner.Test.ID = 0; while (i < end) { // the test array could resize in the middle of this loop this.current_test_id = i; var test_ = tests[i]; const result = TestScope.run(&test_); tests[i] = test_; // invalidate it this.current_test_id = std.math.maxInt(TestRunner.Test.ID); const test_id = i + this.test_id_start; switch (result) { .pass => |count| Jest.runner.?.reportPass(test_id, count), .fail => |count| Jest.runner.?.reportFailure(test_id, source.path.text, tests[i].label, count), .pending => @panic("Unexpected pending test"), } i += 1; } this.tests.deinit(getAllocator(ctx)); } const ScopeStack = ObjectPool(std.ArrayListUnmanaged(*DescribeScope), null, true, 16); // pub fn runBeforeAll(this: *DescribeScope, ctx: js.JSContextRef, exception: js.ExceptionRef) bool { // var scopes = ScopeStack.get(default_allocator); // defer scopes.release(); // scopes.data.clearRetainingCapacity(); // var cur: ?*DescribeScope = this; // while (cur) |scope| { // scopes.data.append(default_allocator, this) catch unreachable; // cur = scope.parent; // } // // while (scopes.data.popOrNull()) |scope| { // // scope. // // } // } pub fn runCallbacks(this: *DescribeScope, ctx: js.JSContextRef, callbacks: std.ArrayListUnmanaged(js.JSObjectRef), exception: js.ExceptionRef) bool { if (comptime is_bindgen) return undefined; var i: usize = 0; while (i < callbacks.items.len) : (i += 1) { var callback = callbacks.items[i]; var result = js.JSObjectCallAsFunctionReturnValue(ctx, callback, this, 0); if (result.isException(ctx.ptr().vm())) { exception.* = result.asObjectRef(); return false; } } } pub const callAfterAll = notImplementedFn; pub const callAfterEach = notImplementedFn; pub const callBeforeAll = notImplementedFn; pub fn createExpect( _: *DescribeScope, ctx: js.JSContextRef, _: js.JSValueRef, _: js.JSStringRef, _: js.ExceptionRef, ) js.JSObjectRef { var expect_ = getAllocator(ctx).create(ExpectPrototype) catch unreachable; expect_.* = .{ .scope = DescribeScope.active, .test_id = DescribeScope.active.current_test_id, }; return ExpectPrototype.Class.make(ctx, expect_); } pub fn createTest( _: *DescribeScope, ctx: js.JSContextRef, _: js.JSValueRef, _: js.JSStringRef, _: js.ExceptionRef, ) js.JSObjectRef { return js.JSObjectMake(ctx, TestScope.Class.get().*, null); } pub fn createDescribe( this: *DescribeScope, ctx: js.JSContextRef, _: js.JSValueRef, _: js.JSStringRef, _: js.ExceptionRef, ) js.JSObjectRef { return DescribeScope.Class.make(ctx, this); } };