import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test"; import { Event, EventManager, InvalidEventReturn, NoParamEvent } from "../../src/core/events"; import { disableConsoleLog, enableConsoleLog } from "../helper"; beforeAll(disableConsoleLog); afterAll(enableConsoleLog); class SpecialEvent extends Event<{ foo: string }> { static override slug = "special-event"; isBar() { return this.params.foo === "bar"; } } class InformationalEvent extends NoParamEvent { static override slug = "informational-event"; } class ReturnEvent extends Event<{ foo: string }, string> { static override slug = "return-event"; override validate(value: string) { if (typeof value !== "string") { throw new InvalidEventReturn("string", typeof value); } return this.clone({ foo: [this.params.foo, value].join("-") }); } } describe("EventManager", async () => { test("executes", async () => { const call = mock(() => null); const delayed = mock(() => null); const emgr = new EventManager(); emgr.registerEvents([SpecialEvent, InformationalEvent]); expect(emgr.eventExists("special-event")).toBe(true); expect(emgr.eventExists("informational-event")).toBe(true); expect(emgr.eventExists("unknown-event")).toBe(false); emgr.onEvent( SpecialEvent, async (event, name) => { expect(name).toBe("special-event"); expect(event.isBar()).toBe(true); call(); await new Promise((resolve) => setTimeout(resolve, 50)); delayed(); }, "sync" ); // don't allow unknown expect(() => emgr.on("unknown", () => void 0)).toThrow(); emgr.onEvent(InformationalEvent, async (event, name) => { call(); expect(name).toBe("informational-event"); }); await emgr.emit(new SpecialEvent({ foo: "bar" })); await emgr.emit(new InformationalEvent()); // expect construct signatures to not cause ts errors new SpecialEvent({ foo: "bar" }); new InformationalEvent(); expect(call).toHaveBeenCalledTimes(2); expect(delayed).toHaveBeenCalled(); }); test("custom async executor", async () => { const call = mock(() => null); const asyncExecutor = (p: Promise[]) => { call(); return Promise.all(p); }; const emgr = new EventManager( { InformationalEvent }, { asyncExecutor } ); emgr.onEvent(InformationalEvent, async () => {}); await emgr.emit(new InformationalEvent()); expect(call).toHaveBeenCalled(); }); test("piping", async () => { const onInvalidReturn = mock(() => null); const asyncEventCallback = mock(() => null); const emgr = new EventManager( { ReturnEvent, InformationalEvent }, { onInvalidReturn } ); // @ts-expect-error InformationalEvent has no return value emgr.onEvent(InformationalEvent, async () => { asyncEventCallback(); return 1; }); emgr.onEvent(ReturnEvent, async () => "1", "sync"); emgr.onEvent(ReturnEvent, async () => "0", "sync"); // @todo: fix this // @ts-expect-error must be string emgr.onEvent(ReturnEvent, async () => 0, "sync"); // return is not required emgr.onEvent(ReturnEvent, async () => {}, "sync"); // was "async", will not return const e1 = await emgr.emit(new InformationalEvent()); expect(e1.returned).toBe(false); const e2 = await emgr.emit(new ReturnEvent({ foo: "bar" })); expect(e2.returned).toBe(true); expect(e2.params.foo).toBe("bar-1-0"); expect(onInvalidReturn).toHaveBeenCalled(); expect(asyncEventCallback).toHaveBeenCalled(); }); test("once", async () => { const call = mock(() => null); const emgr = new EventManager({ InformationalEvent }); emgr.onEvent( InformationalEvent, async (event, slug) => { expect(event).toBeInstanceOf(InformationalEvent); expect(slug).toBe("informational-event"); call(); }, { mode: "sync", once: true } ); expect(emgr.getListeners().length).toBe(1); await emgr.emit(new InformationalEvent()); expect(emgr.getListeners().length).toBe(0); await emgr.emit(new InformationalEvent()); expect(emgr.getListeners().length).toBe(0); expect(call).toHaveBeenCalledTimes(1); }); });