tor-browser

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

browser_webconsole_console_table.js (13645B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Check console.table calls with all the test cases shown
      7 // in the MDN doc (https://developer.mozilla.org/en-US/docs/Web/API/Console/table)
      8 
      9 const TEST_URI =
     10  "http://example.com/browser/devtools/client/webconsole/" +
     11  "test/browser/test-console-table.html";
     12 
     13 add_task(async function () {
     14  const hud = await openNewTabAndConsole(TEST_URI);
     15 
     16  function Person(firstName, lastName) {
     17    this.firstName = firstName;
     18    this.lastName = lastName;
     19  }
     20 
     21  const holeyArray = [];
     22  holeyArray[1] = "apples";
     23  holeyArray[3] = "oranges";
     24  holeyArray[6] = "bananas";
     25 
     26  const testCases = [
     27    {
     28      info: "Testing when data argument is an array",
     29      input: ["apples", "oranges", "bananas"],
     30      expected: {
     31        columns: ["(index)", "Values"],
     32        rows: [
     33          ["0", "apples"],
     34          ["1", "oranges"],
     35          ["2", "bananas"],
     36        ],
     37      },
     38    },
     39    {
     40      info: "Testing when data argument is an holey array",
     41      input: holeyArray,
     42      expected: {
     43        columns: ["(index)", "Values"],
     44        rows: [
     45          ["0", ""],
     46          ["1", "apples"],
     47          ["2", ""],
     48          ["3", "oranges"],
     49          ["4", ""],
     50          ["5", ""],
     51          ["6", "bananas"],
     52        ],
     53      },
     54    },
     55    {
     56      info: "Testing when data argument has holey array",
     57      // eslint-disable-next-line no-sparse-arrays
     58      input: [[1, , 2]],
     59      expected: {
     60        columns: ["(index)", "0", "1", "2"],
     61        rows: [["0", "1", "", "2"]],
     62      },
     63    },
     64    {
     65      info: "Testing when data argument is an object",
     66      input: new Person("John", "Smith"),
     67      expected: {
     68        columns: ["(index)", "Values"],
     69        rows: [
     70          ["firstName", "John"],
     71          ["lastName", "Smith"],
     72        ],
     73      },
     74    },
     75    {
     76      info: "Testing when data argument is an array of arrays",
     77      input: [
     78        ["Jane", "Doe"],
     79        ["Emily", "Jones"],
     80      ],
     81      expected: {
     82        columns: ["(index)", "0", "1"],
     83        rows: [
     84          ["0", "Jane", "Doe"],
     85          ["1", "Emily", "Jones"],
     86        ],
     87      },
     88    },
     89    {
     90      info: "Testing when data argument is an array of objects",
     91      input: [
     92        new Person("Jack", "Foo"),
     93        new Person("Emma", "Bar"),
     94        new Person("Michelle", "Rax"),
     95      ],
     96      expected: {
     97        columns: ["(index)", "firstName", "lastName"],
     98        rows: [
     99          ["0", "Jack", "Foo"],
    100          ["1", "Emma", "Bar"],
    101          ["2", "Michelle", "Rax"],
    102        ],
    103      },
    104    },
    105    {
    106      info: "Testing when data argument is an object whose properties are objects",
    107      input: {
    108        father: new Person("Darth", "Vader"),
    109        daughter: new Person("Leia", "Organa"),
    110        son: new Person("Luke", "Skywalker"),
    111      },
    112      expected: {
    113        columns: ["(index)", "firstName", "lastName"],
    114        rows: [
    115          ["father", "Darth", "Vader"],
    116          ["daughter", "Leia", "Organa"],
    117          ["son", "Luke", "Skywalker"],
    118        ],
    119      },
    120    },
    121    {
    122      info: "Testing when data argument is a Set",
    123      input: new Set(["a", "b", "c"]),
    124      expected: {
    125        columns: ["(iteration index)", "Values"],
    126        rows: [
    127          ["0", "a"],
    128          ["1", "b"],
    129          ["2", "c"],
    130        ],
    131      },
    132    },
    133    {
    134      info: "Testing when data argument is a Map",
    135      input: new Map([
    136        ["key-a", "value-a"],
    137        ["key-b", "value-b"],
    138      ]),
    139      expected: {
    140        columns: ["(iteration index)", "Key", "Values"],
    141        rows: [
    142          ["0", "key-a", "value-a"],
    143          ["1", "key-b", "value-b"],
    144        ],
    145      },
    146    },
    147    {
    148      info: "Testing when data argument is a Int8Array",
    149      input: new Int8Array([1, 2, 3, 4]),
    150      expected: {
    151        columns: ["(index)", "Values"],
    152        rows: [
    153          ["0", "1"],
    154          ["1", "2"],
    155          ["2", "3"],
    156          ["3", "4"],
    157        ],
    158      },
    159    },
    160    {
    161      info: "Testing when data argument is a Uint8Array",
    162      input: new Uint8Array([1, 2, 3, 4]),
    163      expected: {
    164        columns: ["(index)", "Values"],
    165        rows: [
    166          ["0", "1"],
    167          ["1", "2"],
    168          ["2", "3"],
    169          ["3", "4"],
    170        ],
    171      },
    172    },
    173    {
    174      info: "Testing when data argument is a Int16Array",
    175      input: new Int16Array([1, 2, 3, 4]),
    176      expected: {
    177        columns: ["(index)", "Values"],
    178        rows: [
    179          ["0", "1"],
    180          ["1", "2"],
    181          ["2", "3"],
    182          ["3", "4"],
    183        ],
    184      },
    185    },
    186    {
    187      info: "Testing when data argument is a Uint16Array",
    188      input: new Uint16Array([1, 2, 3, 4]),
    189      expected: {
    190        columns: ["(index)", "Values"],
    191        rows: [
    192          ["0", "1"],
    193          ["1", "2"],
    194          ["2", "3"],
    195          ["3", "4"],
    196        ],
    197      },
    198    },
    199    {
    200      info: "Testing when data argument is a Int32Array",
    201      input: new Int32Array([1, 2, 3, 4]),
    202      expected: {
    203        columns: ["(index)", "Values"],
    204        rows: [
    205          ["0", "1"],
    206          ["1", "2"],
    207          ["2", "3"],
    208          ["3", "4"],
    209        ],
    210      },
    211    },
    212    {
    213      info: "Testing when data argument is a Uint32Array",
    214      input: new Uint32Array([1, 2, 3, 4]),
    215      expected: {
    216        columns: ["(index)", "Values"],
    217        rows: [
    218          ["0", "1"],
    219          ["1", "2"],
    220          ["2", "3"],
    221          ["3", "4"],
    222        ],
    223      },
    224    },
    225    {
    226      info: "Testing when data argument is a Float32Array",
    227      input: new Float32Array([1, 2, 3, 4]),
    228      expected: {
    229        columns: ["(index)", "Values"],
    230        rows: [
    231          ["0", "1"],
    232          ["1", "2"],
    233          ["2", "3"],
    234          ["3", "4"],
    235        ],
    236      },
    237    },
    238    {
    239      info: "Testing when data argument is a Float64Array",
    240      input: new Float64Array([1, 2, 3, 4]),
    241      expected: {
    242        columns: ["(index)", "Values"],
    243        rows: [
    244          ["0", "1"],
    245          ["1", "2"],
    246          ["2", "3"],
    247          ["3", "4"],
    248        ],
    249      },
    250    },
    251    {
    252      info: "Testing when data argument is a Uint8ClampedArray",
    253      input: new Uint8ClampedArray([1, 2, 3, 4]),
    254      expected: {
    255        columns: ["(index)", "Values"],
    256        rows: [
    257          ["0", "1"],
    258          ["1", "2"],
    259          ["2", "3"],
    260          ["3", "4"],
    261        ],
    262      },
    263    },
    264    {
    265      info: "Testing when data argument is a BigInt64Array",
    266      // eslint-disable-next-line no-undef
    267      input: new BigInt64Array([1n, 2n, 3n, 4n]),
    268      expected: {
    269        columns: ["(index)", "Values"],
    270        rows: [
    271          ["0", "1n"],
    272          ["1", "2n"],
    273          ["2", "3n"],
    274          ["3", "4n"],
    275        ],
    276      },
    277    },
    278    {
    279      info: "Testing when data argument is a BigUint64Array",
    280      // eslint-disable-next-line no-undef
    281      input: new BigUint64Array([1n, 2n, 3n, 4n]),
    282      expected: {
    283        columns: ["(index)", "Values"],
    284        rows: [
    285          ["0", "1n"],
    286          ["1", "2n"],
    287          ["2", "3n"],
    288          ["3", "4n"],
    289        ],
    290      },
    291    },
    292    {
    293      info: "Testing restricting the columns displayed",
    294      input: [new Person("Sam", "Wright"), new Person("Elena", "Bartz")],
    295      headers: ["firstName"],
    296      expected: {
    297        columns: ["(index)", "firstName"],
    298        rows: [
    299          ["0", "Sam"],
    300          ["1", "Elena"],
    301        ],
    302      },
    303    },
    304    {
    305      info: "Testing nested object with falsy values",
    306      input: [
    307        { a: null, b: false, c: undefined, d: 0 },
    308        { b: null, c: false, d: undefined, e: 0 },
    309      ],
    310      expected: {
    311        columns: ["(index)", "a", "b", "c", "d", "e"],
    312        rows: [
    313          ["0", "null", "false", "undefined", "0", ""],
    314          ["1", "", "null", "false", "undefined", "0"],
    315        ],
    316      },
    317    },
    318    {
    319      info: "Testing invalid headers",
    320      input: ["apples", "oranges", "bananas"],
    321      headers: [[]],
    322      expected: {
    323        columns: ["(index)", "Values"],
    324        rows: [
    325          ["0", "apples"],
    326          ["1", "oranges"],
    327          ["2", "bananas"],
    328        ],
    329      },
    330    },
    331    {
    332      info: "Testing overflow-y",
    333      input: Array.from({ length: 50 }, (_, i) => `item-${i}`),
    334      expected: {
    335        columns: ["(index)", "Values"],
    336        rows: Array.from({ length: 50 }, (_, i) => [i.toString(), `item-${i}`]),
    337        overflow: true,
    338      },
    339    },
    340    {
    341      info: "Testing table with expandable objects",
    342      input: [{ a: { b: 34 } }],
    343      expected: {
    344        columns: ["(index)", "a"],
    345        rows: [["0", "Object { b: 34 }"]],
    346      },
    347      async additionalTest(node) {
    348        info("Check that object in a cell can be expanded");
    349        const objectNode = node.querySelector(".tree .node");
    350        objectNode.click();
    351        await waitFor(() => node.querySelectorAll(".tree .node").length === 3);
    352        const nodes = node.querySelectorAll(".tree .node");
    353        ok(nodes[1].textContent.includes("b: 34"));
    354        ok(nodes[2].textContent.includes("<prototype>"));
    355      },
    356    },
    357    {
    358      info: "Testing max columns",
    359      input: [
    360        Array.from({ length: 30 }).reduce((acc, _, i) => {
    361          return {
    362            ...acc,
    363            ["item" + i]: i,
    364          };
    365        }, {}),
    366      ],
    367      expected: {
    368        // We show 21 columns at most
    369        columns: [
    370          "(index)",
    371          ...Array.from({ length: 20 }, (_, i) => `item${i}`),
    372        ],
    373        rows: [[0, ...Array.from({ length: 20 }, (_, i) => i)]],
    374      },
    375    },
    376    {
    377      info: "Testing performance entries",
    378      evalInput: true,
    379      input: `performance.getEntriesByType("navigation")`,
    380      headers: [
    381        "name",
    382        "entryType",
    383        "initiatorType",
    384        "connectStart",
    385        "connectEnd",
    386        "fetchStart",
    387      ],
    388      expected: {
    389        columns: [
    390          "(index)",
    391          "initiatorType",
    392          "fetchStart",
    393          "connectStart",
    394          "connectEnd",
    395          "name",
    396          "entryType",
    397        ],
    398        rows: [[0, "navigation", /\d+/, /\d+/, /\d+/, TEST_URI, "navigation"]],
    399      },
    400    },
    401    {
    402      // Bug 1953942
    403      info: "Testing with array of Symbols",
    404      evalInput: true,
    405      input: `[Symbol("apricots"), Symbol("pears"), Symbol("raspberries")]`,
    406      expected: {
    407        columns: ["(index)", "Values"],
    408        rows: [
    409          ["0", `Symbol("apricots")`],
    410          ["1", `Symbol("pears")`],
    411          ["2", `Symbol("raspberries")`],
    412        ],
    413      },
    414    },
    415  ];
    416 
    417  await SpecialPowers.spawn(
    418    gBrowser.selectedBrowser,
    419    [
    420      testCases.map(({ input, evalInput, headers }) => ({
    421        input,
    422        evalInput,
    423        headers,
    424      })),
    425    ],
    426    function (tests) {
    427      tests.forEach(test => {
    428        let { input, headers, evalInput } = test;
    429        if (evalInput) {
    430          input = content.wrappedJSObject.eval(input);
    431        }
    432        content.wrappedJSObject.doConsoleTable(input, headers);
    433      });
    434    }
    435  );
    436  const messages = await waitFor(async () => {
    437    const msgs = await findAllMessagesVirtualized(hud);
    438    if (msgs.length === testCases.length) {
    439      return msgs;
    440    }
    441    return null;
    442  });
    443  for (const [index, testCase] of testCases.entries()) {
    444    // Refresh the reference to the message, as it may have been scrolled out of existence.
    445    const node = await findMessageVirtualizedById({
    446      hud,
    447      messageId: messages[index].getAttribute("data-message-id"),
    448    });
    449    await testItem(testCase, node.querySelector(".consoletable"));
    450  }
    451 });
    452 
    453 async function testItem(testCase, tableNode) {
    454  info(testCase.info);
    455 
    456  const ths = Array.from(tableNode.querySelectorAll("th"));
    457  const trs = Array.from(tableNode.querySelectorAll("tbody tr"));
    458 
    459  is(
    460    JSON.stringify(ths.map(column => column.textContent)),
    461    JSON.stringify(testCase.expected.columns),
    462    `${testCase.info} | table has the expected columns`
    463  );
    464 
    465  is(
    466    trs.length,
    467    testCase.expected.rows.length,
    468    `${testCase.info} | table has the expected number of rows`
    469  );
    470 
    471  testCase.expected.rows.forEach((expectedRow, rowIndex) => {
    472    const rowCells = Array.from(trs[rowIndex].querySelectorAll("td")).map(
    473      x => x.textContent
    474    );
    475 
    476    const isRegex = x => x && x.constructor.name === "RegExp";
    477    const hasRegExp = expectedRow.find(isRegex);
    478    if (hasRegExp) {
    479      is(
    480        rowCells.length,
    481        expectedRow.length,
    482        `${testCase.info} | row ${rowIndex} has the expected number of cell`
    483      );
    484      rowCells.forEach((cell, i) => {
    485        const expected = expectedRow[i];
    486        const info = `${testCase.info} | row ${rowIndex} cell ${i} has the expected content`;
    487 
    488        if (isRegex(expected)) {
    489          ok(expected.test(cell), info);
    490        } else {
    491          is(cell, `${expected}`, info);
    492        }
    493      });
    494    } else {
    495      is(
    496        rowCells.join(" | "),
    497        expectedRow.join(" | "),
    498        `${testCase.info} | row has the expected content`
    499      );
    500    }
    501  });
    502 
    503  if (testCase.expected.overflow) {
    504    ok(
    505      tableNode.isConnected,
    506      "Node must be connected to test overflow. It is likely scrolled out of view."
    507    );
    508    const tableWrapperNode = tableNode.closest(".consoletable-wrapper");
    509    Assert.greater(
    510      tableWrapperNode.scrollHeight,
    511      tableWrapperNode.clientHeight,
    512      testCase.info + " table overflows"
    513    );
    514    Assert.notStrictEqual(
    515      getComputedStyle(tableWrapperNode).overflowY,
    516      "hidden",
    517      "table can be scrolled"
    518    );
    519  }
    520 
    521  if (typeof testCase.additionalTest === "function") {
    522    await testCase.additionalTest(tableNode);
    523  }
    524 }