This commit is contained in:
Jarred Sumner
2024-06-21 22:45:28 -07:00
committed by Zack Radisic
parent 38ad595e84
commit 5d4eb072a2
10 changed files with 207 additions and 58 deletions

View File

@@ -2599,10 +2599,21 @@ pub const Formatter = struct {
writer.print("{}", .{str});
},
.Event => {
const event_type = EventType.map.getWithEql(value.get(this.globalThis, "type").?.getZigString(this.globalThis), ZigString.eqlComptime) orelse EventType.unknown;
if (event_type != .MessageEvent and event_type != .ErrorEvent) {
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
}
const event_type_value = brk: {
const value_ = value.get(this.globalThis, "type") orelse break :brk JSValue.undefined;
if (value_.isString()) {
break :brk value_;
}
break :brk JSValue.undefined;
};
const event_type = switch (EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) {
.MessageEvent, .ErrorEvent => |evt| evt,
else => {
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
},
};
writer.print(
comptime Output.prettyFmt("<r><cyan>{s}<r> {{\n", enable_ansi_colors),
@@ -2628,57 +2639,68 @@ pub const Formatter = struct {
event_type.label(),
},
);
if (!single_line) {
},
}
if (value.fastGet(this.globalThis, .message)) |message_value| {
if (message_value.isString()) {
if (!this.single_line) {
this.writeIndent(Writer, writer_) catch unreachable;
}
},
writer.print(
comptime Output.prettyFmt("<r><blue>message<d>:<r> ", enable_ansi_colors),
.{},
);
const tag = Tag.getAdvanced(message_value, this.globalThis, .{ .hide_global = true });
this.format(tag, Writer, writer_, message_value, this.globalThis, enable_ansi_colors);
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
if (!this.single_line) {
writer.writeAll("\n");
}
}
}
switch (event_type) {
.MessageEvent => {
if (!this.single_line) {
this.writeIndent(Writer, writer_) catch unreachable;
}
writer.print(
comptime Output.prettyFmt("<r><blue>data<d>:<r> ", enable_ansi_colors),
.{},
);
const data = value.fastGet(this.globalThis, .data).?;
const data = value.fastGet(this.globalThis, .data) orelse JSValue.undefined;
const tag = Tag.getAdvanced(data, this.globalThis, .{ .hide_global = true });
if (tag.cell.isStringLike()) {
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
} else {
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
if (!this.single_line) {
writer.writeAll("\n");
}
},
.ErrorEvent => {
{
const error_value = value.get(this.globalThis, "error").?;
if (!error_value.isEmptyOrUndefinedOrNull()) {
writer.print(
comptime Output.prettyFmt("<r><blue>error<d>:<r> ", enable_ansi_colors),
.{},
);
const tag = Tag.getAdvanced(error_value, this.globalThis, .{ .hide_global = true });
this.format(tag, Writer, writer_, error_value, this.globalThis, enable_ansi_colors);
if (value.fastGet(this.globalThis, .@"error")) |error_value| {
if (!this.single_line) {
this.writeIndent(Writer, writer_) catch unreachable;
}
}
const message_value = value.get(this.globalThis, "message").?;
if (message_value.isString()) {
writer.print(
comptime Output.prettyFmt("<r><blue>message<d>:<r> ", enable_ansi_colors),
comptime Output.prettyFmt("<r><blue>error<d>:<r> ", enable_ansi_colors),
.{},
);
const tag = Tag.getAdvanced(message_value, this.globalThis, .{ .hide_global = true });
this.format(tag, Writer, writer_, message_value, this.globalThis, enable_ansi_colors);
const tag = Tag.getAdvanced(error_value, this.globalThis, .{ .hide_global = true });
this.format(tag, Writer, writer_, error_value, this.globalThis, enable_ansi_colors);
this.printComma(Writer, writer_, enable_ansi_colors) catch unreachable;
if (!this.single_line) {
writer.writeAll("\n");
}
}
},
else => unreachable,
}
if (!this.single_line) {
writer.writeAll("\n");
}
}
if (!this.single_line) {

View File

@@ -1001,7 +1001,7 @@ pub const ServerConfig = struct {
}
}
if (arg.getTruthy(global, "error")) |onError| {
if (arg.getTruthyComptime(global, "error")) |onError| {
if (!onError.isCallable(global.vm())) {
JSC.throwInvalidArguments("Expected error to be a function", .{}, global, exception);
if (args.ssl_config) |*conf| {
@@ -3667,7 +3667,7 @@ pub const WebSocketServer = struct {
var valid = false;
if (object.getTruthy(globalObject, "message")) |message_| {
if (object.getTruthyComptime(globalObject, "message")) |message_| {
if (!message_.isCallable(vm)) {
globalObject.throwInvalidArguments("websocket expects a function for the message option", .{});
return null;

View File

@@ -4814,9 +4814,12 @@ enum class BuiltinNamesMap : uint8_t {
path,
stream,
asyncIterator,
name,
message,
error,
};
static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigned char name)
static const JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigned char name)
{
auto& vm = globalObject->vm();
auto clientData = WebCore::clientData(vm);
@@ -4863,6 +4866,15 @@ static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigne
case BuiltinNamesMap::asyncIterator: {
return vm.propertyNames->asyncIteratorSymbol;
}
case BuiltinNamesMap::name: {
return vm.propertyNames->name;
}
case BuiltinNamesMap::message: {
return vm.propertyNames->message;
}
case BuiltinNamesMap::error: {
return vm.propertyNames->error;
}
default: {
ASSERT_NOT_REACHED();
return Identifier();

View File

@@ -4828,6 +4828,9 @@ pub const JSValue = enum(JSValueReprInt) {
path,
stream,
asyncIterator,
name,
message,
@"error",
pub fn has(property: []const u8) bool {
return bun.ComptimeEnumMap(BuiltinName).has(property);

View File

@@ -1155,7 +1155,14 @@ void WebSocket::didReceiveClose(CleanStatus wasClean, unsigned short code, WTF::
if (auto* context = scriptExecutionContext()) {
this->incPendingActivityCount();
if (wasConnecting && isConnectionError) {
dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
ErrorEvent::Init eventInit = {};
eventInit.message = makeString("WebSocket connection to '"_s, m_url.stringCenterEllipsizedToLength(), "' failed: "_s, reason);
eventInit.filename = String();
eventInit.bubbles = false;
eventInit.cancelable = false;
eventInit.colno = 0;
eventInit.error = {};
dispatchEvent(ErrorEvent::create(eventNames().errorEvent, eventInit, EventIsTrusted::Yes));
}
// https://html.spec.whatwg.org/multipage/web-sockets.html#feedback-from-the-protocol:concept-websocket-closed, we should synchronously fire a close event.
dispatchEvent(CloseEvent::create(wasClean == CleanStatus::Clean, code, reason));

View File

@@ -2344,8 +2344,8 @@ pub const Expect = struct {
if (expected_value.isEmpty() or expected_value.isUndefined()) {
const signature_no_args = comptime getSignature("toThrow", "", true);
if (result.toError()) |err| {
const name = err.get(globalThis, "name") orelse JSValue.undefined;
const message = err.get(globalThis, "message") orelse JSValue.undefined;
const name = err.getTruthyComptime(globalThis, "name") orelse JSValue.undefined;
const message = err.getTruthyComptime(globalThis, "message") orelse JSValue.undefined;
const fmt = signature_no_args ++ "\n\nError name: <red>{any}<r>\nError message: <red>{any}<r>\n";
globalThis.throwPretty(fmt, .{
name.toFmt(globalThis, &formatter),
@@ -2491,7 +2491,7 @@ pub const Expect = struct {
// If it's not an object, we are going to crash here.
assert(expected_value.isObject());
if (expected_value.get(globalThis, "message")) |expected_message| {
if (expected_value.fastGet(globalThis, .message)) |expected_message| {
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
if (_received_message) |received_message| {
@@ -4629,7 +4629,7 @@ pub const Expect = struct {
pass = pass_value.toBooleanSlow(globalThis);
if (globalThis.hasException()) return false;
if (result.get(globalThis, "message")) |message_value| {
if (result.fastGet(globalThis, .message)) |message_value| {
if (!message_value.isString() and !message_value.isCallable(globalThis.vm())) {
break :valid false;
}

View File

@@ -1101,11 +1101,14 @@ pub const JestPrettyFormat = struct {
.Error => {
var classname = ZigString.Empty;
value.getClassName(this.globalThis, &classname);
var message_string = ZigString.Empty;
if (value.get(this.globalThis, "message")) |message_prop| {
message_prop.toZigString(&message_string, this.globalThis);
var message_string = bun.String.empty;
defer message_string.deref();
if (value.fastGet(this.globalThis, .message)) |message_prop| {
message_string = message_prop.toBunString(this.globalThis);
}
if (message_string.len == 0) {
if (message_string.isEmpty()) {
writer.print("[{s}]", .{classname});
return;
}
@@ -1384,10 +1387,21 @@ pub const JestPrettyFormat = struct {
writer.print("{}", .{str});
},
.Event => {
const event_type = EventType.map.getWithEql(value.get(this.globalThis, "type").?.getZigString(this.globalThis), ZigString.eqlComptime) orelse EventType.unknown;
if (event_type != .MessageEvent and event_type != .ErrorEvent) {
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
}
const event_type_value = brk: {
const value_ = value.get(this.globalThis, "type") orelse break :brk JSValue.undefined;
if (value_.isString()) {
break :brk value_;
}
break :brk JSValue.undefined;
};
const event_type = switch (EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) {
.MessageEvent, .ErrorEvent => |evt| evt,
else => {
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
},
};
writer.print(
comptime Output.prettyFmt("<r><cyan>{s}<r> {{\n", enable_ansi_colors),
@@ -1409,35 +1423,53 @@ pub const JestPrettyFormat = struct {
event_type.label(),
},
);
this.writeIndent(Writer, writer_) catch unreachable;
if (value.fastGet(this.globalThis, .message)) |message_value| {
if (message_value.isString()) {
this.writeIndent(Writer, writer_) catch unreachable;
writer.print(
comptime Output.prettyFmt("<r><blue>message<d>:<r> ", enable_ansi_colors),
.{},
);
const tag = Tag.get(message_value, this.globalThis);
this.format(tag, Writer, writer_, message_value, this.globalThis, enable_ansi_colors);
writer.writeAll(", \n");
}
}
switch (event_type) {
.MessageEvent => {
this.writeIndent(Writer, writer_) catch unreachable;
writer.print(
comptime Output.prettyFmt("<r><blue>data<d>:<r> ", enable_ansi_colors),
.{},
);
const data = value.fastGet(this.globalThis, .data).?;
const data = value.fastGet(this.globalThis, .data) orelse JSValue.undefined;
const tag = Tag.get(data, this.globalThis);
if (tag.cell.isStringLike()) {
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
} else {
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
}
writer.writeAll(", \n");
},
.ErrorEvent => {
writer.print(
comptime Output.prettyFmt("<r><blue>error<d>:<r>\n", enable_ansi_colors),
.{},
);
if (value.fastGet(this.globalThis, .@"error")) |data| {
this.writeIndent(Writer, writer_) catch unreachable;
writer.print(
comptime Output.prettyFmt("<r><blue>error<d>:<r> ", enable_ansi_colors),
.{},
);
const data = value.get(this.globalThis, "error").?;
const tag = Tag.get(data, this.globalThis);
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
const tag = Tag.get(data, this.globalThis);
this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
writer.writeAll("\n");
}
},
else => unreachable,
}
writer.writeAll("\n");
}
this.writeIndent(Writer, writer_) catch unreachable;

View File

@@ -174,7 +174,32 @@ it("MessageEvent", () => {
expect(Bun.inspect(new MessageEvent("message", { data: 123 }))).toBe(
`MessageEvent {
type: "message",
data: 123
data: 123,
}`,
);
});
it("MessageEvent with no data set", () => {
expect(Bun.inspect(new MessageEvent("message"))).toBe(
`MessageEvent {
type: "message",
data: null,
}`,
);
});
it("MessageEvent with deleted data", () => {
const event = new MessageEvent("message");
Object.defineProperty(event, "data", {
value: 123,
writable: true,
configurable: true,
});
delete event.data;
expect(Bun.inspect(event)).toBe(
`MessageEvent {
type: "message",
data: null,
}`,
);
});

View File

@@ -0,0 +1,29 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP
exports[`WebSocket error event snapshot: Snapshot snapshot 1`] = `ErrorEvent {
type: "error",
message: "WebSocket connection to 'ws://127.0.0.1:8080/' failed: Failed to connect",
error: null
}`;
exports[`WebSocket error event snapshot: Inspect snapshot 1`] = `
"ErrorEvent {
type: "error",
message: "WebSocket connection to 'ws://127.0.0.1:8080/' failed: Failed to connect",
error: null,
}"
`;
exports[`ErrorEvent with no message: Inspect snapshot 1`] = `
"ErrorEvent {
type: "error",
message: "",
error: null,
}"
`;
exports[`ErrorEvent with no message: Snapshot snapshot 1`] = `ErrorEvent {
type: "error",
message: "",
error: null
}`;

View File

@@ -0,0 +1,19 @@
import { test, expect } from "bun:test";
test("WebSocket error event snapshot", async () => {
const ws = new WebSocket("ws://127.0.0.1:8080");
const { promise, resolve } = Promise.withResolvers();
ws.onerror = error => {
resolve(error);
};
const error = await promise;
expect(error).toMatchSnapshot("Snapshot snapshot");
expect(Bun.inspect(error)).toMatchSnapshot("Inspect snapshot");
});
test("ErrorEvent with no message", async () => {
const error = new ErrorEvent("error");
expect(error.message).toBe("");
expect(Bun.inspect(error)).toMatchSnapshot("Inspect snapshot");
expect(error).toMatchSnapshot("Snapshot snapshot");
});