diff options
author | 2023-05-26 16:28:09 -0700 | |
---|---|---|
committer | 2023-05-26 16:28:09 -0700 | |
commit | 3a0735e16470cde796c1420f4db982af61bdab9c (patch) | |
tree | fd16595c122f085c5f18465f6db130e7f219dfbf | |
parent | 42125b43513a76e3e18b2b76ecc4323b1d8e1b3a (diff) | |
download | bun-3a0735e16470cde796c1420f4db982af61bdab9c.tar.gz bun-3a0735e16470cde796c1420f4db982af61bdab9c.tar.zst bun-3a0735e16470cde796c1420f4db982af61bdab9c.zip |
Pretty formatter for `Headers` & `URLSearchParams` (#3081)
* Pretty formatter for `Headers` & `URLSearchParams`
* cleanup
* console.log on Headers, FormData, URLSearchParams will always quote the keys now
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/bun.js/bindings/JSDOMWrapper.h | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 75 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSFetchHeaders.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSURLSearchParams.cpp | 58 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSURLSearchParams.h | 2 | ||||
-rw-r--r-- | src/bun.js/webcore/body.zig | 4 | ||||
-rw-r--r-- | src/bun.js/webcore/request.zig | 14 | ||||
-rw-r--r-- | src/bun.js/webcore/response.zig | 16 | ||||
-rw-r--r-- | test/js/bun/util/inspect.test.js | 5 | ||||
-rw-r--r-- | test/js/web/fetch/headers.test.ts | 41 | ||||
-rw-r--r-- | test/js/web/html/FormData.test.ts | 24 | ||||
-rw-r--r-- | test/js/web/html/URLSearchParams.test.ts | 63 | ||||
-rw-r--r-- | test/js/web/url/url.test.ts | 32 |
14 files changed, 279 insertions, 62 deletions
diff --git a/src/bun.js/bindings/JSDOMWrapper.h b/src/bun.js/bindings/JSDOMWrapper.h index 726b2e8bf..f80585e72 100644 --- a/src/bun.js/bindings/JSDOMWrapper.h +++ b/src/bun.js/bindings/JSDOMWrapper.h @@ -58,6 +58,7 @@ static const uint8_t JSCommentNodeType = JSNodeType | NodeConstants::COMMENT_NOD static const uint8_t JSCDATASectionNodeType = JSNodeType | NodeConstants::CDATA_SECTION_NODE; static const uint8_t JSAttrNodeType = JSNodeType | NodeConstants::ATTRIBUTE_NODE; static const uint8_t JSElementType = 0b11110000 | NodeConstants::ELEMENT_NODE; +static const uint8_t JSAsJSONType = JSElementType; static_assert(JSDOMWrapperType > JSC::LastJSCObjectType, "JSC::JSType offers the highest bit."); static_assert(NodeConstants::LastNodeType <= JSNodeTypeMask, "NodeType should be represented in 4bit."); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 42f0c5344..3bf2b9172 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -2980,6 +2980,10 @@ pub const JSValue = enum(JSValueReprInt) { Event = 0b11101111, DOMWrapper = 0b11101110, Blob = 0b11111100, + + /// This means that we don't have Zig bindings for the type yet, but it + /// implements .toJSON() + JSAsJSONType = 0b11110000 | 1, _, pub fn canGet(this: JSType) bool { diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index bfea93306..37fc7dabc 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -1182,6 +1182,7 @@ pub const ZigConsoleClient = struct { depth: u16 = 0, max_depth: u16 = 8, quote_strings: bool = false, + quote_keys: bool = false, failed: bool = false, estimated_line_length: usize = 0, always_newline_scope: bool = false, @@ -1266,6 +1267,7 @@ pub const ZigConsoleClient = struct { Promise, JSON, + toJSON, NativeCode, ArrayBuffer, @@ -1485,6 +1487,8 @@ pub const ZigConsoleClient = struct { .GetterSetter, .CustomGetterSetter => .Getter, + .JSAsJSONType => .toJSON, + else => .JSON, }, .cell = js_type, @@ -1774,14 +1778,14 @@ pub const ZigConsoleClient = struct { if (!is_symbol) { // TODO: make this one pass? - if (!key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice())) { + if (!key.is16Bit() and (!this.quote_keys and JSLexer.isLatin1Identifier(@TypeOf(key.slice()), key.slice()))) { this.addForNewLine(key.len + 1); writer.print( comptime Output.prettyFmt("<r>{}<d>:<r> ", enable_ansi_colors), .{key}, ); - } else if (key.is16Bit() and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned())) { + } else if (key.is16Bit() and (!this.quote_keys and JSLexer.isLatin1Identifier(@TypeOf(key.utf16SliceAligned()), key.utf16SliceAligned()))) { this.addForNewLine(key.len + 1); writer.print( @@ -2156,20 +2160,41 @@ pub const ZigConsoleClient = struct { } else if (value.as(JSC.WebCore.Blob)) |blob| { blob.writeFormat(ZigConsoleClient.Formatter, this, writer_, enable_ansi_colors) catch {}; return; + } else if (value.as(JSC.FetchHeaders) != null) { + if (value.get(this.globalThis, "toJSON")) |toJSONFunction| { + this.addForNewLine("Headers ".len); + writer.writeAll(comptime Output.prettyFmt("<r>Headers ", enable_ansi_colors)); + const prev_quote_keys = this.quote_keys; + this.quote_keys = true; + defer this.quote_keys = prev_quote_keys; + + return this.printAs( + .Object, + Writer, + writer_, + toJSONFunction.callWithThis(this.globalThis, value, &.{}), + .Object, + enable_ansi_colors, + ); + } } else if (value.as(JSC.DOMFormData) != null) { - const toJSONFunction = value.get(this.globalThis, "toJSON").?; - - this.addForNewLine("FormData (entries) ".len); - writer.writeAll(comptime Output.prettyFmt("<r><blue>FormData<r> <d>(entries)<r> ", enable_ansi_colors)); - - return this.printAs( - .Object, - Writer, - writer_, - toJSONFunction.callWithThis(this.globalThis, value, &.{}), - .Object, - enable_ansi_colors, - ); + if (value.get(this.globalThis, "toJSON")) |toJSONFunction| { + const prev_quote_keys = this.quote_keys; + this.quote_keys = true; + defer this.quote_keys = prev_quote_keys; + + return this.printAs( + .Object, + Writer, + writer_, + toJSONFunction.callWithThis(this.globalThis, value, &.{}), + .Object, + enable_ansi_colors, + ); + } + + // this case should never happen + return this.printAs(.Undefined, Writer, writer_, .undefined, .Cell, enable_ansi_colors); } else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| { this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0)))); if (timer.kind == .setInterval) { @@ -2317,6 +2342,20 @@ pub const ZigConsoleClient = struct { this.writeIndent(Writer, writer_) catch {}; writer.writeAll("}"); }, + .toJSON => { + if (value.get(this.globalThis, "toJSON")) |func| { + const result = func.callWithThis(this.globalThis, value, &.{}); + if (result.toError() == null) { + const prev_quote_keys = this.quote_keys; + this.quote_keys = true; + defer this.quote_keys = prev_quote_keys; + this.printAs(.Object, Writer, writer_, result, value.jsType(), enable_ansi_colors); + return; + } + } + + writer.writeAll("{}"); + }, .JSON => { var str = ZigString.init(""); value.jsonStringify(this.globalThis, this.indent, &str); @@ -2844,7 +2883,13 @@ pub const ZigConsoleClient = struct { .GlobalObject => this.printAs(.GlobalObject, Writer, writer, value, result.cell, enable_ansi_colors), .Private => this.printAs(.Private, Writer, writer, value, result.cell, enable_ansi_colors), .Promise => this.printAs(.Promise, Writer, writer, value, result.cell, enable_ansi_colors), + + // Call JSON.stringify on the value .JSON => this.printAs(.JSON, Writer, writer, value, result.cell, enable_ansi_colors), + + // Call value.toJSON() and print as an object + .toJSON => this.printAs(.toJSON, Writer, writer, value, result.cell, enable_ansi_colors), + .NativeCode => this.printAs(.NativeCode, Writer, writer, value, result.cell, enable_ansi_colors), .JSX => this.printAs(.JSX, Writer, writer, value, result.cell, enable_ansi_colors), .Event => this.printAs(.Event, Writer, writer, value, result.cell, enable_ansi_colors), diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp index 2e75cdc86..6bea5dc84 100644 --- a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp +++ b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp @@ -301,7 +301,7 @@ static const HashTableValue JSFetchHeadersPrototypeTableValues[] = { { "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_values, 0 } }, { "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_forEach, 1 } }, { "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_toJSON, 0 } }, - { "count"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsFetchHeadersGetterCount, 0 } }, + { "count"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsFetchHeadersGetterCount, 0 } }, // { "getSetCookie"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_getSetCookie, 0 } }, }; diff --git a/src/bun.js/bindings/webcore/JSURLSearchParams.cpp b/src/bun.js/bindings/webcore/JSURLSearchParams.cpp index d80304684..a99587d40 100644 --- a/src/bun.js/bindings/webcore/JSURLSearchParams.cpp +++ b/src/bun.js/bindings/webcore/JSURLSearchParams.cpp @@ -56,6 +56,7 @@ #include "wtf/URL.h" #include "wtf/Vector.h" #include <variant> +#include "GCDefferalContext.h" namespace WebCore { using namespace JSC; @@ -69,6 +70,7 @@ static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_getAll); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_has); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_set); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_sort); +static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_toJSON); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_entries); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_keys); static JSC_DECLARE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_values); @@ -178,6 +180,7 @@ static const HashTableValue JSURLSearchParamsPrototypeTableValues[] = { { "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_values, 0 } }, { "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_forEach, 1 } }, { "toString"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_toString, 0 } }, + { "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsURLSearchParamsPrototypeFunction_toJSON, 0 } }, { "length"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsURLSearchParamsPrototype_getLength, 0 } }, }; @@ -393,6 +396,60 @@ JSC_DEFINE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_toString, (JSGlobalO return IDLOperation<JSURLSearchParams>::call<jsURLSearchParamsPrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString"); } +static inline JSC::EncodedJSValue jsURLSearchParamsPrototypeFunction_toJSONBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSURLSearchParams>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + auto iter = impl.createIterator(); + + auto* obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), impl.size() + 1); + obj->putDirect(vm, vm.propertyNames->toStringTagSymbol, jsNontrivialString(lexicalGlobalObject->vm(), "URLSearchParams"_s), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | 0); + + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + WTF::HashSet<String> seenKeys; + for (auto entry = iter.next(); entry.has_value(); entry = iter.next()) { + auto& key = entry.value().key; + auto& value = entry.value().value; + auto ident = Identifier::fromString(vm, key); + if (seenKeys.contains(key)) { + JSValue jsValue = obj->getDirect(vm, ident); + if (jsValue.isString()) { + GCDeferralContext deferralContext(lexicalGlobalObject->vm()); + JSC::ObjectInitializationScope initializationScope(lexicalGlobalObject->vm()); + + JSC::JSArray* array = JSC::JSArray::tryCreateUninitializedRestricted( + initializationScope, &deferralContext, + lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), + 2); + + array->initializeIndex(initializationScope, 0, jsValue); + array->initializeIndex(initializationScope, 1, jsString(vm, value)); + obj->putDirect(vm, ident, array, 0); + } else if (jsValue.isObject() && jsValue.getObject()->inherits<JSC::JSArray>()) { + JSC::JSArray* array = jsCast<JSC::JSArray*>(jsValue.getObject()); + array->push(lexicalGlobalObject, jsString(vm, value)); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + } else { + RELEASE_ASSERT_NOT_REACHED(); + } + } else { + seenKeys.add(key); + obj->putDirect(vm, ident, jsString(vm, value), 0); + } + } + + RELEASE_AND_RETURN(throwScope, JSValue::encode(obj)); +} + +JSC_DEFINE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_toJSON, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSURLSearchParams>::call<jsURLSearchParamsPrototypeFunction_toJSONBody>(*lexicalGlobalObject, *callFrame, "toJSON"); +} + struct URLSearchParamsIteratorTraits { static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map; using KeyType = IDLUSVString; @@ -566,5 +623,4 @@ URLSearchParams* JSURLSearchParams::toWrapped(JSC::VM& vm, JSC::JSValue value) return &wrapper->wrapped(); return nullptr; } - } diff --git a/src/bun.js/bindings/webcore/JSURLSearchParams.h b/src/bun.js/bindings/webcore/JSURLSearchParams.h index b7e90a3fb..54abe1237 100644 --- a/src/bun.js/bindings/webcore/JSURLSearchParams.h +++ b/src/bun.js/bindings/webcore/JSURLSearchParams.h @@ -45,7 +45,7 @@ public: static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray); + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::JSType(WebCore::JSAsJSONType), StructureFlags), info(), JSC::NonArray); } static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index b39fb1761..9cfa1f9ce 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -79,7 +79,7 @@ pub const Body = struct { const Writer = @TypeOf(writer); try formatter.writeIndent(Writer, writer); - try writer.writeAll("bodyUsed: "); + try writer.writeAll(comptime Output.prettyFmt("<r>bodyUsed<d>:<r> ", enable_ansi_colors)); formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.value == .Used), .BooleanObject, enable_ansi_colors); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); @@ -92,7 +92,7 @@ pub const Body = struct { // } try formatter.writeIndent(Writer, writer); - try writer.writeAll("status: "); + try writer.writeAll(comptime Output.prettyFmt("<r>status<d>:<r> ", enable_ansi_colors)); formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors); if (this.value == .Blob) { try formatter.printComma(Writer, writer, enable_ansi_colors); diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index 248b6a1bf..643fbb38b 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -133,18 +133,24 @@ pub const Request = struct { defer formatter.indent -|= 1; try formatter.writeIndent(Writer, writer); - try writer.writeAll("method: \""); + try writer.writeAll(comptime Output.prettyFmt("<r>method<d>:<r> \"", enable_ansi_colors)); + try writer.writeAll(bun.asByteSlice(@tagName(this.method))); try writer.writeAll("\""); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); - try writer.writeAll("url: \""); + try writer.writeAll(comptime Output.prettyFmt("<r>url<d>:<r> ", enable_ansi_colors)); try this.ensureURL(); - try writer.print(comptime Output.prettyFmt("<r><b>{s}<r>", enable_ansi_colors), .{this.url}); + try writer.print(comptime Output.prettyFmt("\"<b>{s}<r>\"", enable_ansi_colors), .{this.url}); + formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + try writer.writeAll("\n"); + + try formatter.writeIndent(Writer, writer); + try writer.writeAll(comptime Output.prettyFmt("<r>headers<d>:<r> ", enable_ansi_colors)); + formatter.printAs(.Private, Writer, writer, this.getHeaders(formatter.globalThis), .DOMWrapper, enable_ansi_colors); - try writer.writeAll("\""); if (this.body.value == .Blob) { try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index af69b8f95..8d1bfb961 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -120,7 +120,7 @@ pub const Response = struct { pub const Props = struct {}; - pub fn writeFormat(this: *const Response, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { + pub fn writeFormat(this: *Response, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { const Writer = @TypeOf(writer); try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())}); { @@ -128,26 +128,32 @@ pub const Response = struct { defer formatter.indent -|= 1; try formatter.writeIndent(Writer, writer); - try writer.writeAll("ok: "); + try writer.writeAll(comptime Output.prettyFmt("<r>ok<d>:<r> ", enable_ansi_colors)); formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.isOK()), .BooleanObject, enable_ansi_colors); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); - try writer.writeAll("url: \""); + try writer.writeAll(comptime Output.prettyFmt("<r>url<d>:<r> \"", enable_ansi_colors)); try writer.print(comptime Output.prettyFmt("<r><b>{s}<r>", enable_ansi_colors), .{this.url}); try writer.writeAll("\""); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); - try writer.writeAll("statusText: "); + try writer.writeAll(comptime Output.prettyFmt("<r>headers<d>:<r> ", enable_ansi_colors)); + formatter.printAs(.Private, Writer, writer, this.getHeaders(formatter.globalThis), .DOMWrapper, enable_ansi_colors); + formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; + try writer.writeAll("\n"); + + try formatter.writeIndent(Writer, writer); + try writer.writeAll(comptime Output.prettyFmt("<r>statusText<d>:<r> ", enable_ansi_colors)); try JSPrinter.writeJSONString(this.status_text, Writer, writer, .ascii); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); try formatter.writeIndent(Writer, writer); - try writer.writeAll("redirected: "); + try writer.writeAll(comptime Output.prettyFmt("<r>redirected<d>:<r> ", enable_ansi_colors)); formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.redirected), .BooleanObject, enable_ansi_colors); formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; try writer.writeAll("\n"); diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 5c67999fe..e91204c69 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -54,6 +54,7 @@ it("Blob inspect", () => { expect(Bun.inspect(new Response(new Blob()))).toBe(`Response (0 KB) { ok: true, url: "", + headers: Headers {}, statusText: "", redirected: false, bodyUsed: false, @@ -63,6 +64,7 @@ it("Blob inspect", () => { expect(Bun.inspect(new Response("Hello"))).toBe(`Response (5 bytes) { ok: true, url: "", + headers: Headers {}, statusText: "", redirected: false, bodyUsed: false, @@ -105,7 +107,8 @@ it("Request object", () => { ` Request (0 KB) { method: "GET", - url: "https://example.com" + url: "https://example.com", + headers: Headers {} }`.trim(), ); }); diff --git a/test/js/web/fetch/headers.test.ts b/test/js/web/fetch/headers.test.ts index 48cd3ad35..8ae17c11e 100644 --- a/test/js/web/fetch/headers.test.ts +++ b/test/js/web/fetch/headers.test.ts @@ -412,10 +412,46 @@ describe("Headers", () => { ]); }); }); - describe("toJSON()", () => { + describe("Bun.inspect()", () => { const it = "toJSON" in new Headers() ? test : test.skip; it("can convert to json when empty", () => { const headers = new Headers(); + expect(Bun.inspect(headers)).toStrictEqual(`Headers {}`); + }); + it("can convert to json", () => { + const headers = new Headers({ + "cache-control": "public, immutable", + }); + expect(Bun.inspect(headers)).toStrictEqual( + "Headers {" + "\n " + `"cache-control": "public, immutable"` + "\n" + "}", + ); + }); + it("can convert to json normalized", () => { + const headers = new Headers({ + "user-agent": "bun", + "X-Custom-Header": "1", + "cache-control": "public, immutable", + }); + expect(Bun.inspect(headers)).toStrictEqual( + "Headers " + + JSON.stringify( + { + "user-agent": "bun", + "cache-control": "public, immutable", + "x-custom-header": "1", + }, + null, + 2, + ), + ); + }); + }); + describe("toJSON()", () => { + // @ts-ignore + const it = new Headers()?.toJSON ? test : test.skip; + + it("can convert to json when empty", () => { + const headers = new Headers(); expect(headers.toJSON()).toStrictEqual({}); }); it("can convert to json", () => { @@ -440,7 +476,8 @@ describe("Headers", () => { }); }); describe("count", () => { - const it = "count" in new Headers() ? test : test.skip; + // @ts-ignore + const it = typeof new Headers()?.count !== "undefined" ? test : test.skip; it("can count headers when empty", () => { const headers = new Headers(); expect(headers.count).toBe(0); diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts index af2871b10..abb298c1a 100644 --- a/test/js/web/html/FormData.test.ts +++ b/test/js/web/html/FormData.test.ts @@ -338,6 +338,30 @@ describe("FormData", () => { expect(Bun.inspect(formData).length > 0).toBe(true); }); + describe("non-standard extensions", () => { + it("should support .length", () => { + const formData = new FormData(); + formData.append("foo", "bar"); + formData.append("foo", new Blob(["bar"])); + formData.append("bar", "baz"); + // @ts-ignore + expect(formData.length).toBe(3); + formData.delete("foo"); + // @ts-ignore + expect(formData.length).toBe(1); + formData.append("foo", "bar"); + // @ts-ignore + expect(formData.length).toBe(2); + formData.delete("foo"); + formData.delete("foo"); + // @ts-ignore + expect(formData.length).toBe(1); + formData.delete("bar"); + // @ts-ignore + expect(formData.length).toBe(0); + }); + }); + describe("URLEncoded", () => { test("should parse URL encoded", async () => { const response = new Response("foo=bar&baz=qux", { diff --git a/test/js/web/html/URLSearchParams.test.ts b/test/js/web/html/URLSearchParams.test.ts new file mode 100644 index 000000000..028590971 --- /dev/null +++ b/test/js/web/html/URLSearchParams.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from "bun:test"; + +describe("URLSearchParams", () => { + describe("non-standard extensions", () => { + it("should support .length", () => { + const params = new URLSearchParams(); + params.append("foo", "bar"); + params.append("foo", "boop"); + params.append("bar", "baz"); + expect(params.length).toBe(3); + params.delete("foo"); + expect(params.length).toBe(1); + params.append("foo", "bar"); + expect(params.length).toBe(2); + params.delete("foo"); + params.delete("foo"); + expect(params.length).toBe(1); + params.delete("bar"); + expect(params.length).toBe(0); + }); + + it("should support .toJSON", () => { + const params = new URLSearchParams(); + params.append("foo", "bar"); + params.append("foo", "boop"); + params.append("bar", "baz"); + // @ts-ignore + expect(params.toJSON()).toEqual({ + foo: ["bar", "boop"], + bar: "baz", + }); + expect(JSON.parse(JSON.stringify(params))).toEqual({ + foo: ["bar", "boop"], + bar: "baz", + }); + expect(Bun.inspect(params)).toBe( + "URLSearchParams {" + "\n" + ' foo: [ "bar", "boop" ],' + "\n" + ' bar: "baz"' + "\n" + "}", + ); + params.delete("foo"); + // @ts-ignore + expect(params.toJSON()).toEqual({ + bar: "baz", + }); + params.append("foo", "bar"); + // @ts-ignore + expect(params.toJSON()).toEqual({ + foo: "bar", + bar: "baz", + }); + params.delete("foo"); + params.delete("foo"); + // @ts-ignore + expect(params.toJSON()).toEqual({ + bar: "baz", + }); + params.delete("bar"); + // @ts-ignore + expect(params.toJSON()).toEqual({}); + + expect(JSON.stringify(params)).toBe("{}"); + }); + }); +}); diff --git a/test/js/web/url/url.test.ts b/test/js/web/url/url.test.ts index 4e71c44d7..6ad691c1b 100644 --- a/test/js/web/url/url.test.ts +++ b/test/js/web/url/url.test.ts @@ -73,21 +73,7 @@ describe("url", () => { pathname: "/", hash: "", search: "", - searchParams: URLSearchParams { - append: [Function: append], - delete: [Function: delete], - get: [Function: get], - getAll: [Function: getAll], - has: [Function: has], - set: [Function: set], - sort: [Function: sort], - entries: [Function: entries], - keys: [Function: keys], - values: [Function: values], - forEach: [Function: forEach], - toString: [Function: toString], - [Symbol(Symbol.iterator)]: [Function: entries] - }, + searchParams: ${Bun.inspect(new URLSearchParams())}, toJSON: [Function: toJSON], toString: [Function: toString] }`); @@ -108,21 +94,7 @@ describe("url", () => { pathname: "/oven-sh/bun/issues/135", hash: "", search: "?hello%20i%20have%20spaces%20thank%20you%20good%20night", - searchParams: URLSearchParams { - append: [Function: append], - delete: [Function: delete], - get: [Function: get], - getAll: [Function: getAll], - has: [Function: has], - set: [Function: set], - sort: [Function: sort], - entries: [Function: entries], - keys: [Function: keys], - values: [Function: values], - forEach: [Function: forEach], - toString: [Function: toString], - [Symbol(Symbol.iterator)]: [Function: entries] - }, + searchParams: URLSearchParams {\n \"hello i have spaces thank you good night\": \"\"\n }, toJSON: [Function: toJSON], toString: [Function: toString] }`); |