tor-browser

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

Mochia.js (6880B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /**
      6 * Define Mochia's helpers on the given scope.
      7 */
      8 (() => {
      9  /**
     10   * The context of each test suite.
     11   */
     12  class Context {
     13    static #stack = [];
     14 
     15    static current() {
     16      return Context.#stack.at(-1);
     17    }
     18 
     19    static push(ctx) {
     20      Context.#stack.push(ctx);
     21    }
     22 
     23    static pop() {
     24      Context.#stack.pop();
     25    }
     26 
     27    constructor() {
     28      this.description = [];
     29      this.beforeEach = [];
     30      this.afterEach = [];
     31    }
     32 
     33    clone() {
     34      const newCtx = new Context();
     35      newCtx.description.push(...this.description);
     36      newCtx.beforeEach.push(...this.beforeEach);
     37      newCtx.afterEach.push(...this.afterEach);
     38      return newCtx;
     39    }
     40  }
     41 
     42  Context.push(new Context());
     43 
     44  let _testScope = null;
     45 
     46  /**
     47   * @typedef {void|Promise<void>} MaybePromise
     48   *
     49   * Either undefined or a Promise that resolves to undefined.
     50   */
     51 
     52  const MochiaImpl = {
     53    /**
     54     * Describe a new test suite, which is a scoped environment for running setup
     55     * and teardown.
     56     *
     57     * @param {string} desc
     58     *        A description of the test suite.
     59     *
     60     * @param {function(): MaybePromise} suite
     61     *        The test suite
     62     */
     63    async describe(desc, suite) {
     64      const ctx = Context.current().clone();
     65      ctx.description.push(desc);
     66 
     67      Context.push(ctx);
     68 
     69      const p = suite();
     70      if (p?.then) {
     71        await p;
     72      }
     73 
     74      Context.pop();
     75    },
     76 
     77    /**
     78     * Register a setup funciton to run before each test.
     79     *
     80     * Multiple functions can be registered with `beforeEach` and they will be run
     81     * in order before each test in the suite and the suites nested inside of it.
     82     *
     83     * @param {function(): MaybePromise} setupFn
     84     *        The setup function. If this function returns a `Promise` it will be
     85     *        awaited.
     86     */
     87    beforeEach(setupFn) {
     88      Context.current().beforeEach.push(setupFn);
     89    },
     90 
     91    /**
     92     * Register a tear down function to run at the end of each test.
     93     *
     94     * Multiple functions can be registered with `afterEach` and they will run in
     95     * reverse order after each test in the suite and the suites nested inside of it.
     96     *
     97     * @param {function(): MaybePromise} tearDownFn
     98     *        The tear down function. If this function returns a `Promise` it will
     99     *        be awaited.
    100     */
    101    afterEach(tearDownFn) {
    102      Context.current().afterEach.push(tearDownFn);
    103    },
    104 
    105    /**
    106     * Register a test function.
    107     *
    108     * The test will be registered via `add_task`. Each setup function registered
    109     * before this function call will be called in order before the actual test
    110     * and each teardown function before this function will be called in reverse
    111     * order after the actual test.
    112     *
    113     * @param {string} desc
    114     *        A description of the test. This is logged at the start of the test.
    115     *
    116     * @param {function(): MaybePromise} testFn
    117     *        The test function. If this function returns a `Promise` it will be
    118     *        awaited.
    119     *
    120     * @returns {any}
    121     *          The result of calling `add_task` with the wrapped function.
    122     */
    123    it(desc, testFn) {
    124      return _testScope.add_task(MochiaImpl.wrap(desc, testFn));
    125    },
    126 
    127    /**
    128     * Register a test that will be the only test run.
    129     *
    130     * @param {string} desc
    131     *        A description of the test. This is logged at the start of the test.
    132     *
    133     * @param {function(): MaybePromise} testFn
    134     *        The test function. If this function returns a `Promise` it will be
    135     *        awaited.
    136     */
    137    only(desc, testFn) {
    138      MochiaImpl.it(desc, testFn).only();
    139    },
    140 
    141    /**
    142     * Register a test that will be skipped.
    143     *
    144     * @param {string} desc
    145     *        A description of the test. This is logged at the start of the test.
    146     *
    147     * @param {function(): MaybePromise} testFn
    148     *        The test function. If this function returns a `Promise` it will be
    149     *        awaited.
    150     */
    151    skip(desc, testFn) {
    152      MochiaImpl.it(desc, testFn).skip();
    153    },
    154 
    155    /**
    156     * Register a test that will be skipped if the provided predicate evaluates to
    157     * a truthy value.
    158     *
    159     * @param {string} desc
    160     *        A description of the test. This is logged at the start of the test.
    161     *
    162     * @param {function(): boolean} skipFn
    163     *        A predicate that will be called by the test harness to determine
    164     *        whether or not the test should be skipped.
    165     *
    166     * @param {function(): MaybePromise} testFn
    167     *        The test function. If this function returns a `Promise` it will be
    168     *        awaited.
    169     *
    170     * @returns {any}
    171     *          The result of calling `add_task` with the wrapped function.
    172     */
    173    skipIf(desc, skipFn, testFn) {
    174      return _testScope.add_task(
    175        { skip_if: skipFn },
    176        MochiaImpl.wrap(desc, testFn)
    177      );
    178    },
    179 
    180    /**
    181     * Wrap `fn` so that all the setup functions declared with `beforeEach` are
    182     * called before it and all the teardown functions declared with `afterEach`
    183     * are called after.
    184     *
    185     * @param {string} desc
    186     *        A description of the test.
    187     *
    188     * @param {function(): MaybePromise} fn
    189     *        The function to wrap.
    190     *
    191     * @returns {function(): Promise<void>}
    192     *          The wrapped function.
    193     */
    194    wrap(desc, fn) {
    195      const ctx = Context.current().clone();
    196      const name = [...ctx.description, desc].join(" / ");
    197 
    198      // This is a hack to give the function an implicit name.
    199      const wrapper = {
    200        [name]: async () => {
    201          _testScope.info(name);
    202 
    203          for (const before of ctx.beforeEach) {
    204            const p = before();
    205            if (p?.then) {
    206              await p;
    207            }
    208          }
    209 
    210          {
    211            const p = fn();
    212            if (p?.then) {
    213              await p;
    214            }
    215          }
    216 
    217          for (let i = ctx.afterEach.length - 1; i >= 0; i--) {
    218            const after = ctx.afterEach[i];
    219            const p = after();
    220            if (p?.then) {
    221              await p;
    222            }
    223          }
    224        },
    225      };
    226 
    227      return wrapper[name];
    228    },
    229  };
    230 
    231  Object.defineProperties(MochiaImpl.it, {
    232    only: {
    233      configurable: false,
    234      value: MochiaImpl.only,
    235    },
    236    skip: {
    237      configurable: false,
    238      value: MochiaImpl.skip,
    239    },
    240    skipIf: {
    241      configurable: false,
    242      value: MochiaImpl.skipIf,
    243    },
    244  });
    245 
    246  _testScope = this;
    247 
    248  Object.assign(_testScope, {
    249    describe: MochiaImpl.describe,
    250    beforeEach: MochiaImpl.beforeEach,
    251    afterEach: MochiaImpl.afterEach,
    252    it: MochiaImpl.it,
    253  });
    254 })();