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 }