import { test } from "../testing/mod.ts"; import { assert, assertEquals, fail, assertThrows } from "../testing/asserts.ts"; import EventEmitter, { WrappedFunction, once } from "./events.ts"; const shouldNeverBeEmitted: Function = () => { fail("Should never be called"); }; test({ name: 'When adding a new event, "eventListener" event is fired before adding the listener', fn() { let eventsFired = []; const testEmitter = new EventEmitter(); testEmitter.on("newListener", event => { if (event !== "newListener") { eventsFired.push("newListener"); } }); testEmitter.on("event", () => { eventsFired.push("event"); }); assertEquals(eventsFired, ["newListener"]); eventsFired = []; testEmitter.emit("event"); assertEquals(eventsFired, ["event"]); } }); test({ name: 'When removing a listenert, "removeListener" event is fired after removal', fn() { const eventsFired = []; const testEmitter = new EventEmitter(); testEmitter.on("removeListener", () => { eventsFired.push("removeListener"); }); const eventFunction = function(): void { eventsFired.push("event"); }; testEmitter.on("event", eventFunction); assertEquals(eventsFired, []); testEmitter.removeListener("event", eventFunction); assertEquals(eventsFired, ["removeListener"]); } }); test({ name: "Default max listeners is 10, but can be changed by direct assignment only", fn() { assertEquals(EventEmitter.defaultMaxListeners, 10); new EventEmitter().setMaxListeners(20); assertEquals(EventEmitter.defaultMaxListeners, 10); EventEmitter.defaultMaxListeners = 20; assertEquals(EventEmitter.defaultMaxListeners, 20); EventEmitter.defaultMaxListeners = 10; //reset back to original value } }); test({ name: "addListener adds a listener, and listener count is correct", fn() { const testEmitter = new EventEmitter(); testEmitter.on("event", shouldNeverBeEmitted); assertEquals(1, testEmitter.listenerCount("event")); testEmitter.on("event", shouldNeverBeEmitted); assertEquals(2, testEmitter.listenerCount("event")); } }); test({ name: "Emitted events are called synchronously in the order they were added", fn() { const testEmitter = new EventEmitter(); const eventsFired = []; testEmitter.on("event", oneArg => { eventsFired.push("event(" + oneArg + ")"); }); testEmitter.on("event", (oneArg, twoArg) => { eventsFired.push("event(" + oneArg + ", " + twoArg + ")"); }); testEmitter.on("non-event", shouldNeverBeEmitted); testEmitter.on("event", (oneArg, twoArg, threeArg) => { eventsFired.push( "event(" + oneArg + ", " + twoArg + ", " + threeArg + ")" ); }); testEmitter.emit("event", 1, 2, 3); assertEquals(eventsFired, ["event(1)", "event(1, 2)", "event(1, 2, 3)"]); } }); test({ name: "Registered event names are returned as strings or Sybols", fn() { const testEmitter = new EventEmitter(); testEmitter.on("event", shouldNeverBeEmitted); testEmitter.on("event", shouldNeverBeEmitted); const sym = Symbol("symbol"); testEmitter.on(sym, shouldNeverBeEmitted); assertEquals(testEmitter.eventNames(), ["event", sym]); } }); test({ name: "You can set and get max listeners", fn() { const testEmitter = new EventEmitter(); assertEquals(testEmitter.getMaxListeners(), 10); testEmitter.setMaxListeners(20); assertEquals(testEmitter.getMaxListeners(), 20); } }); test({ name: "You can retrieve registered functions for an event", fn() { const testEmitter = new EventEmitter(); testEmitter.on("someOtherEvent", shouldNeverBeEmitted); testEmitter.on("event", shouldNeverBeEmitted); const testFunction = (): void => {}; testEmitter.on("event", testFunction); assertEquals(testEmitter.listeners("event"), [ shouldNeverBeEmitted, testFunction ]); } }); test({ name: "Off is alias for removeListener", fn() { const testEmitter = new EventEmitter(); testEmitter.on("event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 1); testEmitter.off("event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 0); } }); test({ name: "Event registration can be chained", fn() { const testEmitter = new EventEmitter(); testEmitter .on("event", shouldNeverBeEmitted) .on("event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 2); } }); test({ name: "Events can be registered to only fire once", fn() { let eventsFired = []; const testEmitter = new EventEmitter(); //prove multiple emits on same event first (when registered with 'on') testEmitter.on("multiple event", () => { eventsFired.push("multiple event"); }); testEmitter.emit("multiple event"); testEmitter.emit("multiple event"); assertEquals(eventsFired, ["multiple event", "multiple event"]); //now prove multiple events registered via 'once' only emit once eventsFired = []; testEmitter.once("single event", () => { eventsFired.push("single event"); }); testEmitter.emit("single event"); testEmitter.emit("single event"); assertEquals(eventsFired, ["single event"]); } }); test({ name: "You can inject a listener into the start of the stack, rather than at the end", fn() { const eventsFired = []; const testEmitter = new EventEmitter(); testEmitter.on("event", () => { eventsFired.push("first"); }); testEmitter.on("event", () => { eventsFired.push("second"); }); testEmitter.prependListener("event", () => { eventsFired.push("third"); }); testEmitter.emit("event"); assertEquals(eventsFired, ["third", "first", "second"]); } }); test({ name: 'You can prepend a "once" listener', fn() { const eventsFired = []; const testEmitter = new EventEmitter(); testEmitter.on("event", () => { eventsFired.push("first"); }); testEmitter.on("event", () => { eventsFired.push("second"); }); testEmitter.prependOnceListener("event", () => { eventsFired.push("third"); }); testEmitter.emit("event"); testEmitter.emit("event"); assertEquals(eventsFired, ["third", "first", "second", "first", "second"]); } }); test({ name: "Remove all listeners, which can also be chained", fn() { const testEmitter = new EventEmitter(); testEmitter.on("event", shouldNeverBeEmitted); testEmitter.on("event", shouldNeverBeEmitted); testEmitter.on("other event", shouldNeverBeEmitted); testEmitter.on("other event", shouldNeverBeEmitted); testEmitter.once("other event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 2); assertEquals(testEmitter.listenerCount("other event"), 3); testEmitter.removeAllListeners("event").removeAllListeners("other event"); assertEquals(testEmitter.listenerCount("event"), 0); assertEquals(testEmitter.listenerCount("other event"), 0); } }); test({ name: "Remove individual listeners, which can also be chained", fn() { const testEmitter = new EventEmitter(); testEmitter.on("event", shouldNeverBeEmitted); testEmitter.on("event", shouldNeverBeEmitted); testEmitter.once("other event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 2); assertEquals(testEmitter.listenerCount("other event"), 1); testEmitter.removeListener("other event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 2); assertEquals(testEmitter.listenerCount("other event"), 0); testEmitter .removeListener("event", shouldNeverBeEmitted) .removeListener("event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 0); assertEquals(testEmitter.listenerCount("other event"), 0); } }); test({ name: "It is OK to try to remove non-existant listener", fn() { const testEmitter = new EventEmitter(); const madeUpEvent = (): void => { fail("Should never be called"); }; testEmitter.on("event", shouldNeverBeEmitted); assertEquals(testEmitter.listenerCount("event"), 1); testEmitter.removeListener("event", madeUpEvent); testEmitter.removeListener("non-existant event", madeUpEvent); assertEquals(testEmitter.listenerCount("event"), 1); } }); test({ name: "all listeners complete execution even if removed before execution", fn() { const testEmitter = new EventEmitter(); let eventsProcessed = []; const listenerB = (): number => eventsProcessed.push("B"); const listenerA = (): void => { eventsProcessed.push("A"); testEmitter.removeListener("event", listenerB); }; testEmitter.on("event", listenerA); testEmitter.on("event", listenerB); testEmitter.emit("event"); assertEquals(eventsProcessed, ["A", "B"]); eventsProcessed = []; testEmitter.emit("event"); assertEquals(eventsProcessed, ["A"]); } }); test({ name: 'Raw listener will return event listener or wrapped "once" function', fn() { const testEmitter = new EventEmitter(); const eventsProcessed = []; const listenerA = (): number => eventsProcessed.push("A"); const listenerB = (): number => eventsProcessed.push("B"); testEmitter.on("event", listenerA); testEmitter.once("once-event", listenerB); const rawListenersForEvent = testEmitter.rawListeners("event"); const rawListenersForOnceEvent = testEmitter.rawListeners("once-event"); assertEquals(rawListenersForEvent.length, 1); assertEquals(rawListenersForOnceEvent.length, 1); assertEquals(rawListenersForEvent[0], listenerA); assertEquals( (rawListenersForOnceEvent[0] as WrappedFunction).listener, listenerB ); } }); test({ name: "Once wrapped raw listeners may be executed multiple times, until the wrapper is executed", fn() { const testEmitter = new EventEmitter(); let eventsProcessed = []; const listenerA = (): number => eventsProcessed.push("A"); testEmitter.once("once-event", listenerA); const rawListenersForOnceEvent = testEmitter.rawListeners("once-event"); const wrappedFn: WrappedFunction = rawListenersForOnceEvent[0] as WrappedFunction; wrappedFn.listener(); wrappedFn.listener(); wrappedFn.listener(); assertEquals(eventsProcessed, ["A", "A", "A"]); eventsProcessed = []; wrappedFn(); // executing the wrapped listener function will remove it from the event assertEquals(eventsProcessed, ["A"]); assertEquals(testEmitter.listeners("once-event").length, 0); } }); test({ name: "Can add once event listener to EventEmitter via standalone function", async fn() { const ee: EventEmitter = new EventEmitter(); setTimeout(() => { ee.emit("event", 42, "foo"); }, 0); // eslint-disable-next-line @typescript-eslint/no-explicit-any const valueArr: any[] = await once(ee, "event"); assertEquals(valueArr, [42, "foo"]); } }); test({ name: "Can add once event listener to EventTarget via standalone function", async fn() { const et: EventTarget = new EventTarget(); setTimeout(() => { const event: Event = new Event("event", { composed: true }); et.dispatchEvent(event); }, 0); // eslint-disable-next-line @typescript-eslint/no-explicit-any const eventObj: any[] = await once(et, "event"); assert(!eventObj[0].isTrusted); } }); test({ name: "Only valid integers are allowed for max listeners", fn() { const ee: EventEmitter = new EventEmitter(); ee.setMaxListeners(0); assertThrows( () => { ee.setMaxListeners(-1); }, Error, "must be >= 0" ); assertThrows( () => { ee.setMaxListeners(3.45); }, Error, "must be 'an integer'" ); } });