Bug 1286186 - Reps: Test that array indexes are sorted as numbers. r=honza
☠☠ backed out by 24530e316b12 ☠ ☠
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Wed, 20 Jul 2016 09:30:16 +0100
changeset 330926 b9c789ba01a0cd5c0ddf30b26c283ce41bcf2610
parent 330925 a6c528c3fe72fcd5d2959c08d5163a1e48276433
child 330927 63a62a6c3cddfefae733a428c24def6063d320d1
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonza
bugs1286186
milestone50.0a1
Bug 1286186 - Reps: Test that array indexes are sorted as numbers. r=honza
devtools/client/dom/test/browser.ini
devtools/client/dom/test/browser_dom_array.js
devtools/client/dom/test/head.js
devtools/client/dom/test/page_array.html
devtools/client/shared/components/test/mochitest/.eslintrc
devtools/client/shared/components/test/mochitest/head.js
devtools/client/shared/components/test/mochitest/test_reps_array.html
devtools/client/shared/components/tree/label-cell.js
--- a/devtools/client/dom/test/browser.ini
+++ b/devtools/client/dom/test/browser.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
+  page_array.html
   page_basic.html
   !/devtools/client/framework/test/shared-head.js
 
+[browser_dom_array.js]
 [browser_dom_basic.js]
 [browser_dom_refresh.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_array.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_array.html";
+const TEST_ARRAY = [
+  "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+  "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+];
+
+/**
+ * Basic test that checks content of the DOM panel.
+ */
+add_task(function* () {
+  info("Test DOM Panel Array Expansion started");
+
+  let { panel } = yield addTestTab(TEST_PAGE_URL);
+
+  // Expand specified row and wait till children are displayed.
+  yield expandRow(panel, "_a");
+
+  // Verify that children is displayed now.
+  let childRows = getAllRowsForLabel(panel, "_a");
+
+  let item = childRows.pop();
+  is(item.name, "length", "length property is correct");
+  is(item.value, 26, "length property value is 26");
+
+  let i = 0;
+  for (let name in childRows) {
+    let row = childRows[name];
+
+    is(name, i++, `index ${name} is correct and sorted into the correct position`);
+    ok(typeof row.name === "number", "array index is displayed as a number");
+    is(TEST_ARRAY[name], row.value, `value for array[${name}] is ${row.value}`);
+  }
+});
--- a/devtools/client/dom/test/head.js
+++ b/devtools/client/dom/test/head.js
@@ -93,16 +93,84 @@ function synthesizeMouseClickSoon(panel,
 function getRowByLabel(panel, text) {
   let doc = panel.panelWin.document;
   let labels = [...doc.querySelectorAll(".treeLabel")];
   let label = labels.find(node => node.textContent == text);
   return label ? label.closest(".treeRow") : null;
 }
 
 /**
+ * Returns the children (tree row text) of the specified object name as an
+ * array.
+ */
+function getAllRowsForLabel(panel, text) {
+  let rootObjectLevel;
+  let node;
+  let result = [];
+  let doc = panel.panelWin.document;
+  let nodes = [...doc.querySelectorAll(".treeLabel")];
+
+  // Find the label (object name) for which we want the children. We remove
+  // nodes from the start of the array until we reach the property. The children
+  // are then at the start of the array.
+  while (true) {
+    node = nodes.shift();
+
+    if (!node || node.textContent === text) {
+      rootObjectLevel = node.getAttribute("data-level");
+      break;
+    }
+  }
+
+  // Return an empty array if the node is not found.
+  if (!node) {
+    return result;
+  }
+
+  // Now get the children.
+  for (node of nodes) {
+    let level = node.getAttribute("data-level");
+
+    if (level > rootObjectLevel) {
+      result.push({
+        name: normalizeTreeValue(node.textContent),
+        value: normalizeTreeValue(node.parentNode.nextElementSibling.textContent)
+      });
+    } else {
+      break;
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Strings in the tree are in the form ""a"" and numbers in the form "1". We
+ * normalize these values by converting ""a"" to "a" and "1" to 1.
+ *
+ * @param  {String} value
+ *         The value to normalize.
+ * @return {String|Number}
+ *         The normalized value.
+ */
+function normalizeTreeValue(value) {
+  if (value === `""`) {
+    return "";
+  }
+  if (value.startsWith(`"`) && value.endsWith(`"`)) {
+    return value.substr(1, value.length - 2);
+  }
+  if (isFinite(value) && parseInt(value, 10) == value) {
+    return parseInt(value, 10);
+  }
+
+  return value;
+}
+
+/**
  * Expands elements with given label and waits till
  * children are received from the backend.
  */
 function expandRow(panel, labelText) {
   let row = getRowByLabel(panel, labelText);
   return synthesizeMouseClickSoon(panel, row).then(() => {
     // Wait till children (properties) are fetched
     // from the backend.
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/page_array.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>DOM Panel Array Expansion Test Page</title>
+  </head>
+  <body>
+  <h2>DOM Panel Array Expansion Test Page</h2>
+  <script type="text/javascript">
+    "use strict";
+    window._a = [
+      "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+      "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+    ];
+  </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/.eslintrc
@@ -0,0 +1,4 @@
+{
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../../.eslintrc.mochitests"
+}
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -1,10 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { Assert } = require("resource://testing-common/Assert.jsm");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
--- a/devtools/client/shared/components/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -9,16 +9,19 @@ Test ArrayRep rep
   <title>Rep test - ArrayRep</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
+"use strict";
+/* import-globals-from head.js */
+
 window.onload = Task.async(function* () {
   let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
   let { ArrayRep } = browserRequire("devtools/client/shared/components/reps/array");
 
   let componentUnderTest = ArrayRep;
   const maxLength = {
     short: 3,
     long: 300
@@ -30,27 +33,31 @@ window.onload = Task.async(function* () 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
 
     // Test that properties are rendered as expected by ItemRep
     yield testNested();
-  } catch(e) {
+
+    yield testArray();
+  } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test that correct rep is chosen
     const stub = [];
     const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ArrayRep.rep, `Rep correctly selects ${ArrayRep.rep.displayName}`);
+    is(renderedRep.type, ArrayRep.rep,
+       `Rep correctly selects ${ArrayRep.rep.displayName}`);
+
 
     // Test rendering
     const defaultOutput = `[]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -205,13 +212,46 @@ window.onload = Task.async(function* () 
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
   }
+
+  function testArray() {
+    let stub = [
+      "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+      "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+    ];
+
+    const defaultOutput = `["a", "b", "c", "d", "e", "f", "g", "h", "i", "j",` +
+                          ` "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",` +
+                          ` "u", "v", "w", "x", "y", "z"]`;
+    const shortOutput = `["a", "b", "c", more...]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: shortOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[26]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: shortOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/tree/label-cell.js
+++ b/devtools/client/shared/components/tree/label-cell.js
@@ -37,19 +37,20 @@ define(function (require, exports, modul
       };
 
       return (
         td({
           className: "treeLabelCell",
           key: "default",
           style: rowStyle},
           span({ className: "treeIcon" }),
-          span({ className: "treeLabel " + member.type + "Label" },
-            member.name
-          )
+          span({
+            className: "treeLabel " + member.type + "Label",
+            "data-level": level
+          }, member.name)
         )
       );
     }
   });
 
   // Exports from this module
   module.exports = LabelCell;
 });