@irrelon/emitter
Version:
Simple JavaScript event emitter with some powerful features.
461 lines (460 loc) • 26 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const globals_1 = require("@jest/globals");
const assert_1 = __importDefault(require("assert"));
const Emitter_1 = require("../src/Emitter");
let expectedAssertions = 0;
let actualAssertions = 0;
function expectAssertionCount(val) {
expectedAssertions = val;
}
function resetAssertionCount() {
expectedAssertions = 0;
actualAssertions = 0;
}
function countAssertion() {
actualAssertions++;
}
function checkAssertionCount() {
if (expectedAssertions === undefined || expectedAssertions === actualAssertions) {
return;
}
throw new Error("expected " + expectedAssertions + " assertions, got " + actualAssertions);
}
beforeEach(resetAssertionCount);
afterEach(checkAssertionCount);
describe("Emitter", () => {
describe("new Emitter()", () => {
it("Creates emitter instance by instantiation", () => {
var _a, _b;
const myClass = new Emitter_1.Emitter();
myClass.on("moo", () => { });
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["*"].length, 1, "Listener registered on event");
myClass.off("moo");
assert_1.default.strictEqual(!((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo) ||
!myClass._eventListeners.moo["*"] ||
myClass._eventListeners.moo["*"].length, true, "Listeners all removed from event");
});
it("Supports extending Emitter as a base class", () => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
// Test extending Emitter and modifying base functionality
class MyClass extends Emitter_1.Emitter {
on(eventName, ...rest) {
const restTypes = rest.map((arg) => typeof arg);
if (restTypes[0] === "function") {
const listener = restTypes[0];
const id = "^^noId";
return super.on(eventName, id, listener);
}
return super.on(eventName, rest[0], rest[1]);
}
// @ts-ignore
emit(eventName, ...rest) {
return super.emitId(eventName, "^^noId", ...rest);
}
off(eventName, ...rest) {
let id = rest[0];
let func = rest[1];
if (typeof rest[0] === "function") {
func = rest[0];
id = "^^noId";
}
else if (typeof id === "undefined") {
id = "^^noId";
}
return super.off(eventName, id, func);
}
}
const myClass = new MyClass();
let emitCount = 0;
// Register id-based listener
myClass.on("moo", "testId", () => {
emitCount++;
});
// Register non-id-based listener
myClass.on("moo", () => {
emitCount++;
});
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["testId"].length, 1, "Listener registered on event");
myClass.emitId("moo", "testId", "foo");
assert_1.default.strictEqual(emitCount, 1, "Event fired correctly");
myClass.off("moo", "testId");
assert_1.default.strictEqual(!((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo) ||
!((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["testId"]) ||
((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length) === 0, true, "Listeners all removed from event");
assert_1.default.strictEqual(((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo) &&
((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["^^noId"]) &&
((_g = myClass._eventListeners) === null || _g === void 0 ? void 0 : _g.moo["^^noId"].length) === 1, true, "Global listener still there");
assert_1.default.strictEqual(!((_h = myClass._eventListeners) === null || _h === void 0 ? void 0 : _h.moo) ||
!((_j = myClass._eventListeners) === null || _j === void 0 ? void 0 : _j.moo["*"]) ||
((_k = myClass._eventListeners) === null || _k === void 0 ? void 0 : _k.moo["*"].length) === 0, true, "Global listener still there");
myClass.off("moo");
assert_1.default.strictEqual(!((_l = myClass._eventListeners) === null || _l === void 0 ? void 0 : _l.moo) ||
!((_m = myClass._eventListeners) === null || _m === void 0 ? void 0 : _m.moo["*"]) ||
((_o = myClass._eventListeners) === null || _o === void 0 ? void 0 : _o.moo["*"].length) === 0, true, "Listeners all removed from event");
});
});
describe("emit()", () => {
it("Supports type safe usage", () => __awaiter(void 0, void 0, void 0, function* () {
const emitter = new Emitter_1.Emitter();
let listenerFiredCount = 0;
emitter.on("person", (name) => {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve(name);
}, 1000);
});
});
emitter.on("post", () => {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve();
}, 1000);
});
});
emitter.on("post", () => {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve();
}, 1000);
});
});
const time = new Date().getTime();
yield Promise.all(emitter.emit("post"));
const delta = new Date().getTime() - time;
assert_1.default.ok(delta > 900, `Delta was not correct, await may not have paused? Delta was: ${delta}`);
assert_1.default.ok(listenerFiredCount === 2, `listenerFiredCount was not correct, await may not have paused? listenerFiredCount was: ${listenerFiredCount}`);
}));
it("Supports awaiting async listeners", () => __awaiter(void 0, void 0, void 0, function* () {
const emitter = new Emitter_1.Emitter();
let listenerFiredCount = 0;
emitter.on("foo", () => __awaiter(void 0, void 0, void 0, function* () {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve();
}, 1000);
});
}));
emitter.on("foo", () => __awaiter(void 0, void 0, void 0, function* () {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve();
}, 1000);
});
}));
const time = new Date().getTime();
yield Promise.all(emitter.emit("foo"));
const delta = new Date().getTime() - time;
assert_1.default.ok(delta > 900, "Delta was not correct, await may not have paused?");
assert_1.default.strictEqual(listenerFiredCount, 2, "Listener fire count was incorect");
}));
it("Supports awaiting async and non-async listener return data", () => __awaiter(void 0, void 0, void 0, function* () {
const emitter = new Emitter_1.Emitter();
let listenerFiredCount = 0;
emitter.on("foo", () => {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve("Foo1 Return Value");
}, 1000);
});
});
emitter.on("foo", () => {
return "Foo2 Return Value";
});
const time = new Date().getTime();
const results = yield Promise.all(emitter.emit("foo"));
const delta = new Date().getTime() - time;
assert_1.default.ok(delta > 900, "Delta was not correct, await may not have paused?");
assert_1.default.ok(listenerFiredCount === 1, "listenerFiredCount was not correct, await may not have been used?");
assert_1.default.strictEqual(results[0], "Foo1 Return Value", "Return data for foo1 was not correct");
assert_1.default.strictEqual(results[1], "Foo2 Return Value", "Return data for foo2 was not correct");
}));
it("Supports awaiting async and non-async rpc listener return data", () => __awaiter(void 0, void 0, void 0, function* () {
const emitter = new Emitter_1.Emitter();
let listenerFiredCount = 0;
emitter.on("foo1", () => {
return new Promise((resolve) => {
setTimeout(() => {
listenerFiredCount++;
resolve("Foo1 Return Value");
}, 1000);
});
});
emitter.on("foo2", () => {
return "Foo2 Return Value";
});
const time = new Date().getTime();
const result1 = yield emitter.rpc("foo1");
const result2 = emitter.rpc("foo2");
const delta = new Date().getTime() - time;
assert_1.default.ok(delta > 900, "Delta was not correct, await may not have paused?");
assert_1.default.ok(listenerFiredCount === 1, "listenerFiredCount was not correct, await may not have been used?");
assert_1.default.strictEqual(result1, "Foo1 Return Value", "Return data for foo1 was not correct");
assert_1.default.strictEqual(result2, "Foo2 Return Value", "Return data for foo2 was not correct");
}));
it("Supports responding with a cancellation signal", () => __awaiter(void 0, void 0, void 0, function* () {
const emitter = new Emitter_1.Emitter();
let listenerFiredCount = 0;
emitter.on("foo", () => {
listenerFiredCount++;
return "Foo1 Return Value";
});
emitter.on("foo", () => {
listenerFiredCount++;
// This listener will respond with a cancellation, this should terminate further listener calls
return Emitter_1.ListenerReturnFlag.cancel;
});
emitter.on("foo", () => {
listenerFiredCount++;
return "Foo3 Return Value";
});
emitter.on("foo", () => {
listenerFiredCount++;
return "Foo4 Return Value";
});
const results = emitter.emit("foo");
assert_1.default.ok(listenerFiredCount === 2, "listenerFiredCount was not correct, await may not have been used?");
assert_1.default.strictEqual(results[0], "Foo1 Return Value", "Return data should be a cancel flag");
assert_1.default.strictEqual(results[1], Emitter_1.ListenerReturnFlag.cancel, "Return data should be a cancel flag");
assert_1.default.ok(emitter.didCancel(results), "didCancel should detect a cancel flag");
}));
});
describe("off()", () => {
it("Cancels all listeners (event)", () => {
var _a, _b, _c, _d, _e, _f, _g;
const myClass = new Emitter_1.Emitter();
const listener1 = () => { };
const listener2 = () => { };
myClass.on("moo", () => { });
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["*"].length, 1, "Listeners registered on event");
myClass.on("moo", "testId", () => { });
assert_1.default.strictEqual((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo["testId"].length, 1, "Listeners registered on event");
myClass.on("moo", listener1);
assert_1.default.strictEqual((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["*"].length, 2, "Listeners registered on event");
myClass.on("moo", "testId", listener2);
assert_1.default.strictEqual((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length, 2, "Listeners registered on event");
myClass.off("moo");
assert_1.default.strictEqual(!((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo) ||
(((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["*"].length) === 0 &&
((_g = myClass._eventListeners) === null || _g === void 0 ? void 0 : _g.moo["testId"].length) === 0), true, "Listeners all removed from event");
});
it("Cancels id-based listeners (event, id)", () => {
var _a, _b, _c, _d, _e, _f, _g;
const myClass = new Emitter_1.Emitter();
const listener1 = () => { };
const listener2 = () => { };
myClass.on("moo", () => { });
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["*"].length, 1, "Listeners registered on event");
myClass.on("moo", "testId", () => { });
assert_1.default.strictEqual((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo["testId"].length, 1, "Listeners registered on event");
myClass.on("moo", listener1);
assert_1.default.strictEqual((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["*"].length, 2, "Listeners registered on event");
myClass.on("moo", "testId", listener2);
assert_1.default.strictEqual((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length, 2, "Listeners registered on event");
myClass.off("moo", "testId");
assert_1.default.strictEqual(((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo["*"].length) === 2, true, "Listeners all removed from event");
assert_1.default.strictEqual(!((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["testId"]) || ((_g = myClass._eventListeners) === null || _g === void 0 ? void 0 : _g.moo["testId"].length) === 0, true, "Listeners all removed from event");
});
it("Cancels listener-based listeners (event, listener)", () => {
var _a, _b, _c, _d, _e, _f;
const myClass = new Emitter_1.Emitter();
const listener1 = () => { };
const listener2 = () => { };
myClass.on("moo", () => { });
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["*"].length, 1, "Listeners registered on event");
myClass.on("moo", "testId", () => { });
assert_1.default.strictEqual((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo["testId"].length, 1, "Listeners registered on event");
myClass.on("moo", listener1);
assert_1.default.strictEqual((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["*"].length, 2, "Listeners registered on event");
myClass.on("moo", "testId", listener2);
assert_1.default.strictEqual((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length, 2, "Listeners registered on event");
myClass.off("moo", listener1);
assert_1.default.strictEqual(((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo["*"].length) === 1, true, "Listeners all removed from event");
assert_1.default.strictEqual(((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["testId"].length) === 2, true, "Listeners all removed from event");
});
it("Cancels id-based + listener-based listeners (event, id, listener)", () => {
var _a, _b, _c, _d, _e, _f;
const myClass = new Emitter_1.Emitter();
const listener1 = () => { };
const listener2 = () => { };
myClass.on("moo", () => { });
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["*"].length, 1, "Listeners registered on event");
myClass.on("moo", "testId", () => { });
assert_1.default.strictEqual((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo["testId"].length, 1, "Listeners registered on event");
myClass.on("moo", listener1);
assert_1.default.strictEqual((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["*"].length, 2, "Listeners registered on event");
myClass.on("moo", "testId", listener2);
assert_1.default.strictEqual((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length, 2, "Listeners registered on event");
myClass.off("moo", "testId", listener2);
assert_1.default.strictEqual(((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo["*"].length) === 2, true, "Listeners all removed from event");
assert_1.default.strictEqual(((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["testId"].length) === 1, true, "Listeners all removed from event");
});
});
describe("emitStatic()", () => {
it("Static emitter will emit when a new listener is added", (resolve) => {
class MyClass extends Emitter_1.Emitter {
}
expectAssertionCount(1);
const myClass = new MyClass();
myClass.emitStatic("moo");
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(true, "Callback was fired");
resolve();
});
});
});
describe("cancelStatic()", () => {
it("Removes static emitter", (resolve) => {
class MyClass extends Emitter_1.Emitter {
}
expectAssertionCount(1);
const myClass = new MyClass();
myClass.emitStatic("moo");
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(true, "Callback was fired");
});
myClass.cancelStatic("moo");
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(false, "Callback was fired");
});
setTimeout(() => {
resolve();
}, 10);
});
});
describe("overwrite()", () => {
it("Only fires the last listener added, cancelling all other listeners before it", (resolve) => {
class MyClass extends Emitter_1.Emitter {
}
expectAssertionCount(1);
const myClass = new MyClass();
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(false, "Correct callback was fired");
});
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(false, "Correct callback was fired");
});
myClass.overwrite("moo", () => {
countAssertion();
assert_1.default.ok(true, "Correct callback was fired");
});
myClass.emit("moo");
setTimeout(() => {
resolve();
}, 10);
});
it("Only fires the last listener added (with id), cancelling all other listeners before it", (resolve) => {
class MyClass extends Emitter_1.Emitter {
}
expectAssertionCount(2);
const myClass = new MyClass();
// Define a listener without an id, this should not be overwritten
myClass.on("moo", () => {
countAssertion();
assert_1.default.ok(true, "Correct callback was fired");
});
// Define some listeners WITH ids, these should be overwritten
myClass.on("moo", "1234", () => {
countAssertion();
assert_1.default.ok(false, "Correct callback was fired");
});
myClass.on("moo", "1234", () => {
countAssertion();
assert_1.default.ok(false, "Correct callback was fired");
});
// Overwrite the id listeners
myClass.overwrite("moo", "1234", () => {
countAssertion();
assert_1.default.ok(true, "Correct callback was fired");
});
myClass.emitId("moo", "1234");
setTimeout(() => {
resolve();
}, 50);
});
});
describe("emitId()", () => {
it("Fires an id-based event first followed by a global event", () => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const myClass = new Emitter_1.Emitter();
let emitCount = 0;
// Register id-based listener
myClass.on("moo", "testId", () => {
emitCount++;
});
// Register non-id-based listener
myClass.on("moo", () => {
emitCount++;
});
assert_1.default.strictEqual((_a = myClass._eventListeners) === null || _a === void 0 ? void 0 : _a.moo["testId"].length, 1, "Listener registered on event");
myClass.emitId("moo", "testId", "foo");
assert_1.default.strictEqual(emitCount, 2, "Event fired correctly");
myClass.off("moo", "testId");
assert_1.default.strictEqual(!((_b = myClass._eventListeners) === null || _b === void 0 ? void 0 : _b.moo) ||
!((_c = myClass._eventListeners) === null || _c === void 0 ? void 0 : _c.moo["testId"]) ||
((_d = myClass._eventListeners) === null || _d === void 0 ? void 0 : _d.moo["testId"].length), true, "Listeners all removed from event");
assert_1.default.strictEqual(((_e = myClass._eventListeners) === null || _e === void 0 ? void 0 : _e.moo) &&
((_f = myClass._eventListeners) === null || _f === void 0 ? void 0 : _f.moo["*"]) &&
((_g = myClass._eventListeners) === null || _g === void 0 ? void 0 : _g.moo["*"].length) === 1, true, "Global listener still there");
myClass.off("moo");
assert_1.default.strictEqual(!((_h = myClass._eventListeners) === null || _h === void 0 ? void 0 : _h.moo) ||
!((_j = myClass._eventListeners) === null || _j === void 0 ? void 0 : _j.moo["*"]) ||
((_k = myClass._eventListeners) === null || _k === void 0 ? void 0 : _k.moo["*"].length) === 0, true, "Listeners all removed from event");
});
});
describe("Global listener functionality", () => {
it("Listens for any event emitted and fires the listener with the onAny() function", () => {
const emitter = new Emitter_1.Emitter();
const spy = globals_1.jest.fn();
emitter.onAny(spy);
emitter.emit("someEvent", "someArg1", true, false);
expect(spy).toHaveBeenCalledWith("someEvent", "someArg1", true, false);
});
it(`Listens for any event emitted and fires the listener with the on("*") function`, () => {
const emitter = new Emitter_1.Emitter();
const spy = globals_1.jest.fn();
emitter.on("*", spy);
emitter.emit("someEvent", "someArg1", true, false);
expect(spy).toHaveBeenCalledWith("someEvent", "someArg1", true, false);
});
it(`Cancels an event listener for any event emitted with the offAny() function`, () => {
const emitter = new Emitter_1.Emitter();
const spy = globals_1.jest.fn();
emitter.on("*", spy);
emitter.offAny(spy);
emitter.emit("someEvent", "someArg1", true, false);
expect(spy).toHaveBeenCalledTimes(0);
});
it(`Cancels an event listener for any event emitted with the off("*") function`, () => {
const emitter = new Emitter_1.Emitter();
const spy = globals_1.jest.fn();
emitter.on("*", spy);
emitter.off("*", spy);
emitter.emit("someEvent", "someArg1", true, false);
expect(spy).toHaveBeenCalledTimes(0);
});
});
});