aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
Diffstat (limited to 'test/js')
-rw-r--r--test/js/node/events/event-emitter.test.ts529
-rw-r--r--test/js/node/events/node-builtins.test.js18
2 files changed, 96 insertions, 451 deletions
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index cef309d48..401ccf605 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -1,106 +1,34 @@
-import { test, describe, expect } from "bun:test";
-import { sleep } from "bun";
-
+import { test, describe, expect, it } from "bun:test";
+import { heapStats } from "bun:jsc";
+import { expectMaxObjectTypeCount, gc } from "harness";
// this is also testing that imports with default and named imports in the same statement work
// our transpiler transform changes this to a var with import.meta.require
import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events";
-describe("node:events", () => {
- test("captureRejectionSymbol", () => {
+describe("EventEmitter", () => {
+ it("captureRejectionSymbol", () => {
expect(EventEmitter.captureRejectionSymbol).toBeDefined();
expect(captureRejectionSymbol).toBeDefined();
- expect(captureRejectionSymbol).toBe(EventEmitter.captureRejectionSymbol);
- });
-
- test("once", done => {
- const emitter = new EventEmitter();
- EventEmitter.once(emitter, "hey").then(x => {
- try {
- expect(x).toEqual([1, 5]);
- } catch (error) {
- done(error);
- }
- done();
- });
- emitter.emit("hey", 1, 5);
});
-
- test("once (abort)", done => {
- const emitter = new EventEmitter();
- const controller = new AbortController();
- EventEmitter.once(emitter, "hey", { signal: controller.signal })
- .then(() => done(new Error("Should not be called")))
- .catch(() => done());
- controller.abort();
- });
-
- test("once (two events in same tick)", done => {
- const emitter = new EventEmitter();
- EventEmitter.once(emitter, "hey").then(() => {
- EventEmitter.once(emitter, "hey").then(data => {
- try {
- expect(data).toEqual([3]);
- } catch (error) {
- done(error);
- }
- done();
- });
- setTimeout(() => {
- emitter.emit("hey", 3);
- }, 10);
- });
- emitter.emit("hey", 1);
- emitter.emit("hey", 2);
- });
-
- // TODO: extensive events.on tests
- // test("on", () => {
- // const emitter = new EventEmitter();
- // const asyncIterator = EventEmitter.on(emitter, "hey");
-
- // expect(asyncIterator.next).toBeDefined();
- // expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
-
- // const fn = async () => {
- // const { value } = await asyncIterator.next();
- // expect(value).toBe(1);
- // };
-
- // emitter.emit("hey", 1, 2, 3);
- // });
-});
-
-describe("EventEmitter", () => {
test("getEventListeners", () => {
expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0);
});
-
- test("constructor", () => {
+ test("EventEmitter constructor", () => {
var emitter = new EventEmitter();
emitter.setMaxListeners(100);
expect(emitter.getMaxListeners()).toBe(100);
});
- test("removeAllListeners()", () => {
- var emitter = new EventEmitter() as any;
+ test("EventEmitter.removeAllListeners()", () => {
+ var emitter = new EventEmitter();
var ran = false;
emitter.on("hey", () => {
ran = true;
});
- emitter.on("hey", () => {
- ran = true;
- });
- emitter.on("exit", () => {
- ran = true;
- });
- const { _events } = emitter;
emitter.removeAllListeners();
expect(emitter.listenerCount("hey")).toBe(0);
- expect(emitter.listenerCount("exit")).toBe(0);
emitter.emit("hey");
- emitter.emit("exit");
expect(ran).toBe(false);
- expect(_events).not.toBe(emitter._events); // This looks wrong but node.js replaces it too
emitter.on("hey", () => {
ran = true;
});
@@ -109,366 +37,42 @@ describe("EventEmitter", () => {
expect(emitter.listenerCount("hey")).toBe(1);
});
- test("removeAllListeners(type)", () => {
- var emitter = new EventEmitter();
- var ran = false;
- emitter.on("hey", () => {
- ran = true;
- });
- emitter.on("exit", () => {
- ran = true;
- });
- expect(emitter.listenerCount("hey")).toBe(1);
- emitter.removeAllListeners("hey");
- expect(emitter.listenerCount("hey")).toBe(0);
- expect(emitter.listenerCount("exit")).toBe(1);
- emitter.emit("hey");
- expect(ran).toBe(false);
- emitter.emit("exit");
- expect(ran).toBe(true);
- });
-
// These are also tests for the done() function in the test runner.
- describe("emit", () => {
- test("different tick", done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- queueMicrotask(() => {
- emitter.emit("wow");
- });
- });
-
- // Unlike Jest, bun supports async and done
- test("async microtask before", done => {
- (async () => {
- await 1;
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- emitter.emit("wow");
- })();
- });
-
- test("async microtask after", done => {
- (async () => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- await 1;
- emitter.emit("wow");
- })();
- });
-
- test("same tick", done => {
- var emitter = new EventEmitter();
-
- emitter.on("wow", () => done());
-
+ test("EventEmitter emit (different tick)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ queueMicrotask(() => {
emitter.emit("wow");
});
-
- test("setTimeout task", done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- setTimeout(() => emitter.emit("wow"), 1);
- });
- });
-
- test("addListener return type", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.addListener("foo", () => {})).toBe(myEmitter);
- });
-
- test("addListener validates function", () => {
- var myEmitter = new EventEmitter();
- expect(() => myEmitter.addListener("foo", {} as any)).toThrow();
- });
-
- test("removeListener return type", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.removeListener("foo", () => {})).toBe(myEmitter);
- });
-
- test("once", () => {
- var myEmitter = new EventEmitter();
- var calls = 0;
-
- const fn = () => {
- calls++;
- };
-
- myEmitter.once("foo", fn);
-
- expect(myEmitter.listenerCount("foo")).toBe(1);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
-
- myEmitter.emit("foo");
- myEmitter.emit("foo");
-
- expect(calls).toBe(1);
- expect(myEmitter.listenerCount("foo")).toBe(0);
- });
-
- test("addListener/removeListener aliases", () => {
- expect(EventEmitter.prototype.addListener).toBe(EventEmitter.prototype.on);
- expect(EventEmitter.prototype.removeListener).toBe(EventEmitter.prototype.off);
});
- test("prependListener", () => {
- const myEmitter = new EventEmitter();
- const order: number[] = [];
-
- myEmitter.on("foo", () => {
- order.push(1);
- });
-
- myEmitter.prependListener("foo", () => {
- order.push(2);
- });
-
- myEmitter.prependListener("foo", () => {
- order.push(3);
- });
-
- myEmitter.on("foo", () => {
- order.push(4);
- });
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4]);
- });
-
- test("prependOnceListener", () => {
- const myEmitter = new EventEmitter();
- const order: number[] = [];
-
- myEmitter.on("foo", () => {
- order.push(1);
- });
-
- myEmitter.prependOnceListener("foo", () => {
- order.push(2);
- });
- myEmitter.prependOnceListener("foo", () => {
- order.push(3);
- });
-
- myEmitter.on("foo", () => {
- order.push(4);
- });
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4]);
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4, 1, 4]);
- });
-
- test("listeners", () => {
- const myEmitter = new EventEmitter();
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- const fn2 = () => {};
- myEmitter.on("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
- myEmitter.off("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- });
-
- test("rawListeners", () => {
- const myEmitter = new EventEmitter();
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- const fn2 = () => {};
- myEmitter.on("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
- myEmitter.off("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- });
-
- test("eventNames", () => {
- const myEmitter = new EventEmitter();
- expect(myEmitter.eventNames()).toEqual([]);
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.eventNames()).toEqual(["foo"]);
- myEmitter.on("bar", () => {});
- expect(myEmitter.eventNames()).toEqual(["foo", "bar"]);
- myEmitter.off("foo", fn);
- expect(myEmitter.eventNames()).toEqual(["bar"]);
- });
-
- test("_eventsCount", () => {
- const myEmitter = new EventEmitter() as EventEmitter & {
- _eventsCount: number;
- };
- expect(myEmitter._eventsCount).toBe(0);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(1);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(1);
- myEmitter.on("bar", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.on("bar", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.removeAllListeners("foo");
- expect(myEmitter._eventsCount).toBe(1);
- });
-
- test("events.init", () => {
- // init is a undocumented property that is identical to the constructor except it doesn't return the instance
- // in node, EventEmitter just calls init()
- let instance = Object.create(EventEmitter.prototype);
- (EventEmitter as any).init.call(instance);
- expect(instance._eventsCount).toBe(0);
- expect(instance._maxListeners).toBeUndefined();
- expect(instance._events).toEqual({});
- expect(instance instanceof EventEmitter).toBe(true);
- });
-});
-
-describe("EventEmitter error handling", () => {
- test("unhandled error event throws on emit", () => {
- const myEmitter = new EventEmitter();
-
- expect(() => {
- myEmitter.emit("error", "Hello!");
- }).toThrow("Hello!");
- });
-
- test("unhandled error event throws on emit with no arguments", () => {
- const myEmitter = new EventEmitter();
-
- expect(() => {
- myEmitter.emit("error");
- }).toThrow("Unhandled error.");
- });
-
- test("handled error event", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on("error", (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- myEmitter.emit("error", "Hello", "World");
-
- expect(handled).toBe(true);
- });
-
- test("errorMonitor", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on(EventEmitter.errorMonitor, (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- myEmitter.on("error", () => {});
-
- myEmitter.emit("error", "Hello", "World");
-
- expect(handled).toBe(true);
- });
-
- test("errorMonitor (unhandled)", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on(EventEmitter.errorMonitor, (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- expect(() => {
- myEmitter.emit("error", "Hello", "World");
- }).toThrow("Hello");
-
- expect(handled).toBe(true);
+ // Unlike Jest, bun supports async and done
+ test("async EventEmitter emit (microtask)", async done => {
+ await 1;
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ emitter.emit("wow");
});
-});
-
-describe("EventEmitter captureRejections", () => {
- // Can't catch the unhandled rejection because we do not have process.on("unhandledRejection")
- // test("captureRejections off will not capture rejections", async () => {
- // const myEmitter = new EventEmitter();
-
- // let handled = false;
- // myEmitter.on("error", (...args) => {
- // handled = true;
- // });
-
- // myEmitter.on("action", async () => {
- // throw new Error("Hello World");
- // });
-
- // myEmitter.emit("action");
-
- // await sleep(1);
-
- // expect(handled).toBe(false);
- // });
- test("it captures rejections", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
-
- let handled: any = null;
- myEmitter.on("error", (...args) => {
- handled = args;
- });
- myEmitter.on("action", async () => {
- throw 123;
- });
-
- myEmitter.emit("action");
-
- await sleep(5);
-
- expect(handled).toEqual([123]);
+ test("async EventEmitter emit (microtask) after", async done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ await 1;
+ emitter.emit("wow");
});
- test("it does not capture successful promises", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
- let handled: any = null;
- myEmitter.on("error", () => {
- handled = true;
- });
-
- myEmitter.on("action", async () => {
- return 123;
- });
-
- myEmitter.emit("action");
+ test("EventEmitter emit (same tick)", done => {
+ var emitter = new EventEmitter();
- await sleep(5);
+ emitter.on("wow", () => done());
- expect(handled).toEqual(null);
+ emitter.emit("wow");
});
- test("it does not capture handled rejections", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
- let handled: any = null;
- myEmitter.on("error", () => {
- handled = true;
- });
-
- myEmitter.on("action", async () => {
- return Promise.reject(123).catch(() => 234);
- });
-
- myEmitter.emit("action");
-
- await sleep(5);
-
- expect(handled).toEqual(null);
+ test("EventEmitter emit (setTimeout task)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ setTimeout(() => emitter.emit("wow"), 1);
});
});
@@ -508,30 +112,53 @@ const waysOfCreating = [
},
];
-describe("EventEmitter constructors", () => {
- for (let create of waysOfCreating) {
- test(`${create
- .toString()
- .slice(6, 52)
- .replaceAll("\n", "")
- .trim()
- .replaceAll(/ {2,}/g, " ")
- .replace(/^\{ ?/, "")} should work`, () => {
- var myEmitter = create();
- var called = false;
- (myEmitter as EventEmitter).once("event", function () {
- called = true;
- // @ts-ignore
- expect(this).toBe(myEmitter);
- });
- var firstEvents = myEmitter._events;
- expect(myEmitter.listenerCount("event")).toBe(1);
+for (let create of waysOfCreating) {
+ it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => {
+ var myEmitter = create();
+ var called = false;
+ (myEmitter as EventEmitter).once("event", function () {
+ called = true;
+ // @ts-ignore
+ expect(this).toBe(myEmitter);
+ });
+ var firstEvents = myEmitter._events;
+ expect(myEmitter.listenerCount("event")).toBe(1);
- expect(myEmitter.emit("event")).toBe(true);
- expect(myEmitter.listenerCount("event")).toBe(0);
+ expect(myEmitter.emit("event")).toBe(true);
+ expect(myEmitter.listenerCount("event")).toBe(0);
- expect(firstEvents).toEqual({ event: firstEvents.event }); // it shouldn't mutate
- expect(called).toBe(true);
- });
- }
+ expect(firstEvents).toBe(myEmitter._events);
+ expect(called).toBe(true);
+ });
+}
+
+test("EventEmitter.on", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
+});
+
+test("EventEmitter.off", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.off("foo", () => {})).toBe(myEmitter);
+});
+
+// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener
+test("EventEmitter GCs", async () => {
+ gc();
+
+ const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0;
+ (function () {
+ function EventEmitterSubclass(this: any) {
+ EventEmitter.call(this);
+ }
+
+ Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype);
+ Object.setPrototypeOf(EventEmitterSubclass, EventEmitter);
+ // @ts-ignore
+ var myEmitter = new EventEmitterSubclass();
+ myEmitter.on("foo", () => {});
+ myEmitter.emit("foo");
+ })();
+
+ await expectMaxObjectTypeCount(expect, "EventEmitter", startCount);
});
diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js
new file mode 100644
index 000000000..67050f31a
--- /dev/null
+++ b/test/js/node/events/node-builtins.test.js
@@ -0,0 +1,18 @@
+import { describe, it, expect } from "bun:test";
+
+import { EventEmitter } from "events";
+var emitters = [EventEmitter, require("events")];
+describe("EventEmitter", () => {
+ it("should emit events", () => {
+ for (let Emitter of emitters) {
+ const emitter = new Emitter();
+ var called = false;
+ const listener = () => {
+ called = true;
+ };
+ emitter.on("test", listener);
+ emitter.emit("test");
+ expect(called).toBe(true);
+ }
+ });
+});