Bug 1310608 - Add a Rep for Promises. r=Honza;
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Mon, 24 Oct 2016 19:24:10 +0200
changeset 320056 b7e94b82004ec38beaf69fce2176433745b277c7
parent 320055 bcccdc43232ba3b6b44924605e98bff5e04af64f
child 320057 657c36b85c688b9f77a79f796157edf87bc75a86
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1310608
milestone52.0a1
Bug 1310608 - Add a Rep for Promises. r=Honza; MozReview-Commit-ID: BkeHonHQ28M
devtools/client/shared/components/reps/moz.build
devtools/client/shared/components/reps/promise.js
devtools/client/shared/components/reps/rep.js
devtools/client/shared/components/test/mochitest/chrome.ini
devtools/client/shared/components/test/mochitest/test_reps_promise.html
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -18,16 +18,17 @@ DevToolsModules(
     'grip.js',
     'infinity.js',
     'nan.js',
     'null.js',
     'number.js',
     'object-with-text.js',
     'object-with-url.js',
     'object.js',
+    'promise.js',
     'prop-rep.js',
     'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
     'string.js',
     'stylesheet.js',
     'symbol.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/promise.js
@@ -0,0 +1,111 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+  // Dependencies
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { PropRep } = createFactories(require("./prop-rep"));
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a DOM Promise object.
+   */
+  const PromiseRep = React.createClass({
+    displayName: "Promise",
+
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    getTitle: function (object) {
+      const title = object.class;
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: object
+        }, title);
+      }
+      return title;
+    },
+
+    getProps: function (promiseState) {
+      const keys = ["state"];
+      if (Object.keys(promiseState).includes("value")) {
+        keys.push("value");
+      }
+
+      return keys.map((key, i) => {
+        return PropRep(Object.assign({}, this.props, {
+          mode: "tiny",
+          name: `<${key}>`,
+          object: promiseState[key],
+          equal: ": ",
+          delim: i < keys.length - 1 ? ", " : ""
+        }));
+      });
+    },
+
+    render: function () {
+      const object = this.props.object;
+      const {promiseState} = object;
+      let objectLink = this.props.objectLink || span;
+
+      if (this.props.mode == "tiny") {
+        let { Rep } = createFactories(require("./rep"));
+
+        return (
+          span({className: "objectBox objectBox-object"},
+            this.getTitle(object),
+            objectLink({
+              className: "objectLeftBrace",
+              object: object
+            }, " { "),
+            Rep({object: promiseState.state}),
+            objectLink({
+              className: "objectRightBrace",
+              object: object
+            }, " }")
+          )
+        );
+      }
+
+      const props = this.getProps(promiseState);
+      return (
+        span({className: "objectBox objectBox-object"},
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            object: object
+          }, " { "),
+          ...props,
+          objectLink({
+            className: "objectRightBrace",
+            object: object
+          }, " }")
+        )
+      );
+    },
+  });
+
+  // Registration
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+    return type === "Promise";
+  }
+
+  // Exports from this module
+  exports.PromiseRep = {
+    rep: PromiseRep,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -25,16 +25,17 @@ define(function (require, exports, modul
   const { NaNRep } = require("./nan");
 
   // DOM types (grips)
   const { Attribute } = require("./attribute");
   const { DateTime } = require("./date-time");
   const { Document } = require("./document");
   const { Event } = require("./event");
   const { Func } = require("./function");
+  const { PromiseRep } = require("./promise");
   const { RegExp } = require("./regexp");
   const { StyleSheet } = require("./stylesheet");
   const { CommentNode } = require("./comment-node");
   const { TextNode } = require("./text-node");
   const { Window } = require("./window");
   const { ObjectWithText } = require("./object-with-text");
   const { ObjectWithURL } = require("./object-with-url");
   const { GripArray } = require("./grip-array");
@@ -48,16 +49,17 @@ define(function (require, exports, modul
     RegExp,
     StyleSheet,
     Event,
     DateTime,
     CommentNode,
     TextNode,
     Attribute,
     Func,
+    PromiseRep,
     ArrayRep,
     Document,
     Window,
     ObjectWithText,
     ObjectWithURL,
     GripArray,
     GripMap,
     Grip,
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -19,16 +19,17 @@ support-files =
 [test_reps_grip-map.html]
 [test_reps_infinity.html]
 [test_reps_nan.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
+[test_reps_promise.html]
 [test_reps_regexp.html]
 [test_reps_string.html]
 [test_reps_stylesheet.html]
 [test_reps_symbol.html]
 [test_reps_text-node.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
 [test_sidebar_toggle.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_promise.html
@@ -0,0 +1,331 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test Promise rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - Promise</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";
+
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { PromiseRep } = browserRequire("devtools/client/shared/components/reps/promise");
+
+  const componentUnderTest = PromiseRep;
+
+  try {
+    yield testPending();
+    yield testFulfilledWithNumber();
+    yield testFulfilledWithString();
+    yield testFulfilledWithObject();
+    yield testFulfilledWithArray();
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testPending() {
+    // Test object = `new Promise((resolve, reject) => true)`
+    const stub = getGripStub("testPending");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${PromiseRep.rep.displayName} for pending Promise`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "pending" }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "pending" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testPending", componentUnderTest, stub);
+  }
+  function testFulfilledWithNumber() {
+    // Test object = `Promise.resolve(42)`
+    const stub = getGripStub("testFulfilledWithNumber");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with a number`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: 42 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithNumber", componentUnderTest, stub);
+  }
+  function testFulfilledWithString() {
+    // Test object = `Promise.resolve("foo")`
+    const stub = getGripStub("testFulfilledWithString");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with a string`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: "foo" }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithString", componentUnderTest, stub);
+  }
+
+  function testFulfilledWithObject() {
+    // Test object = `Promise.resolve({foo: "bar", baz: "boo"})`
+    const stub = getGripStub("testFulfilledWithObject");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with an object`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: Object }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithObject", componentUnderTest, stub);
+  }
+
+  function testFulfilledWithArray() {
+    // Test object = `Promise.resolve([1,2,3])`
+    const stub = getGripStub("testFulfilledWithArray");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with an array`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: [3] }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithArray", componentUnderTest, stub);
+  }
+
+  function getGripStub(name) {
+    switch (name) {
+      case "testPending":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj54",
+          "class": "Promise",
+          "promiseState": {
+            "state": "pending",
+            "creationTimestamp": 1477327760242.5752
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithNumber":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj55",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": 42,
+            "creationTimestamp": 1477327760242.721,
+            "timeToSettle": 0.018497000000479602
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithString":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj56",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": "foo",
+            "creationTimestamp": 1477327760243.2483,
+            "timeToSettle": 0.0019969999998465937
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithObject":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj59",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj60",
+              "class": "Object",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 2
+            },
+            "creationTimestamp": 1477327760243.2214,
+            "timeToSettle": 0.002035999999861815
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithArray":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj57",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj58",
+              "class": "Array",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 4,
+              "preview": {
+                "kind": "ArrayLike",
+                "length": 3
+              }
+            },
+            "creationTimestamp": 1477327760242.9597,
+            "timeToSettle": 0.006158000000141328
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+    }
+    return null;
+  }
+});
+</script>
+</pre>
+</body>
+</html>