diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/shared/tests/xpcshell/test_eventemitter_static.js | 378 |
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)); |