mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 11:29:02 +00:00
committed by
Zack Radisic
parent
38ad595e84
commit
5d4eb072a2
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}`,
|
||||
);
|
||||
});
|
||||
|
||||
29
test/js/web/websocket/__snapshots__/error-event.test.ts.snap
Normal file
29
test/js/web/websocket/__snapshots__/error-event.test.ts.snap
Normal 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
|
||||
}`;
|
||||
19
test/js/web/websocket/error-event.test.ts
Normal file
19
test/js/web/websocket/error-event.test.ts
Normal 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");
|
||||
});
|
||||
Reference in New Issue
Block a user