diff options
author | 2023-02-21 11:47:13 -0800 | |
---|---|---|
committer | 2023-02-21 11:47:13 -0800 | |
commit | d786dd6c0bd0c8b4f4444ab55cbcb479e8d87b7e (patch) | |
tree | 059749b2af3d294d1ac59886123157f86bf3d1a2 /src/bun.js | |
parent | e21796acf506094ec39289c868b33a40ca505b74 (diff) | |
download | bun-d786dd6c0bd0c8b4f4444ab55cbcb479e8d87b7e.tar.gz bun-d786dd6c0bd0c8b4f4444ab55cbcb479e8d87b7e.tar.zst bun-d786dd6c0bd0c8b4f4444ab55cbcb479e8d87b7e.zip |
Update test runner output with colors and diffs (#2122)
* add zig-diff
* move diff functions
* toHaveProperty diff for objects
* use formatter
* format labels
* move work to format, diff when it makes sense
* remove comptime, dim equal slices
* order before diff
* line diffs
* add diffz
* switch to diffz
* add `diffLines()` function
* small `prettyFmt()` bug fix
* test runner color output
* update `toBe()` error output
* fix test
* diff method, fix crash
* fix link test
* remove `isRegex`
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/api/bun.zig | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 96 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 29 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 25 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 979 |
7 files changed, 969 insertions, 166 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index da42e5feb..0e17d089f 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -246,6 +246,8 @@ pub fn inspect( false, false, false, + false, + false, ); buffered_writer.flush() catch { return JSC.C.JSValueMakeUndefined(ctx); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index bb01e4101..c093fad59 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -97,9 +97,7 @@ static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) auto& internalHeaders = headers->internalHeaders(); for (auto& value : internalHeaders.getSetCookieHeaders()) { - res->writeHeader(std::string_view("set-cookie", 10), std::string_view( - value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length() - )); + res->writeHeader(std::string_view("set-cookie", 10), std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length())); } for (auto& header : internalHeaders.commonHeaders()) { @@ -107,8 +105,7 @@ static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) auto& value = header.value; res->writeHeader( std::string_view(name.is8Bit() ? reinterpret_cast<const char*>(name.characters8()) : name.utf8().data(), name.length()), - std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length()) - ); + std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length())); } for (auto& header : internalHeaders.uncommonHeaders()) { @@ -116,8 +113,7 @@ static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) auto& value = header.value; res->writeHeader( std::string_view(name.is8Bit() ? reinterpret_cast<const char*>(name.characters8()) : name.utf8().data(), name.length()), - std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length()) - ); + std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length())); } } @@ -718,12 +714,11 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createEmpty() return new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} }); } void WebCore__FetchHeaders__append(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2, - JSC__JSGlobalObject* lexicalGlobalObject) + JSC__JSGlobalObject* lexicalGlobalObject) { auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm()); WebCore::propagateException(*lexicalGlobalObject, throwScope, - headers->append(Zig::toString(*arg1), Zig::toString(*arg2)) - ); + headers->append(Zig::toString(*arg1), Zig::toString(*arg2))); } WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__VM* vm) { @@ -752,8 +747,7 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject* // ExceptionOr<void>. So we need to check for the exception and, if set, // translate it to JSValue and throw it. WebCore::propagateException(*lexicalGlobalObject, throwScope, - headers->fill(WTFMove(init.value())) - ); + headers->fill(WTFMove(init.value()))); } return headers; } @@ -770,8 +764,7 @@ JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* headers, JSC__J Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg1); auto* clone = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} }); WebCore::propagateException(*arg1, throwScope, - clone->fill(*headers) - ); + clone->fill(*headers)); return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg1, globalObject, WTFMove(clone))); } @@ -780,8 +773,7 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* h auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm()); auto* clone = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} }); WebCore::propagateException(*lexicalGlobalObject, throwScope, - clone->fill(*headers) - ); + clone->fill(*headers)); return clone; } @@ -936,9 +928,8 @@ JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, Strin } Ref<WebCore::FetchHeaders> headers = WebCore::FetchHeaders::create(); - WebCore::propagateException(*arg0, throwScope, - headers->fill(WebCore::FetchHeaders::Init(WTFMove(pairs))) - ); + WebCore::propagateException(*arg0, throwScope, + headers->fill(WebCore::FetchHeaders::Init(WTFMove(pairs)))); pairs.releaseBuffer(); return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), WTFMove(headers))); } @@ -965,15 +956,13 @@ void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* headers, const ZigString { auto throwScope = DECLARE_THROW_SCOPE(global->vm()); WebCore::propagateException(*global, throwScope, - headers->set(Zig::toString(*arg1), Zig::toString(*arg2)) - ); + headers->set(Zig::toString(*arg1), Zig::toString(*arg2))); } void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* headers, const ZigString* arg1, JSC__JSGlobalObject* global) { auto throwScope = DECLARE_THROW_SCOPE(global->vm()); WebCore::propagateException(*global, throwScope, - headers->remove(Zig::toString(*arg1)) - ); + headers->remove(Zig::toString(*arg1))); } void WebCore__FetchHeaders__fastRemove_(WebCore__FetchHeaders* headers, unsigned char headerName) @@ -3762,6 +3751,40 @@ restart: } } +inline bool propertyCompare(const std::pair<String, JSValue>& a, const std::pair<String, JSValue>& b) +{ + return codePointCompare(a.first.impl(), b.first.impl()) < 0; +} + +void JSC__JSValue__forEachPropertyOrdered(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, void* arg2, void (*iter)(JSC__JSGlobalObject* arg0, void* ctx, ZigString* arg2, JSC__JSValue JSValue3, bool isSymbol)) +{ + JSC::JSValue value = JSC::JSValue::decode(JSValue0); + JSC::JSObject* object = value.getObject(); + if (!object) + return; + + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + JSC::PropertyNameArray properties(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + JSC::JSObject::getOwnPropertyNames(object, globalObject, properties, DontEnumPropertiesMode::Include); + + Vector<std::pair<String, JSValue>> ordered_properties; + for (auto property : properties) { + JSValue propertyValue = object->getDirect(vm, property); + ordered_properties.append(std::pair<String, JSValue>(property.isSymbol() && !property.isPrivateName() ? property.impl() : property.string(), propertyValue)); + } + + std::sort(ordered_properties.begin(), ordered_properties.end(), propertyCompare); + + for (auto item : ordered_properties) { + ZigString key = toZigString(item.first); + JSValue propertyValue = item.second; + JSC::EnsureStillAliveScope ensureStillAliveScope(propertyValue); + iter(globalObject, arg2, &key, JSC::JSValue::encode(propertyValue), propertyValue.isSymbol()); + } +} + extern "C" JSC__JSValue JSC__JSValue__createRopeString(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) { return JSValue::encode(JSC::jsString(globalObject, JSC::JSValue::decode(JSValue0).toString(globalObject), JSC::JSValue::decode(JSValue1).toString(globalObject))); @@ -3796,39 +3819,44 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC__JSGlobalObject* arg0 JSC::JSValue::decode(JSValue4)); } -extern "C" JSC__AbortSignal* JSC__AbortSignal__signal(JSC__AbortSignal* arg0, JSC__JSValue JSValue1) { +extern "C" JSC__AbortSignal* JSC__AbortSignal__signal(JSC__AbortSignal* arg0, JSC__JSValue JSValue1) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); abortSignal->signalAbort(JSC::JSValue::decode(JSValue1)); return arg0; } -extern "C" bool JSC__AbortSignal__aborted(JSC__AbortSignal* arg0) { +extern "C" bool JSC__AbortSignal__aborted(JSC__AbortSignal* arg0) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); return abortSignal->aborted(); } -extern "C" JSC__JSValue JSC__AbortSignal__abortReason(JSC__AbortSignal* arg0) { +extern "C" JSC__JSValue JSC__AbortSignal__abortReason(JSC__AbortSignal* arg0) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); return JSC::JSValue::encode(abortSignal->reason().getValue()); } - -extern "C" JSC__AbortSignal* JSC__AbortSignal__ref(JSC__AbortSignal* arg0) { +extern "C" JSC__AbortSignal* JSC__AbortSignal__ref(JSC__AbortSignal* arg0) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); abortSignal->ref(); return arg0; } -extern "C" JSC__AbortSignal* JSC__AbortSignal__unref(JSC__AbortSignal* arg0) { +extern "C" JSC__AbortSignal* JSC__AbortSignal__unref(JSC__AbortSignal* arg0) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); abortSignal->deref(); return arg0; } -extern "C" JSC__AbortSignal* JSC__AbortSignal__addListener(JSC__AbortSignal* arg0, void* ctx, void (*callback)(void* ctx, JSC__JSValue reason)) { +extern "C" JSC__AbortSignal* JSC__AbortSignal__addListener(JSC__AbortSignal* arg0, void* ctx, void (*callback)(void* ctx, JSC__JSValue reason)) +{ WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0); - - if(abortSignal->aborted()){ + + if (abortSignal->aborted()) { callback(ctx, JSC::JSValue::encode(abortSignal->reason().getValue())); return arg0; } @@ -3859,7 +3887,7 @@ extern "C" JSC__JSValue JSC__AbortSignal__createAbortError(const ZigString* mess error->putDirect( vm, vm.propertyNames->name, JSC::JSValue(JSC::jsOwnedString(vm, ABORT_ERROR_NAME)), - 0); + 0); if (code.len > 0) { auto clientData = WebCore::clientData(vm); @@ -3881,7 +3909,7 @@ extern "C" JSC__JSValue JSC__AbortSignal__createTimeoutError(const ZigString* me error->putDirect( vm, vm.propertyNames->name, JSC::JSValue(JSC::jsOwnedString(vm, TIMEOUT_ERROR_NAME)), - 0); + 0); if (code.len > 0) { auto clientData = WebCore::clientData(vm); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 64b2b060d..800fd0af9 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -211,12 +211,12 @@ pub const ZigString = extern struct { pub fn substring(this: ZigString, offset: usize, maxlen: usize) ZigString { var len: usize = undefined; - if(maxlen == 0){ + if (maxlen == 0) { len = this.len; - }else { + } else { len = @max(this.len, maxlen); } - + if (this.is16Bit()) { return ZigString.from16Slice(this.utf16SliceAligned()[@min(this.len, offset)..len]); } @@ -2911,6 +2911,15 @@ pub const JSValue = enum(JSValueReprInt) { cppFn("forEachProperty", .{ this, globalThis, ctx, callback }); } + pub fn forEachPropertyOrdered( + this: JSValue, + globalObject: *JSC.JSGlobalObject, + ctx: ?*anyopaque, + callback: PropertyIteratorFn, + ) void { + cppFn("forEachPropertyOrdered", .{ this, globalObject, ctx, callback }); + } + pub fn coerce(this: JSValue, comptime T: type, globalThis: *JSC.JSGlobalObject) T { return switch (T) { ZigString => this.getZigString(globalThis), @@ -3638,6 +3647,19 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("strictDeepEquals", .{ this, other, global }); } + pub const DiffMethod = enum(u8) { + none, + character, + word, + line, + }; + + pub fn determineDiffMethod(this: JSValue, other: JSValue, global: *JSGlobalObject) DiffMethod { + if ((this.isString() and other.isString()) or (this.jsType() == .RegExpObject and other.jsType() == .RegExpObject) or (this.isBuffer(global) and other.isBuffer(global))) return .character; + if (this.isObject() and other.isObject()) return .line; + return .none; + } + pub fn asString(this: JSValue) *JSString { return cppFn("asString", .{ this, @@ -3852,6 +3874,7 @@ pub const JSValue = enum(JSValueReprInt) { "fastGet_", "forEach", "forEachProperty", + "forEachPropertyOrdered", "fromEntries", "fromInt64NoTruncate", "fromUInt64NoTruncate", diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 70aae7df3..aeced9a69 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -967,6 +967,8 @@ pub const ZigConsoleClient = struct { enable_colors, true, true, + false, + false, ) else if (message_type == .Log) { _ = console.writer.write("\n") catch 0; @@ -1015,6 +1017,8 @@ pub const ZigConsoleClient = struct { enable_colors: bool, add_newline: bool, flush: bool, + order_properties: bool, + quote_strings: bool, ) void { var fmt: ZigConsoleClient.Formatter = undefined; defer { @@ -1026,7 +1030,12 @@ pub const ZigConsoleClient = struct { } if (len == 1) { - fmt = ZigConsoleClient.Formatter{ .remaining_values = &[_]JSValue{}, .globalThis = global }; + fmt = ZigConsoleClient.Formatter{ + .remaining_values = &[_]JSValue{}, + .globalThis = global, + .ordered_properties = order_properties, + .quote_strings = quote_strings, + }; const tag = ZigConsoleClient.Formatter.Tag.get(vals[0], global); var unbuffered_writer = if (comptime Writer != RawWriter) @@ -1099,7 +1108,12 @@ pub const ZigConsoleClient = struct { } var this_value: JSValue = vals[0]; - fmt = ZigConsoleClient.Formatter{ .remaining_values = vals[0..len][1..], .globalThis = global }; + fmt = ZigConsoleClient.Formatter{ + .remaining_values = vals[0..len][1..], + .globalThis = global, + .ordered_properties = order_properties, + .quote_strings = quote_strings, + }; var tag: ZigConsoleClient.Formatter.Tag.Result = undefined; var any = false; @@ -1163,6 +1177,7 @@ pub const ZigConsoleClient = struct { failed: bool = false, estimated_line_length: usize = 0, always_newline_scope: bool = false, + ordered_properties: bool = false, pub fn goodTimeForANewLine(this: *@This()) bool { if (this.estimated_line_length > 80) { @@ -2541,7 +2556,11 @@ pub const ZigConsoleClient = struct { .parent = value, }; - value.forEachProperty(this.globalThis, &iter, Iterator.forEach); + if (this.ordered_properties) { + value.forEachPropertyOrdered(this.globalThis, &iter, Iterator.forEach); + } else { + value.forEachProperty(this.globalThis, &iter, Iterator.forEach); + } if (iter.i == 0) { if (value.isClass(this.globalThis) and !value.isCallable(this.globalThis.vm())) diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index a81695ac1..50c37dde6 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1676656020 +//-- AUTOGENERATED FILE -- 1676945760 #pragma once #include <stddef.h> @@ -296,6 +296,7 @@ CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue CPP_DECL JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, unsigned char arg2); CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, void* arg2, JSC__JSValue JSValue3)) __attribute__((nonnull (3))); CPP_DECL void JSC__JSValue__forEachProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, ZigString* arg2, JSC__JSValue JSValue3, bool arg4)) __attribute__((nonnull (3))); +CPP_DECL void JSC__JSValue__forEachPropertyOrdered(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, ZigString* arg2, JSC__JSValue JSValue3, bool arg4)) __attribute__((nonnull (3))); CPP_DECL JSC__JSValue JSC__JSValue__fromEntries(JSC__JSGlobalObject* arg0, ZigString* arg1, ZigString* arg2, size_t arg3, bool arg4); CPP_DECL JSC__JSValue JSC__JSValue__fromInt64NoTruncate(JSC__JSGlobalObject* arg0, int64_t arg1); CPP_DECL JSC__JSValue JSC__JSValue__fromUInt64NoTruncate(JSC__JSGlobalObject* arg0, uint64_t arg1); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index ac936e280..12c72a717 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -209,6 +209,7 @@ pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSVa pub extern fn JSC__JSValue__fastGet_(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u8) JSC__JSValue; pub extern fn JSC__JSValue__forEach(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.VM, *bindings.JSGlobalObject, ?*anyopaque, JSC__JSValue) callconv(.C) void) void; pub extern fn JSC__JSValue__forEachProperty(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.JSGlobalObject, ?*anyopaque, [*c]ZigString, JSC__JSValue, bool) callconv(.C) void) void; +pub extern fn JSC__JSValue__forEachPropertyOrdered(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.JSGlobalObject, ?*anyopaque, [*c]ZigString, JSC__JSValue, bool) callconv(.C) void) void; pub extern fn JSC__JSValue__fromEntries(arg0: *bindings.JSGlobalObject, arg1: [*c]ZigString, arg2: [*c]ZigString, arg3: usize, arg4: bool) JSC__JSValue; pub extern fn JSC__JSValue__fromInt64NoTruncate(arg0: *bindings.JSGlobalObject, arg1: i64) JSC__JSValue; pub extern fn JSC__JSValue__fromUInt64NoTruncate(arg0: *bindings.JSGlobalObject, arg1: u64) JSC__JSValue; diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 1ae0bd51a..90c595037 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -8,6 +8,8 @@ const HTTPClient = @import("bun").HTTP; const NetworkThread = HTTPClient.NetworkThread; const Environment = @import("../../env.zig"); +const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig"); + const JSC = @import("bun").JSC; const js = JSC.C; @@ -61,6 +63,161 @@ fn notImplementedProp( return null; } +pub const DiffFormatter = struct { + received: JSValue, + expected: JSValue, + globalObject: *JSC.JSGlobalObject, + not: bool = false, + + pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var received_buf = MutableString.init(default_allocator, 0) catch unreachable; + var expected_buf = MutableString.init(default_allocator, 0) catch unreachable; + defer { + received_buf.deinit(); + expected_buf.deinit(); + } + + { + var buffered_writer_ = bun.MutableString.BufferedWriter{ .context = &received_buf }; + var buffered_writer = &buffered_writer_; + + var buf_writer = buffered_writer.writer(); + const Writer = @TypeOf(buf_writer); + + JSC.ZigConsoleClient.format( + .Debug, + this.globalObject, + @ptrCast([*]const JSValue, &this.received), + 1, + Writer, + Writer, + buf_writer, + false, + false, + false, + true, + true, + ); + buffered_writer.flush() catch unreachable; + + buffered_writer_.context = &expected_buf; + + JSC.ZigConsoleClient.format( + .Debug, + this.globalObject, + @ptrCast([*]const JSValue, &this.expected), + 1, + Writer, + Writer, + buf_writer, + false, + false, + false, + true, + true, + ); + buffered_writer.flush() catch unreachable; + } + + const received_slice = received_buf.toOwnedSliceLeaky(); + const expected_slice = expected_buf.toOwnedSliceLeaky(); + + if (this.not) { + const not_fmt = "Expected: not <green>{s}<r>"; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(not_fmt, true), .{expected_slice}); + } else { + try writer.print(Output.prettyFmt(not_fmt, false), .{expected_slice}); + } + return; + } + + const equal_fmt = "<d>{s}<r>"; + const delete_fmt = "<red>{s}<r>"; + const insert_fmt = "<green>{s}<r>"; + + switch (this.received.determineDiffMethod(this.expected, this.globalObject)) { + .none => { + const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>"; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = this.globalObject, .quote_strings = true }; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(fmt, true), .{ + this.expected.toFmt(this.globalObject, &formatter), + this.received.toFmt(this.globalObject, &formatter), + }); + return; + } + + try writer.print(Output.prettyFmt(fmt, true), .{ + this.expected.toFmt(this.globalObject, &formatter), + this.received.toFmt(this.globalObject, &formatter), + }); + return; + }, + .character => { + var dmp = DiffMatchPatch.default; + dmp.diff_timeout = 200; + var diffs = try dmp.diff(default_allocator, received_slice, expected_slice, false); + defer diffs.deinit(default_allocator); + + try writer.writeAll(Output.prettyFmt("Expected: ", true)); + for (diffs.items) |df| { + switch (df.operation) { + .delete => continue, + .insert => try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}), + .equal => try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}), + } + } + + try writer.writeAll(Output.prettyFmt("\nReceived: ", true)); + for (diffs.items) |df| { + switch (df.operation) { + .insert => continue, + .delete => try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}), + .equal => try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}), + } + } + return; + }, + .line => { + var dmp = DiffMatchPatch.default; + dmp.diff_timeout = 200; + var diffs = try dmp.diffLines(default_allocator, received_slice, expected_slice); + defer diffs.deinit(default_allocator); + + var insert_count: usize = 0; + var delete_count: usize = 0; + + for (diffs.items) |df| { + switch (df.operation) { + .equal => { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); + }, + .insert => { + for (df.text) |c| { + if (c == '\n') insert_count += 1; + } + try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}); + }, + .delete => { + for (df.text) |c| { + if (c == '\n') delete_count += 1; + } + try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}); + }, + } + } + + try writer.print(Output.prettyFmt("\n\n<green>- Expected - {d}<r>\n", true), .{insert_count}); + try writer.print(Output.prettyFmt("<red>+ Received + {d}<r>", true), .{delete_count}); + return; + }, + .word => {}, + } + return; + } +}; + const ArrayIdentityContext = @import("../../identity_context.zig").ArrayIdentityContext; pub const TestRunner = struct { tests: TestRunner.Test.List = .{}, @@ -327,38 +484,89 @@ pub const Expect = struct { } active_test_expectation_counter.actual += 1; - const left = arguments[0]; - left.ensureStillAlive(); - const right = Expect.capturedValueGetCached(thisValue) orelse { + const right = arguments[0]; + right.ensureStillAlive(); + const left = Expect.capturedValueGetCached(thisValue) orelse { globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; - right.ensureStillAlive(); + left.ensureStillAlive(); const not = this.op.contains(.not); - var pass = left.isSameValue(right, globalObject); + var pass = right.isSameValue(left, globalObject); if (comptime Environment.allow_assert) { - std.debug.assert(pass == JSC.C.JSValueIsStrictEqual(globalObject, left.asObjectRef(), right.asObjectRef())); + std.debug.assert(pass == JSC.C.JSValueIsStrictEqual(globalObject, right.asObjectRef(), left.asObjectRef())); } if (not) pass = !pass; if (pass) return thisValue; // handle failure - var lhs_fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; - var rhs_fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; - if (comptime Environment.allow_assert) { - Output.prettyErrorln("\nJSType: {s}\nJSType: {s}\n\n", .{ @tagName(left.jsType()), @tagName(right.jsType()) }); + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + if (not) { + const signature = comptime getSignature("toBe", "<green>expected<r>", true); + const fmt = signature ++ "\n\nExpected: not <green>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{right.toFmt(globalObject, &formatter)}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{right.toFmt(globalObject, &formatter)}); + return .zero; } - if (not) { - globalObject.throw("\n\tExpected: not {any}\n\tReceived: {any}", .{ left.toFmt(globalObject, &lhs_fmt), right.toFmt(globalObject, &rhs_fmt) }); - } else { - globalObject.throw("\n\tExpected: {any}\n\tReceived: {any}", .{ left.toFmt(globalObject, &lhs_fmt), right.toFmt(globalObject, &rhs_fmt) }); + const signature = comptime getSignature("toBe", "<green>expected<r>", false); + if (left.deepEquals(right, globalObject) or left.strictDeepEquals(right, globalObject)) { + const fmt = signature ++ + "\n\n<d>If this test should pass, replace \"toBe\" with \"toEqual\" or \"toStrictEqual\"<r>" ++ + "\n\nExpected: <green>{any}<r>\n" ++ + "Received: serializes to the same string\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{right.toFmt(globalObject, &formatter)}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{right.toFmt(globalObject, &formatter)}); + return .zero; + } + + if (right.isString() and left.isString()) { + const diff_format = DiffFormatter{ + .expected = right, + .received = left, + .globalObject = globalObject, + .not = not, + }; + const fmt = signature ++ "\n\n{any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_format}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_format}); + return .zero; } + + const fmt = signature ++ "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + right.toFmt(globalObject, &formatter), + left.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + right.toFmt(globalObject, &formatter), + left.toFmt(globalObject, &formatter), + }); return .zero; } + pub fn getSignature(comptime matcher_name: string, comptime args: string, comptime not: bool) string { + const received = "<d>expect(<r><red>received<r><d>).<r>"; + comptime if (not) { + return received ++ "not<d>.<r>" ++ matcher_name ++ "<d>(<r>" ++ args ++ "<d>)<r>"; + }; + return received ++ matcher_name ++ "<d>(<r>" ++ args ++ "<d>)<r>"; + } + pub fn toHaveLength( this: *Expect, globalObject: *JSC.JSGlobalObject, @@ -438,10 +646,27 @@ pub const Expect = struct { // handle failure if (not) { - globalObject.throw("\n\tExpected: not {d}\n\tReceived: {d}", .{ expected_length, actual_length }); - } else { - globalObject.throw("\n\tExpected: {d}\n\tReceived: {d}", .{ expected_length, actual_length }); + const expected_line = "Expected length: not <green>{d}<r>\n"; + const fmt = comptime getSignature("toHaveLength", "<green>expected<r>", true) ++ "\n\n" ++ expected_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_length}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_length}); + return .zero; + } + + const expected_line = "Expected length: <green>{d}<r>\n"; + const received_line = "Received length: <red>{d}<r>\n"; + const fmt = comptime getSignature("toHaveLength", "<green>expected<r>", false) ++ "\n\n" ++ + expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_length, actual_length }); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_length, actual_length }); return .zero; } @@ -501,12 +726,30 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = expected.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected to not contain \"{any}\"", .{expected.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected to contain \"{any}\"", .{expected.toFmt(globalObject, &fmt)}); + const expected_line = "Expected to contain: not <green>{any}<r>\n"; + const fmt = comptime getSignature("toContain", "<green>expected<r>", true) ++ "\n\n" ++ expected_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_fmt}); + return .zero; + } + + const expected_line = "Expected to contain: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toContain", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); return .zero; } @@ -536,12 +779,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not truthy.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be truthy.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeTruthy", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeTruthy", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -564,12 +823,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not undefined.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be undefined.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeUndefined", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeUndefined", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -596,12 +871,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not NaN.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be NaN.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeNaN", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeNaN", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -623,12 +914,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not null.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be null.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeNull", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeNull", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -650,12 +957,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not defined.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be defined.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeDefined", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeDefined", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -680,12 +1003,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected \"{any}\" to be not falsy.", .{value.toFmt(globalObject, &fmt)}); - } else { - globalObject.throw("Expected \"{any}\" to be falsy.", .{value.toFmt(globalObject, &fmt)}); + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeFalsy", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeFalsy", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); return .zero; } @@ -722,12 +1061,26 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const diff_formatter = DiffFormatter{ .received = value, .expected = expected, .globalObject = globalObject, .not = not }; + if (not) { - globalObject.throw("Expected values to not be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected values to be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); + const signature = comptime getSignature("toEqual", "<green>expected<r>", true); + const fmt = signature ++ "\n\n{any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + return .zero; } + + const signature = comptime getSignature("toEqual", "<green>expected<r>", false); + const fmt = signature ++ "\n\n{any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); return .zero; } @@ -764,12 +1117,26 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const diff_formatter = DiffFormatter{ .received = value, .expected = expected, .globalObject = globalObject, .not = not }; + if (not) { - globalObject.throw("Expected values to not be strictly equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected values to be strictly equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); + const signature = comptime getSignature("toStrictEqual", "<green>expected<r>", true); + const fmt = signature ++ "\n\n{any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + return .zero; } + + const signature = comptime getSignature("toStrictEqual", "<green>expected<r>", false); + const fmt = signature ++ "\n\n{any}\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); return .zero; } @@ -794,8 +1161,8 @@ pub const Expect = struct { const expected_property_path = arguments[0]; expected_property_path.ensureStillAlive(); - const expected_value: ?JSValue = if (arguments.len > 1) arguments[1] else null; - if (expected_value) |ev| ev.ensureStillAlive(); + const expected_property: ?JSValue = if (arguments.len > 1) arguments[1] else null; + if (expected_property) |ev| ev.ensureStillAlive(); const value = Expect.capturedValueGetCached(thisValue) orelse { globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); @@ -812,33 +1179,97 @@ pub const Expect = struct { var path_string = ZigString.Empty; expected_property_path.toZigString(&path_string, globalObject); - const expected_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); + const received_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); - var pass = !expected_property.isEmpty(); + var pass = !received_property.isEmpty(); - if (pass and expected_value != null) { - pass = expected_property.deepEquals(expected_value.?, globalObject); + if (pass and expected_property != null) { + pass = received_property.deepEquals(expected_property.?, globalObject); } if (not) pass = !pass; if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; if (not) { - if (!expected_property.isEmpty() and expected_value != null) { - globalObject.throw("Expected property \"{any}\" to not be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected \"{any}\" to not have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + if (expected_property != null) { + const signature = comptime getSignature("toHaveProperty", "<green>path<r><d>, <r><green>value<r>", true); + if (!received_property.isEmpty()) { + const fmt = signature ++ "\n\nExpected path: <green>{any}<r>\n\nExpected value: not <green>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_property_path.toFmt(globalObject, &formatter), + expected_property.?.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_property_path.toFmt(globalObject, &formatter), + expected_property.?.toFmt(globalObject, &formatter), + }); + return .zero; + } } - } else { - if (!expected_property.isEmpty() and expected_value != null) { - globalObject.throw("Expected property \"{any}\" to be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected \"{any}\" to have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + + const signature = comptime getSignature("toHaveProperty", "<green>path<r>", true); + const fmt = signature ++ "\n\nExpected path: not <green>{any}<r>\n\nReceived value: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_property_path.toFmt(globalObject, &formatter), + received_property.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + expected_property_path.toFmt(globalObject, &formatter), + received_property.toFmt(globalObject, &formatter), + }); + return .zero; + } + + if (expected_property != null) { + const signature = comptime getSignature("toHaveProperty", "<green>path<r><d>, <r><green>value<r>", false); + if (!received_property.isEmpty()) { + // deep equal case + const fmt = signature ++ "\n\n{any}\n"; + const diff_format = DiffFormatter{ + .received = received_property, + .expected = expected_property.?, + .globalObject = globalObject, + }; + + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{diff_format}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{diff_format}); + return .zero; } + + const fmt = signature ++ "\n\nExpected path: <green>{any}<r>\n\nExpected value: <green>{any}<r>\n\n" ++ + "Unable to find property\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_property_path.toFmt(globalObject, &formatter), + expected_property.?.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + expected_property_path.toFmt(globalObject, &formatter), + expected_property.?.toFmt(globalObject, &formatter), + }); + return .zero; } + const signature = comptime getSignature("toHaveProperty", "<green>path<r>", false); + const fmt = signature ++ "\n\nExpected path: <green>{any}<r>\n\nUnable to find property\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_property_path.toFmt(globalObject, &formatter)}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_property_path.toFmt(globalObject, &formatter)}); return .zero; } @@ -896,12 +1327,32 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = other_value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected {any} to not be greater than {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected {any} to be greater than {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); + const expected_line = "Expected: not \\> <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeGreaterThan", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected: \\> <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeGreaterThan", "<green>expected<r>", false) ++ "\n\n" ++ + expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(comptime Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); return .zero; } @@ -959,11 +1410,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = other_value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected {any} to not be greater than or equal to {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected {any} to be greater than or equal to {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); + const expected_line = "Expected: not \\>= <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeGreaterThanOrEqual", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected: \\>= <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeGreaterThanOrEqual", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(comptime Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; } return .zero; } @@ -1022,11 +1490,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = other_value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected {any} to not be less than {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected {any} to be less than {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); + const expected_line = "Expected: not \\< <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeLessThan", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected: \\< <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeLessThan", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(comptime Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; } return .zero; } @@ -1085,11 +1570,28 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const value_fmt = value.toFmt(globalObject, &formatter); + const expected_fmt = other_value.toFmt(globalObject, &formatter); if (not) { - globalObject.throw("Expected {any} to not be less than or equal to {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); - } else { - globalObject.throw("Expected {any} to be less than or equal to {any}", .{ value.toFmt(globalObject, &fmt), other_value.toFmt(globalObject, &fmt) }); + const expected_line = "Expected: not \\<= <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeLessThanOrEqual", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected: \\<= <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeLessThanOrEqual", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(comptime Output.prettyFmt(fmt, true), .{ expected_fmt, value_fmt }); + return .zero; } return .zero; } @@ -1108,7 +1610,7 @@ pub const Expect = struct { active_test_expectation_counter.actual += 1; - const expected_value = if (arguments.len > 0) brk: { + const expected_value: JSValue = if (arguments.len > 0) brk: { const value = arguments[0]; if (value.isEmptyOrUndefinedOrNull() or !value.isObject() and !value.isString()) { var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; @@ -1160,59 +1662,286 @@ pub const Expect = struct { }; const did_throw = result_ != null; - const matched_expectation = did_throw == !not; - if (!matched_expectation) { - if (!not) - globalObject.throw("Expected function to throw", .{}) - else { - var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; - globalObject.throw("Expected function not to throw. Received:\n\t{any}", .{result_.?.toFmt(globalObject, &fmt)}); + if (not) { + const signature = comptime getSignature("toThrow", "<green>expected<r>", true); + + if (!did_throw) return thisValue; + + const result: JSValue = result_.?; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + + if (expected_value.isEmpty()) { + const signature_no_args = comptime getSignature("toThrow", "", true); + if (result.isError()) { + const name = result.getIfPropertyExistsImpl(globalObject, "name", 4); + const message = result.getIfPropertyExistsImpl(globalObject, "message", 7); + const fmt = signature_no_args ++ "\n\nError name: <red>{any}<r>\nError message: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + name.toFmt(globalObject, &formatter), + message.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + name.toFmt(globalObject, &formatter), + message.toFmt(globalObject, &formatter), + }); + return .zero; + } + + // non error thrown + const fmt = signature_no_args ++ "\n\nThrown value: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{result.toFmt(globalObject, &formatter)}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{result.toFmt(globalObject, &formatter)}); + return .zero; + } + + if (expected_value.isString()) { + const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); + + // partial match (regex not supported) + { + var expected_string = ZigString.Empty; + var received_string = ZigString.Empty; + expected_value.toZigString(&expected_string, globalObject); + received_message.toZigString(&received_string, globalObject); + const expected_slice = expected_string.toSlice(default_allocator); + const received_slice = received_string.toSlice(default_allocator); + defer { + expected_slice.deinit(); + received_slice.deinit(); + } + if (!strings.contains(received_slice.slice(), expected_slice.slice())) return thisValue; + } + + const fmt = signature ++ "\n\nExpected substring: not <green>{any}<r>\nReceived message: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ + expected_value.toFmt(globalObject, &formatter), + received_message.toFmt(globalObject, &formatter), + }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ + expected_value.toFmt(globalObject, &formatter), + received_message.toFmt(globalObject, &formatter), + }); + return .zero; } + if (expected_value.get(globalObject, "message")) |expected_message| { + const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); + // no partial match for this case + if (!expected_message.isSameValue(received_message, globalObject)) return thisValue; + + const fmt = signature ++ "\n\nExpected message: not <green>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_message.toFmt(globalObject, &formatter)}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_message.toFmt(globalObject, &formatter)}); + return .zero; + } + + if (!result.isInstanceOf(globalObject, expected_value)) return thisValue; + + var expected_class = ZigString.Empty; + expected_value.getClassName(globalObject, &expected_class); + const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7); + const fmt = signature ++ "\n\nExpected constructor: not <green>{s}<r>\n\nReceived message: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_class, received_message.toFmt(globalObject, &formatter) }); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_class, received_message.toFmt(globalObject, &formatter) }); return .zero; } - // If you throw a string, it's treated as the message of an Error - // If you are expected not to throw and you didn't throw, then you pass - // If you are expected to throw a specific message and you throw a different one, then you fail. - if (matched_expectation and (!expected_value.isCell() or not)) - return thisValue; + const signature = comptime getSignature("toThrow", "<green>expected<r>", false); + if (did_throw) { + if (expected_value.isEmpty()) return thisValue; + + const result: JSValue = result_.?; + const _received_message = result.get(globalObject, "message"); + + if (expected_value.isString()) { + if (_received_message) |received_message| { + // partial match (regex not supported) + var expected_string = ZigString.Empty; + var received_string = ZigString.Empty; + expected_value.toZigString(&expected_string, globalObject); + received_message.toZigString(&received_string, globalObject); + const expected_slice = expected_string.toSlice(default_allocator); + const received_slice = received_string.toSlice(default_allocator); + defer { + expected_slice.deinit(); + received_slice.deinit(); + } + if (strings.contains(received_slice.slice(), expected_slice.slice())) return thisValue; + } - const result = result_ orelse JSC.JSValue.jsUndefined(); + // error: message from received error does not match expected string + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; - const expected_error = expected_value.toError(); + if (_received_message) |received_message| { + const expected_value_fmt = expected_value.toFmt(globalObject, &formatter); + const received_message_fmt = received_message.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived message: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_value_fmt, received_message_fmt }); + return .zero; + } - if (expected_value.isString() or expected_error != null) { - const expected = brk: { - if (expected_value.isString()) break :brk expected_value; - break :brk expected_error.?.get(globalObject, "message"); - }; - const actual: ?JSValue = if (result.isObject()) - result.get(globalObject, "message") - else - null; - // TODO support partial match - const pass = brk: { - if (expected) |expected_message| - if (actual) |actual_message| - break :brk expected_message.isSameValue(actual_message, globalObject); - break :brk false; - }; + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_value_fmt, received_message_fmt }); + return .zero; + } + + const expected_fmt = expected_value.toFmt(globalObject, &formatter); + const received_fmt = result.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived value: <red>{any}<r>"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt }); + return .zero; + } + + if (expected_value.get(globalObject, "message")) |expected_message| { + if (_received_message) |received_message| { + if (received_message.isSameValue(expected_message, globalObject)) return thisValue; + } + + // error: message from received error does not match expected error message. + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + + if (_received_message) |received_message| { + const expected_fmt = expected_message.toFmt(globalObject, &formatter); + const received_fmt = received_message.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\nExpected message: <green>{any}<r>\nReceived message: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt }); + return .zero; + } - if (pass) return thisValue; - globalObject.throw("\n\tExpected: {s}\n\tReceived: {s}", .{ - if (expected) |message| message.getZigString(globalObject) else ZigString.init("undefined"), - if (actual) |message| message.getZigString(globalObject) else ZigString.init("undefined"), + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt }); + return .zero; + } + + const expected_fmt = expected_message.toFmt(globalObject, &formatter); + const received_fmt = result.toFmt(globalObject, &formatter); + const fmt = signature ++ "\n\nExpected message: <green>{any}<r>\nReceived value: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt }); + return .zero; + } + + if (result.isInstanceOf(globalObject, expected_value)) return thisValue; + + // error: received error not instance of received error constructor + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + var expected_class = ZigString.Empty; + var received_class = ZigString.Empty; + expected_value.getClassName(globalObject, &expected_class); + result.getClassName(globalObject, &received_class); + const fmt = signature ++ "\n\nExpected constructor: <green>{s}<r>\nReceived constructor: <red>{s}<r>\n\n"; + + if (_received_message) |received_message| { + const message_fmt = fmt ++ "Received message: <red>{any}<r>\n"; + const received_message_fmt = received_message.toFmt(globalObject, &formatter); + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(message_fmt, true), .{ + expected_class, + received_class, + received_message_fmt, + }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(message_fmt, false), .{ + expected_class, + received_class, + received_message_fmt, + }); + return .zero; + } + + const received_fmt = result.toFmt(globalObject, &formatter); + const value_fmt = fmt ++ "Received value: <red>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(value_fmt, true), .{ + expected_class, + received_class, + received_fmt, + }); + return .zero; + } + + globalObject.throw(Output.prettyFmt(value_fmt, false), .{ + expected_class, + received_class, + received_fmt, }); return .zero; } - if (result.isInstanceOf(globalObject, expected_value)) return thisValue; - globalObject.throw("\n\tExpected type: {s}\n\tReceived type: {s}", .{ - expected_value.getName(globalObject), - if (result.get(globalObject, "name")) |name| name.getZigString(globalObject) else ZigString.init("<Unknown>"), - }); + // did not throw + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + const received_line = "Received function did not throw\n"; + + if (expected_value.isEmpty()) { + const fmt = comptime getSignature("toThrow", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, false), .{}); + return .zero; + } + + if (expected_value.isString()) { + const expected_fmt = "\n\nExpected substring: <green>{any}<r>\n\n" ++ received_line; + const fmt = signature ++ expected_fmt; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_value.toFmt(globalObject, &formatter)}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_value.toFmt(globalObject, &formatter)}); + return .zero; + } + + if (expected_value.get(globalObject, "message")) |expected_message| { + const expected_fmt = "\n\nExpected message: <green>{any}<r>\n\n" ++ received_line; + const fmt = signature ++ expected_fmt; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_message.toFmt(globalObject, &formatter)}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{expected_message.toFmt(globalObject, &formatter)}); + return .zero; + } + + const expected_fmt = "\n\nExpected constructor: <green>{s}<r>\n\n" ++ received_line; + var expected_class = ZigString.Empty; + expected_value.getClassName(globalObject, &expected_class); + const fmt = signature ++ expected_fmt; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_class}); + return .zero; + } + globalObject.throw(Output.prettyFmt(fmt, true), .{expected_class}); return .zero; } |