summaryrefslogtreecommitdiffstats
path: root/devtools/shared/tests/xpcshell/test_eventemitter_static.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/tests/xpcshell/test_eventemitter_static.js')
-rw-r--r--devtools/shared/tests/xpcshell/test_eventemitter_static.js378
1 files changed, 378 insertions, 0 deletions
diff --git a/devtools/shared/tests/xpcshell/test_eventemitter_static.js b/devtools/shared/tests/xpcshell/test_eventemitter_static.js
new file mode 100644
index 0000000000..9b17a7612f
--- /dev/null
+++ b/devtools/shared/tests/xpcshell/test_eventemitter_static.js
@@ -0,0 +1,378 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+ ConsoleAPIListener,
+} = require("resource://devtools/server/actors/webconsole/listeners/console-api.js");
+const {
+ on,
+ once,
+ off,
+ emit,
+ count,
+ handler,
+} = require("resource://devtools/shared/event-emitter.js");
+
+const pass = message => ok(true, message);
+const fail = message => ok(false, message);
+
+/**
+ * Each method of this object is a test; tests can be synchronous or asynchronous:
+ *
+ * 1. Plain method are synchronous tests.
+ * 2. methods with `async` keyword are asynchronous tests.
+ * 3. methods with `done` as argument are asynchronous tests (`done` needs to be called to
+ * complete the test).
+ */
+const TESTS = {
+ testAddListener() {
+ const events = [{ name: "event#1" }, "event#2"];
+ const target = { name: "target" };
+
+ on(target, "message", function (message) {
+ equal(this, target, "this is a target object");
+ equal(message, events.shift(), "message is emitted event");
+ });
+
+ emit(target, "message", events[0]);
+ emit(target, "message", events[0]);
+ },
+
+ testListenerIsUniquePerType() {
+ const actual = [];
+ const target = {};
+ listener = () => actual.push(1);
+
+ on(target, "message", listener);
+ on(target, "message", listener);
+ on(target, "message", listener);
+ on(target, "foo", listener);
+ on(target, "foo", listener);
+
+ emit(target, "message");
+ deepEqual([1], actual, "only one message listener added");
+
+ emit(target, "foo");
+ deepEqual([1, 1], actual, "same listener added for other event");
+ },
+
+ testEventTypeMatters() {
+ const target = { name: "target" };
+ on(target, "message", () => fail("no event is expected"));
+ on(target, "done", () => pass("event is emitted"));
+
+ emit(target, "foo");
+ emit(target, "done");
+ },
+
+ testAllArgumentsArePassed() {
+ const foo = { name: "foo" },
+ bar = "bar";
+ const target = { name: "target" };
+
+ on(target, "message", (a, b) => {
+ equal(a, foo, "first argument passed");
+ equal(b, bar, "second argument passed");
+ });
+
+ emit(target, "message", foo, bar);
+ },
+
+ testNoSideEffectsInEmit() {
+ const target = { name: "target" };
+
+ on(target, "message", () => {
+ pass("first listener is called");
+
+ on(target, "message", () => fail("second listener is called"));
+ });
+ emit(target, "message");
+ },
+
+ testCanRemoveNextListener() {
+ const target = { name: "target" };
+
+ on(target, "data", () => {
+ pass("first listener called");
+ off(target, "data", fail);
+ });
+ on(target, "data", fail);
+
+ emit(target, "data", "Listener should be removed");
+ },
+
+ testOrderOfPropagation() {
+ const actual = [];
+ const target = { name: "target" };
+
+ on(target, "message", () => actual.push(1));
+ on(target, "message", () => actual.push(2));
+ on(target, "message", () => actual.push(3));
+ emit(target, "message");
+
+ deepEqual([1, 2, 3], actual, "called in order they were added");
+ },
+
+ testRemoveListener() {
+ const target = { name: "target" };
+ const actual = [];
+
+ on(target, "message", function listener() {
+ actual.push(1);
+ on(target, "message", () => {
+ off(target, "message", listener);
+ actual.push(2);
+ });
+ });
+
+ emit(target, "message");
+ deepEqual([1], actual, "first listener called");
+
+ emit(target, "message");
+ deepEqual([1, 1, 2], actual, "second listener called");
+
+ emit(target, "message");
+ deepEqual([1, 1, 2, 2, 2], actual, "first listener removed");
+ },
+
+ testRemoveAllListenersForType() {
+ const actual = [];
+ const target = { name: "target" };
+
+ on(target, "message", () => actual.push(1));
+ on(target, "message", () => actual.push(2));
+ on(target, "message", () => actual.push(3));
+ on(target, "bar", () => actual.push("b"));
+ off(target, "message");
+
+ emit(target, "message");
+ emit(target, "bar");
+
+ deepEqual(["b"], actual, "all message listeners were removed");
+ },
+
+ testRemoveAllListeners() {
+ const actual = [];
+ const target = { name: "target" };
+
+ on(target, "message", () => actual.push(1));
+ on(target, "message", () => actual.push(2));
+ on(target, "message", () => actual.push(3));
+ on(target, "bar", () => actual.push("b"));
+
+ off(target);
+
+ emit(target, "message");
+ emit(target, "bar");
+
+ deepEqual([], actual, "all listeners events were removed");
+ },
+
+ testFalsyArgumentsAreFine() {
+ let type, listener;
+ const target = { name: "target" },
+ actual = [];
+ on(target, "bar", () => actual.push(0));
+
+ off(target, "bar", listener);
+ emit(target, "bar");
+ deepEqual([0], actual, "3rd bad arg will keep listener");
+
+ off(target, type);
+ emit(target, "bar");
+ deepEqual([0, 0], actual, "2nd bad arg will keep listener");
+
+ off(target, type, listener);
+ emit(target, "bar");
+ deepEqual([0, 0, 0], actual, "2nd & 3rd bad args will keep listener");
+ },
+
+ testUnhandledExceptions(done) {
+ const listener = new ConsoleAPIListener(null, message => {
+ equal(message.level, "error", "Got the first exception");
+ equal(
+ message.arguments[0].message,
+ "Boom!",
+ "unhandled exception is logged"
+ );
+
+ listener.destroy();
+ done();
+ });
+
+ listener.init();
+
+ const target = {};
+
+ on(target, "message", () => {
+ throw Error("Boom!");
+ });
+
+ emit(target, "message");
+ },
+
+ testCount() {
+ const target = { name: "target" };
+
+ equal(count(target, "foo"), 0, "no listeners for 'foo' events");
+ on(target, "foo", () => {});
+ equal(count(target, "foo"), 1, "listener registered");
+ on(target, "foo", () => {});
+ equal(count(target, "foo"), 2, "another listener registered");
+ off(target);
+ equal(count(target, "foo"), 0, "listeners unregistered");
+ },
+
+ async testOnce() {
+ const target = { name: "target" };
+ const called = false;
+
+ const pFoo = once(target, "foo", function (value) {
+ ok(!called, "listener called only once");
+ equal(value, "bar", "correct argument was passed");
+ equal(this, target, "the contextual object is correct");
+ });
+ const pDone = once(target, "done");
+
+ emit(target, "foo", "bar");
+ emit(target, "foo", "baz");
+ emit(target, "done", "");
+
+ await Promise.all([pFoo, pDone]);
+ },
+
+ testRemovingOnce(done) {
+ const target = { name: "target" };
+
+ once(target, "foo", fail);
+ once(target, "done", done);
+
+ off(target, "foo", fail);
+
+ emit(target, "foo", "listener was called");
+ emit(target, "done", "");
+ },
+
+ testAddListenerWithHandlerMethod() {
+ const target = { name: "target" };
+ const actual = [];
+ const listener = function (...args) {
+ equal(
+ this,
+ target,
+ "the contextual object is correct for function listener"
+ );
+ deepEqual(args, [10, 20, 30], "arguments are properly passed");
+ };
+
+ const object = {
+ name: "target",
+ [handler](type, ...rest) {
+ actual.push(type);
+ equal(
+ this,
+ object,
+ "the contextual object is correct for object listener"
+ );
+ deepEqual(rest, [10, 20, 30], "arguments are properly passed");
+ },
+ };
+
+ on(target, "foo", listener);
+ on(target, "bar", object);
+ on(target, "baz", object);
+
+ emit(target, "foo", 10, 20, 30);
+ emit(target, "bar", 10, 20, 30);
+ emit(target, "baz", 10, 20, 30);
+
+ deepEqual(
+ actual,
+ ["bar", "baz"],
+ "object's listener called in the expected order"
+ );
+ },
+
+ testRemoveListenerWithHandlerMethod() {
+ const target = {};
+ const actual = [];
+
+ const object = {
+ [handler](type) {
+ actual.push(1);
+ on(target, "message", () => {
+ off(target, "message", object);
+ actual.push(2);
+ });
+ },
+ };
+
+ on(target, "message", object);
+
+ emit(target, "message");
+ deepEqual([1], actual, "first listener called");
+
+ emit(target, "message");
+ deepEqual([1, 1, 2], actual, "second listener called");
+
+ emit(target, "message");
+ deepEqual([1, 1, 2, 2, 2], actual, "first listener removed");
+ },
+
+ async testOnceListenerWithHandlerMethod() {
+ const target = { name: "target" };
+ const called = false;
+
+ const object = {
+ [handler](type, value) {
+ ok(!called, "listener called only once");
+ equal(type, "foo", "event type is properly passed");
+ equal(value, "bar", "correct argument was passed");
+ equal(
+ this,
+ object,
+ "the contextual object is correct for object listener"
+ );
+ },
+ };
+
+ const pFoo = once(target, "foo", object);
+
+ const pDone = once(target, "done");
+
+ emit(target, "foo", "bar");
+ emit(target, "foo", "baz");
+ emit(target, "done", "");
+
+ await Promise.all([pFoo, pDone]);
+ },
+
+ testCallingOffWithMoreThan3Args() {
+ const target = { name: "target" };
+ on(target, "data", fail);
+ off(target, "data", fail, undefined);
+ emit(target, "data", "Listener should be removed");
+ },
+};
+
+/**
+ * Create a runnable tests based on the tests descriptor given.
+ *
+ * @param {Object} tests
+ * The tests descriptor object, contains the tests to run.
+ */
+const runnable = tests =>
+ async function () {
+ for (const name of Object.keys(tests)) {
+ info(name);
+ if (tests[name].length === 1) {
+ await new Promise(resolve => tests[name](resolve));
+ } else {
+ await tests[name]();
+ }
+ }
+ };
+
+add_task(runnable(TESTS));