tor-browser

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

helper_events_test_runner.js (9073B)


      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 /* eslint no-unused-vars: [2, {"vars": "local"}] */
      5 /* import-globals-from head.js */
      6 /* import-globals-from helper_diff.js */
      7 "use strict";
      8 
      9 const beautify = require("resource://devtools/shared/jsbeautify/beautify.js");
     10 
     11 loadHelperScript("helper_diff.js");
     12 
     13 /**
     14 * Generator function that runs checkEventsForNode() for each object in the
     15 * TEST_DATA array.
     16 */
     17 async function runEventPopupTests(url, tests) {
     18  const { inspector } = await openInspectorForURL(url);
     19 
     20  await inspector.markup.expandAll();
     21 
     22  for (const test of tests) {
     23    await checkEventsForNode(test, inspector);
     24  }
     25 
     26  // Wait for promises to avoid leaks when running this as a single test.
     27  // We need to do this because we have opened a bunch of popups and don't them
     28  // to affect other test runs when they are GCd.
     29  await promiseNextTick();
     30 }
     31 
     32 /**
     33 * Generator function that takes a selector and expected results and returns
     34 * the event info.
     35 *
     36 * @param {object} test
     37 *  A test object should contain the following properties:
     38 *        - selector {String} a css selector targeting the node to edit
     39 *        - expected {Array} array of expected event objects
     40 *          - type {String} event type
     41 *          - filename {String} filename:line where the evt handler is defined
     42 *          - attributes {Array} array of event attributes ({String})
     43 *          - handler {String} string representation of the handler
     44 *        - beforeTest {Function} (optional) a function to execute on the page
     45 *        before running the test
     46 *        - isSourceMapped {Boolean} (optional) true if the location
     47 *        is source-mapped, requiring some extra delay before the checks
     48 * @param {InspectorPanel} inspector The instance of InspectorPanel currently
     49 * opened
     50 */
     51 async function checkEventsForNode(test, inspector) {
     52  const { selector, expected, beforeTest, isSourceMapped } = test;
     53  const container = await getContainerForSelector(selector, inspector);
     54 
     55  if (typeof beforeTest === "function") {
     56    await beforeTest(inspector);
     57  }
     58 
     59  const evHolder = container.elt.querySelector(
     60    ".inspector-badge.interactive[data-event]"
     61  );
     62 
     63  if (expected.length === 0) {
     64    // If no event is expected, check that event bubble is hidden.
     65    ok(!evHolder, "event bubble should be hidden");
     66    return;
     67  }
     68 
     69  const tooltip = inspector.markup.eventDetailsTooltip;
     70 
     71  await selectNode(selector, inspector);
     72 
     73  let sourceMapPromise = null;
     74  if (isSourceMapped) {
     75    sourceMapPromise = tooltip.once("event-tooltip-source-map-ready");
     76  }
     77 
     78  // Click button to show tooltip
     79  info("Clicking evHolder");
     80  evHolder.scrollIntoView({
     81    block: "center",
     82    inline: "end",
     83    behavior: "instant",
     84  });
     85  EventUtils.synthesizeMouseAtCenter(
     86    evHolder,
     87    {},
     88    inspector.markup.doc.defaultView
     89  );
     90  await tooltip.once("shown");
     91  info("tooltip shown");
     92 
     93  if (isSourceMapped) {
     94    info("Waiting for source map to be applied");
     95    await sourceMapPromise;
     96  }
     97 
     98  // Check values
     99  const headers = tooltip.panel.querySelectorAll(".event-header");
    100  const nodeFront = container.node;
    101  const cssSelector = nodeFront.nodeName + "#" + nodeFront.id;
    102 
    103  for (let i = 0; i < headers.length; i++) {
    104    const label = `${cssSelector}.${expected[i].type} (index ${i})`;
    105    info(`${label} START`);
    106 
    107    const header = headers[i];
    108    const type = header.querySelector(".event-tooltip-event-type");
    109    const filename = header.querySelector(".event-tooltip-filename");
    110    const attributes = Array.from(
    111      header.querySelectorAll(".event-tooltip-attributes")
    112    );
    113    const contentBox = header.nextElementSibling;
    114 
    115    info("Looking for " + type.textContent);
    116 
    117    is(type.textContent, expected[i].type, "type matches for " + cssSelector);
    118    is(
    119      filename.textContent,
    120      expected[i].filename,
    121      "filename matches for " + cssSelector
    122    );
    123 
    124    Assert.deepEqual(
    125      attributes.map(el => el.textContent),
    126      expected[i].attributes,
    127      `we have the expected attributes for "${cssSelector}"`
    128    );
    129 
    130    is(
    131      header.classList.contains("content-expanded"),
    132      false,
    133      "We are not in expanded state"
    134    );
    135 
    136    // Make sure the header is not hidden by scrollbars before clicking.
    137    header.scrollIntoView();
    138 
    139    // Avoid clicking the header's center (could hit the debugger button)
    140    EventUtils.synthesizeMouse(header, 2, 2, {}, type.ownerGlobal);
    141    await tooltip.once("event-tooltip-ready");
    142 
    143    is(
    144      header.classList.contains("content-expanded") &&
    145        contentBox.hasAttribute("open"),
    146      true,
    147      "We are in expanded state and icon changed"
    148    );
    149 
    150    is(
    151      tooltip.panel.querySelectorAll(".event-header.content-expanded")
    152        .length === 1 &&
    153        tooltip.panel.querySelectorAll(".event-tooltip-content-box[open]")
    154          .length === 1,
    155      true,
    156      "Only one event box is expanded at a time"
    157    );
    158 
    159    const editor = tooltip.eventTooltip._eventEditors.get(contentBox).editor;
    160    const tidiedHandler = beautify.js(expected[i].handler, {
    161      indent_size: 2,
    162    });
    163    testDiff(
    164      editor.getText(),
    165      tidiedHandler,
    166      "handler matches for " + cssSelector,
    167      ok
    168    );
    169 
    170    const checkbox = header.querySelector("input[type=checkbox]");
    171    ok(checkbox, "The event toggling checkbox is displayed");
    172    const disabled = checkbox.hasAttribute("disabled");
    173    // We can't disable React/jQuery events at the moment, so ensure that for those,
    174    // the checkbox is disabled.
    175    const shouldBeDisabled =
    176      expected[i].attributes?.includes("React") ||
    177      expected[i].attributes?.includes("jQuery");
    178    Assert.strictEqual(
    179      disabled,
    180      shouldBeDisabled,
    181      `The checkbox is ${shouldBeDisabled ? "disabled" : "enabled"}\n`
    182    );
    183 
    184    info(`${label} END`);
    185  }
    186 
    187  const tooltipHidden = tooltip.once("hidden");
    188  tooltip.hide();
    189  await tooltipHidden;
    190 }
    191 
    192 /**
    193 * This should be kept in sync with the content of the window load event listener callback
    194 * content in doc_markup_events_jquery.html.
    195 */
    196 function getDocMarkupEventsJQueryLoadHandlerText() {
    197  return `
    198          () => {
    199            const handler1 = function liveDivDblClick() {
    200              alert(1);
    201            };
    202            const handler2 = function liveDivDragStart() {
    203              alert(2);
    204            };
    205            const handler3 = function liveDivDragLeave() {
    206              alert(3);
    207            };
    208            const handler4 = function liveDivDragEnd() {
    209              alert(4);
    210            };
    211            const handler5 = function liveDivDrop() {
    212              alert(5);
    213            };
    214            const handler6 = function liveDivDragOver() {
    215              alert(6);
    216            };
    217            const handler7 = function divClick1() {
    218              alert(7);
    219            };
    220            const handler8 = function divClick2() {
    221              alert(8);
    222            };
    223            const handler9 = function divKeyDown() {
    224              alert(9);
    225            };
    226            const handler10 = function divDragOut() {
    227              alert(10);
    228            };
    229 
    230            if ($("#livediv").live) {
    231              $("#livediv").live("dblclick", handler1);
    232              $("#livediv").live("dragstart", handler2);
    233            }
    234 
    235            if ($("#livediv").delegate) {
    236              $(document).delegate("#livediv", "dragleave", handler3);
    237              $(document).delegate("#livediv", "dragend", handler4);
    238            }
    239 
    240            if ($("#livediv").on) {
    241              $(document).on("drop", "#livediv", handler5);
    242              $(document).on("dragover", "#livediv", handler6);
    243              $(document).on("dragout", "#livediv:xxxxx", handler10);
    244            }
    245 
    246            const div = $("div")[0];
    247            $(div).click(handler7);
    248            $(div).click(handler8);
    249            $(div).keydown(handler9);
    250 
    251            class MyClass {
    252              constructor() {
    253                $(document).on("click", '#inclassboundeventdiv', this.onClick.bind(this));
    254              }
    255              onClick() { alert(11); }
    256            }
    257            new MyClass();
    258          }`;
    259 }
    260 
    261 /**
    262 * Create diff of two strings.
    263 *
    264 * @param  {string} text1
    265 *         String to compare with text2.
    266 * @param  {string} text2 [description]
    267 *         String to compare with text1.
    268 * @param  {string} msg
    269 *         Message to display on failure. A diff will be displayed after this
    270 *         message.
    271 */
    272 function testDiff(text1, text2, msg) {
    273  let out = "";
    274 
    275  if (text1 === text2) {
    276    ok(true, msg);
    277    return;
    278  }
    279 
    280  const result = textDiff(text1, text2);
    281 
    282  for (const { atom, operation } of result) {
    283    switch (operation) {
    284      case "add":
    285        out += "+ " + atom + "\n";
    286        break;
    287      case "delete":
    288        out += "- " + atom + "\n";
    289        break;
    290      case "none":
    291        out += "  " + atom + "\n";
    292        break;
    293    }
    294  }
    295 
    296  ok(false, msg + "\nDIFF:\n==========\n" + out + "==========\n");
    297 }