/* 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));