tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_eventemitter_basic.js (9883B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const {
      7  ConsoleAPIListener,
      8 } = require("resource://devtools/server/actors/webconsole/listeners/console-api.js");
      9 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
     10 const hasMethod = (target, method) =>
     11  method in target && typeof target[method] === "function";
     12 
     13 /**
     14 * Each method of this object is a test; tests can be synchronous or asynchronous:
     15 *
     16 * 1. Plain functions are synchronous tests.
     17 * 2. methods with `async` keyword are asynchronous tests.
     18 * 3. methods with `done` as argument are asynchronous tests (`done` needs to be called to
     19 *    finish the test).
     20 */
     21 const TESTS = {
     22  testEventEmitterCreation() {
     23    const emitter = getEventEmitter();
     24    const isAnEmitter = emitter instanceof EventEmitter;
     25 
     26    ok(emitter, "We have an event emitter");
     27    ok(
     28      hasMethod(emitter, "on") &&
     29        hasMethod(emitter, "off") &&
     30        hasMethod(emitter, "once") &&
     31        hasMethod(emitter, "count") &&
     32        !hasMethod(emitter, "decorate"),
     33      `Event Emitter ${
     34        isAnEmitter ? "instance" : "mixin"
     35      } has the expected methods.`
     36    );
     37  },
     38 
     39  testEmittingEvents(done) {
     40    const emitter = getEventEmitter();
     41 
     42    let beenHere1 = false;
     43    let beenHere2 = false;
     44 
     45    function next(str1, str2) {
     46      equal(str1, "abc", "Argument 1 is correct");
     47      equal(str2, "def", "Argument 2 is correct");
     48 
     49      ok(!beenHere1, "first time in next callback");
     50      beenHere1 = true;
     51 
     52      emitter.off("next", next);
     53 
     54      emitter.emit("next");
     55 
     56      emitter.once("onlyonce", onlyOnce);
     57 
     58      emitter.emit("onlyonce");
     59      emitter.emit("onlyonce");
     60    }
     61 
     62    function onlyOnce() {
     63      ok(!beenHere2, '"once" listener has been called once');
     64      beenHere2 = true;
     65      emitter.emit("onlyonce");
     66 
     67      done();
     68    }
     69 
     70    emitter.on("next", next);
     71    emitter.emit("next", "abc", "def");
     72  },
     73 
     74  testThrowingExceptionInListener(done) {
     75    const emitter = getEventEmitter();
     76    const listener = new ConsoleAPIListener(null, message => {
     77      equal(message.level, "error");
     78      const [arg] = message.arguments;
     79      equal(arg.message, "foo");
     80      equal(arg.stack, "bar");
     81      listener.destroy();
     82      done();
     83    });
     84 
     85    listener.init();
     86 
     87    function throwListener() {
     88      emitter.off("throw-exception");
     89      const err = new Error("foo");
     90      err.stack = "bar";
     91      throw err;
     92    }
     93 
     94    emitter.on("throw-exception", throwListener);
     95    emitter.emit("throw-exception");
     96  },
     97 
     98  testKillItWhileEmitting(done) {
     99    const emitter = getEventEmitter();
    100 
    101    const c1 = () => ok(true, "c1 called");
    102    const c2 = () => {
    103      ok(true, "c2 called");
    104      emitter.off("tick", c3);
    105    };
    106    const c3 = () => ok(false, "c3 should not be called");
    107    const c4 = () => {
    108      ok(true, "c4 called");
    109      done();
    110    };
    111 
    112    emitter.on("tick", c1);
    113    emitter.on("tick", c2);
    114    emitter.on("tick", c3);
    115    emitter.on("tick", c4);
    116 
    117    emitter.emit("tick");
    118  },
    119 
    120  testOffAfterOnce() {
    121    const emitter = getEventEmitter();
    122 
    123    let enteredC1 = false;
    124    const c1 = () => (enteredC1 = true);
    125 
    126    emitter.once("oao", c1);
    127    emitter.off("oao", c1);
    128 
    129    emitter.emit("oao");
    130 
    131    ok(!enteredC1, "c1 should not be called");
    132  },
    133 
    134  testPromise() {
    135    const emitter = getEventEmitter();
    136    const p = emitter.once("thing");
    137 
    138    // Check that the promise is only resolved once event though we
    139    // emit("thing") more than once
    140    let firstCallbackCalled = false;
    141    const check1 = p.then(arg => {
    142      equal(firstCallbackCalled, false, "first callback called only once");
    143      firstCallbackCalled = true;
    144      equal(arg, "happened", "correct arg in promise");
    145      return "rval from c1";
    146    });
    147 
    148    emitter.emit("thing", "happened", "ignored");
    149 
    150    // Check that the promise is resolved asynchronously
    151    let secondCallbackCalled = false;
    152    const check2 = p.then(arg => {
    153      ok(true, "second callback called");
    154      equal(arg, "happened", "correct arg in promise");
    155      secondCallbackCalled = true;
    156      equal(arg, "happened", "correct arg in promise (a second time)");
    157      return "rval from c2";
    158    });
    159 
    160    // Shouldn't call any of the above listeners
    161    emitter.emit("thing", "trashinate");
    162 
    163    // Check that we can still separate events with different names
    164    // and that it works with no parameters
    165    const pfoo = emitter.once("foo");
    166    const pbar = emitter.once("bar");
    167 
    168    const check3 = pfoo.then(arg => {
    169      Assert.strictEqual(arg, undefined, "no arg for foo event");
    170      return "rval from c3";
    171    });
    172 
    173    pbar.then(() => {
    174      ok(false, "pbar should not be called");
    175    });
    176 
    177    emitter.emit("foo");
    178 
    179    equal(secondCallbackCalled, false, "second callback not called yet");
    180 
    181    return Promise.all([check1, check2, check3]).then(args => {
    182      equal(args[0], "rval from c1", "callback 1 done good");
    183      equal(args[1], "rval from c2", "callback 2 done good");
    184      equal(args[2], "rval from c3", "callback 3 done good");
    185    });
    186  },
    187 
    188  testClearEvents() {
    189    const emitter = getEventEmitter();
    190 
    191    const received = [];
    192    const listener = (...args) => received.push(args);
    193 
    194    emitter.on("a", listener);
    195    emitter.on("b", listener);
    196    emitter.on("c", listener);
    197 
    198    emitter.emit("a", 1);
    199    emitter.emit("b", 1);
    200    emitter.emit("c", 1);
    201 
    202    equal(received.length, 3, "the listener was triggered three times");
    203 
    204    emitter.clearEvents();
    205    emitter.emit("a", 1);
    206    emitter.emit("b", 1);
    207    emitter.emit("c", 1);
    208    equal(received.length, 3, "the listener was not called after clearEvents");
    209  },
    210 
    211  testOnReturn() {
    212    const emitter = getEventEmitter();
    213 
    214    let called = false;
    215    const removeOnTest = emitter.on("test", () => {
    216      called = true;
    217    });
    218 
    219    equal(typeof removeOnTest, "function", "`on` returns a function");
    220    removeOnTest();
    221 
    222    emitter.emit("test");
    223    equal(called, false, "event listener wasn't called");
    224  },
    225 
    226  async testEmitAsync() {
    227    const emitter = getEventEmitter();
    228 
    229    let resolve1, resolve2;
    230    emitter.once("test", async () => {
    231      return new Promise(r => {
    232        resolve1 = r;
    233      });
    234    });
    235 
    236    // Adding a listener which doesn't return a promise should trigger a console warning.
    237    emitter.once("test", () => {});
    238 
    239    emitter.once("test", async () => {
    240      return new Promise(r => {
    241        resolve2 = r;
    242      });
    243    });
    244 
    245    info("Emit an event and wait for all listener resolutions");
    246    const onConsoleWarning = onConsoleWarningLogged(
    247      "Listener for event 'test' did not return a promise."
    248    );
    249    const onEmitted = emitter.emitAsync("test");
    250    let resolved = false;
    251    onEmitted.then(() => {
    252      info("emitAsync just resolved");
    253      resolved = true;
    254    });
    255 
    256    info("Waiting for warning message about the second listener");
    257    await onConsoleWarning;
    258 
    259    // Spin the event loop, to ensure that emitAsync did not resolved too early
    260    await new Promise(r => Services.tm.dispatchToMainThread(r));
    261 
    262    ok(resolve1, "event listener has been called");
    263    ok(!resolved, "but emitAsync hasn't resolved yet");
    264 
    265    info("Resolve the first listener function");
    266    resolve1();
    267    ok(!resolved, "emitAsync isn't resolved until all listener resolve");
    268 
    269    info("Resolve the second listener function");
    270    resolve2();
    271 
    272    // emitAsync is only resolved in the next event loop
    273    await new Promise(r => Services.tm.dispatchToMainThread(r));
    274    ok(resolved, "once we resolve all the listeners, emitAsync is resolved");
    275  },
    276 
    277  testCount() {
    278    const emitter = getEventEmitter();
    279 
    280    equal(emitter.count("foo"), 0, "no listeners for 'foo' events");
    281    emitter.on("foo", () => {});
    282    equal(emitter.count("foo"), 1, "listener registered");
    283    emitter.on("foo", () => {});
    284    equal(emitter.count("foo"), 2, "another listener registered");
    285    emitter.off("foo");
    286    equal(emitter.count("foo"), 0, "listeners unregistered");
    287  },
    288 };
    289 
    290 // Wait for the next call to console.warn which includes
    291 // the text passed as argument
    292 function onConsoleWarningLogged(warningMessage) {
    293  return new Promise(resolve => {
    294    const ConsoleAPIStorage = Cc[
    295      "@mozilla.org/consoleAPI-storage;1"
    296    ].getService(Ci.nsIConsoleAPIStorage);
    297 
    298    const observer = subject => {
    299      // This is the first argument passed to console.warn()
    300      const message = subject.wrappedJSObject.arguments[0];
    301      if (message.includes(warningMessage)) {
    302        ConsoleAPIStorage.removeLogEventListener(observer);
    303        resolve();
    304      }
    305    };
    306 
    307    ConsoleAPIStorage.addLogEventListener(
    308      observer,
    309      Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
    310    );
    311  });
    312 }
    313 
    314 /**
    315 * Create a runnable tests based on the tests descriptor given.
    316 *
    317 * @param {object} tests
    318 *  The tests descriptor object, contains the tests to run.
    319 */
    320 const runnable = tests =>
    321  async function () {
    322    for (const name of Object.keys(tests)) {
    323      info(name);
    324      if (tests[name].length === 1) {
    325        await new Promise(resolve => tests[name](resolve));
    326      } else {
    327        await tests[name]();
    328      }
    329    }
    330  };
    331 
    332 // We want to run the same tests for both an instance of `EventEmitter` and an object
    333 // decorate with EventEmitter; therefore we create two strategies (`createNewEmitter` and
    334 // `decorateObject`) and a factory (`getEventEmitter`), where the factory is the actual
    335 // function used in the tests.
    336 
    337 const createNewEmitter = () => new EventEmitter();
    338 const decorateObject = () => EventEmitter.decorate({});
    339 
    340 // First iteration of the tests with a new instance of `EventEmitter`.
    341 let getEventEmitter = createNewEmitter;
    342 add_task(runnable(TESTS));
    343 // Second iteration of the tests with an object decorate using `EventEmitter`
    344 add_task(() => (getEventEmitter = decorateObject));
    345 add_task(runnable(TESTS));