Bug 1289258 - Part 5: Convert component tests to mocha. r=bgrins draft
authorLin Clark <lclark@mozilla.com>
Wed, 27 Jul 2016 14:20:48 -0400
changeset 399563 e5e5c6fa98686580a93124e23fcde0ec76db4477
parent 398716 4cfced33907f0c2a3f39ee59ebce4ce275234aa0
child 399564 60dea2eb3fbce851c878d6285b7937ab06d01020
push id25876
push userbmo:lclark@mozilla.com
push dateThu, 11 Aug 2016 14:24:59 +0000
reviewersbgrins
bugs1289258
milestone51.0a1
Bug 1289258 - Part 5: Convert component tests to mocha. r=bgrins MozReview-Commit-ID: 7MhaWRxJACl
devtools/client/webconsole/new-console-output/components/grip-message-body.js
devtools/client/webconsole/new-console-output/moz.build
devtools/client/webconsole/new-console-output/test/chrome/chrome.ini
devtools/client/webconsole/new-console-output/test/chrome/head.js
devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
devtools/client/webconsole/new-console-output/test/components/chrome.ini
devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
devtools/client/webconsole/new-console-output/test/components/head.js
devtools/client/webconsole/new-console-output/test/components/message-container.test.js
devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
devtools/client/webconsole/new-console-output/test/components/page-error.test.js
devtools/client/webconsole/new-console-output/test/components/repeat.test.js
devtools/client/webconsole/new-console-output/test/components/test_console-api-call.html
devtools/client/webconsole/new-console-output/test/components/test_console-api-call_repeat.html
devtools/client/webconsole/new-console-output/test/components/test_evaluation-result.html
devtools/client/webconsole/new-console-output/test/components/test_message-container.html
devtools/client/webconsole/new-console-output/test/components/test_message-icon.html
devtools/client/webconsole/new-console-output/test/components/test_message-repeat.html
devtools/client/webconsole/new-console-output/test/components/test_page-error.html
devtools/client/webconsole/new-console-output/test/fixtures/l10n.js
devtools/client/webconsole/new-console-output/test/fixtures/moz.build
devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
devtools/client/webconsole/new-console-output/test/helpers.js
devtools/client/webconsole/new-console-output/test/mochitest/chrome.ini
devtools/client/webconsole/new-console-output/test/mochitest/test_render_perf.html
devtools/client/webconsole/new-console-output/utils/messages.js
devtools/client/webconsole/package.json
--- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -1,16 +1,22 @@
 /* -*- 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";
 
+// If this is being run from Mocha, then the browser loader hasn't set up
+// define. We need to do that before loading Rep.
+if (typeof define === "undefined") {
+  require("amd-loader");
+}
+
 // React
 const {
   createFactory,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
 const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
 const VariablesViewLink = createFactory(require("devtools/client/webconsole/new-console-output/components/variables-view-link").VariablesViewLink);
--- a/devtools/client/webconsole/new-console-output/moz.build
+++ b/devtools/client/webconsole/new-console-output/moz.build
@@ -16,15 +16,14 @@ DevToolsModules(
     'constants.js',
     'main.js',
     'new-console-output-wrapper.js',
     'store.js',
     'types.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += [
-  'test/components/chrome.ini',
-  'test/mochitest/chrome.ini'
+  'test/chrome/chrome.ini'
 ]
 XPCSHELL_TESTS_MANIFESTS += [
   'test/store/xpcshell.ini'
 ]
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/chrome/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+support-files =
+  head.js
+
+[test_render_perf.html]
+skip-if = debug
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/chrome/head.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { utils: Cu } = Components;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { Assert } = require("resource://testing-common/Assert.jsm");
+var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+var { Task } = require("devtools/shared/task");
+
+var { require: browserRequire } = BrowserLoader({
+  baseURI: "resource://devtools/client/webconsole/",
+  window: this
+});
rename from devtools/client/webconsole/new-console-output/test/mochitest/test_render_perf.html
rename to devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/chrome.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[DEFAULT]
-
-support-files =
-  head.js
-
-[test_console-api-call.html]
-[test_console-api-call_repeat.html]
-[test_evaluation-result.html]
-[test_message-icon.html]
-[test_message-container.html]
-[test_message-repeat.html]
-[test_page-error.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
+const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("ConsoleAPICall component:", () => {
+  jsdom();
+  describe("console.log", () => {
+    it("renders string grips", () => {
+      const message = stubConsoleMessages.get("console.log('foobar', 'test')");
+      const rendered = renderComponent(ConsoleApiCall, {message});
+
+      const messageBody = getMessageBody(rendered);
+      // @TODO should output: foobar test
+      expect(messageBody.textContent).toBe("\"foobar\"\"test\"");
+
+      const consoleStringNodes = messageBody.querySelectorAll(".objectBox-string");
+      expect(consoleStringNodes.length).toBe(2);
+    });
+    it("renders repeat node", () => {
+      const message =
+        stubConsoleMessages.get("console.log('foobar', 'test')")
+        .set("repeat", 107);
+      const rendered = renderComponent(ConsoleApiCall, {message});
+
+      const repeatNode = getRepeatNode(rendered);
+      expect(repeatNode[0].textContent).toBe("107");
+    });
+  });
+
+  describe("console.count", () => {
+    it("renders", () => {
+      const message = stubConsoleMessages.get("console.count('bar')");
+      const rendered = renderComponent(ConsoleApiCall, {message});
+
+      const messageBody = getMessageBody(rendered);
+      expect(messageBody.textContent).toBe(message.messageText);
+    });
+  });
+});
+
+function getMessageBody(rendered) {
+  const queryPath = "div.message.cm-s-mozilla span span.message-flex-body span.message-body.devtools-monospace";
+  return rendered.querySelector(queryPath);
+}
+
+function getRepeatNode(rendered) {
+  const repeatPath = "span > span.message-flex-body > span.message-body.devtools-monospace + span.message-repeats";
+  return rendered.querySelectorAll(repeatPath);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
+const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
+
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("EvaluationResult component:", () => {
+  jsdom();
+  it("renders a grip result", () => {
+    const message = stubConsoleMessages.get("new Date(0)");
+    const props = {
+      message
+    };
+    const rendered = renderComponent(EvaluationResult, props);
+
+    const messageBody = getMessageBody(rendered);
+    expect(messageBody.textContent).toBe("Date1970-01-01T00:00:00.000Z");
+  });
+});
+
+function getMessageBody(rendered) {
+  const queryPath = "div.message.cm-s-mozilla span.message-body-wrapper.message-body.devtools-monospace";
+  return rendered.querySelector(queryPath);
+}
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/head.js
+++ /dev/null
@@ -1,268 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* exported getPacket, renderComponent, shallowRenderComponent,
-   cleanActualHTML, cleanExpectedHTML */
-
-"use strict";
-
-var { utils: Cu } = Components;
-
-var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var { Assert } = require("resource://testing-common/Assert.jsm");
-var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var flags = require("devtools/shared/flags");
-var { Task } = require("devtools/shared/task");
-var { DebuggerServer } = require("devtools/server/main");
-var { DebuggerClient } = require("devtools/shared/client/main");
-
-const Services = require("Services");
-
-flags.testing = true;
-var { require: browserRequire } = BrowserLoader({
-  baseURI: "resource://devtools/client/webconsole/",
-  window: this
-});
-
-let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
-let React = browserRequire("devtools/client/shared/vendor/react");
-var TestUtils = React.addons.TestUtils;
-
-const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
-
-// @TODO Remove this.
-let testCommands = new Map();
-testCommands.set("console.log()", {
-  command: "console.log('foobar', 'test')",
-  commandType: "consoleAPICall",
-  // @TODO should output: foobar test
-  expectedText: "\"foobar\"\"test\""
-});
-testCommands.set("new Date()", {
-  command: "new Date(448156800000)",
-  commandType: "evaluationResult",
-  // @TODO should output: Date 1984-03-15T00:00:00.000Z
-  expectedText: "Date1984-03-15T00:00:00.000Z"
-});
-testCommands.set("pageError", {
-  command: null,
-  commandType: "pageError",
-  expectedText: "ReferenceError: asdf is not defined"
-});
-
-function* getPacket(command, type = "evaluationResult") {
-  try {
-    // Attach the console to the tab.
-    let state = yield new Promise(function (resolve) {
-      attachConsoleToTab(["ConsoleAPI"], resolve);
-    });
-
-    // Run the command and get the packet.
-    let packet;
-    switch (type) {
-      case "consoleAPICall":
-        packet = yield new Promise((resolve) => {
-          function onConsoleApiCall(apiCallType, apiCallPacket) {
-            state.dbgClient.removeListener("consoleAPICall", onConsoleApiCall);
-            resolve(apiCallPacket);
-          }
-          state.dbgClient.addListener("consoleAPICall", onConsoleApiCall);
-          state.client.evaluateJS(`top.${command}`);
-        });
-        break;
-      case "evaluationResult":
-        packet = yield new Promise(resolve => {
-          state.client.evaluateJS(command, resolve);
-        });
-        break;
-      case "pageError":
-        // @TODO: get packet with RDP
-        packet = {
-          "from": "server1.conn1.child1/consoleActor2",
-          "type": "pageError",
-          "pageError": {
-            "errorMessage": "ReferenceError: asdf is not defined",
-            "sourceName": "data:text/html,<script>asdf</script>",
-            "lineText": "",
-            "lineNumber": 1,
-            "columnNumber": 1,
-            "category": "content javascript",
-            "timeStamp": 1455735574091,
-            "warning": false,
-            "error": false,
-            "exception": true,
-            "strict": false,
-            "info": false,
-            "private": false,
-            "stacktrace": [{
-              "columnNumber": 68,
-              "filename": "test.html",
-              "functionName": "baz",
-              "language": 2,
-              "lineNumber": 1
-            }, {
-              "columnNumber": 43,
-              "filename": "test.html",
-              "functionName": "bar",
-              "language": 2,
-              "lineNumber": 2
-            }, {
-              "columnNumber": 18,
-              "filename": "test.html",
-              "functionName": "foo",
-              "language": 2,
-              "lineNumber": 3
-            }, {
-              "columnNumber": 150,
-              "filename": "test.html",
-              "functionName": "",
-              "language": 2,
-              "lineNumber": 4
-            }]
-          }
-        };
-        break;
-    }
-
-    closeDebugger(state);
-    return packet;
-  } catch (e) {
-    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
-  }
-}
-
-function renderComponent(component, props) {
-  const el = React.createElement(component, props, {});
-  // By default, renderIntoDocument() won't work for stateless components, but
-  // it will work if the stateless component is wrapped in a stateful one.
-  // See https://github.com/facebook/react/issues/4839
-  const wrappedEl = React.DOM.span({}, [el]);
-  const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
-  return ReactDOM.findDOMNode(renderedComponent).children[0];
-}
-
-function shallowRenderComponent(component, props) {
-  const el = React.createElement(component, props);
-  const renderer = TestUtils.createRenderer();
-  renderer.render(el, {});
-  return renderer.getRenderOutput();
-}
-
-function cleanActualHTML(htmlString) {
-  return htmlString.replace(/ data-reactid=\".*?\"/g, "");
-}
-
-function cleanExpectedHTML(htmlString) {
-  return htmlString.replace(/(?:\r\n|\r|\n)\s*/g, "");
-}
-
-// Helpers copied in from shared/webconsole/test/common.js
-function initCommon()
-{
-  // Services.prefs.setBoolPref("devtools.debugger.log", true);
-}
-
-function initDebuggerServer()
-{
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
-  }
-  DebuggerServer.allowChromeProcess = true;
-}
-
-function connectToDebugger(aCallback)
-{
-  initCommon();
-  initDebuggerServer();
-
-  let transport = DebuggerServer.connectPipe();
-  let client = new DebuggerClient(transport);
-
-  let dbgState = { dbgClient: client };
-  client.connect().then(response => aCallback(dbgState, response));
-}
-
-function closeDebugger(aState, aCallback)
-{
-  aState.dbgClient.close(aCallback);
-  aState.dbgClient = null;
-  aState.client = null;
-}
-
-function attachConsole(aListeners, aCallback) {
-  _attachConsole(aListeners, aCallback);
-}
-function attachConsoleToTab(aListeners, aCallback) {
-  _attachConsole(aListeners, aCallback, true);
-}
-function attachConsoleToWorker(aListeners, aCallback) {
-  _attachConsole(aListeners, aCallback, true, true);
-}
-
-function _attachConsole(aListeners, aCallback, aAttachToTab, aAttachToWorker)
-{
-  function _onAttachConsole(aState, aResponse, aWebConsoleClient)
-  {
-    if (aResponse.error) {
-      console.error("attachConsole failed: " + aResponse.error + " " +
-                    aResponse.message);
-    }
-
-    aState.client = aWebConsoleClient;
-
-    aCallback(aState, aResponse);
-  }
-
-  connectToDebugger(function _onConnect(aState, aResponse) {
-    if (aResponse.error) {
-      console.error("client.connect() failed: " + aResponse.error + " " +
-                    aResponse.message);
-      aCallback(aState, aResponse);
-      return;
-    }
-
-    if (aAttachToTab) {
-      aState.dbgClient.listTabs(function _onListTabs(aResponse) {
-        if (aResponse.error) {
-          console.error("listTabs failed: " + aResponse.error + " " +
-                         aResponse.message);
-          aCallback(aState, aResponse);
-          return;
-        }
-        let tab = aResponse.tabs[aResponse.selected];
-        aState.dbgClient.attachTab(tab.actor, function (response, tabClient) {
-          if (aAttachToWorker) {
-            var worker = new Worker("console-test-worker.js");
-            worker.addEventListener("message", function listener() {
-              worker.removeEventListener("message", listener);
-              tabClient.listWorkers(function (response) {
-                tabClient.attachWorker(response.workers[0].actor, function (response, workerClient) {
-                  workerClient.attachThread({}, function (aResponse) {
-                    aState.actor = workerClient.consoleActor;
-                    aState.dbgClient.attachConsole(workerClient.consoleActor, aListeners,
-                                                   _onAttachConsole.bind(null, aState));
-                  });
-                });
-              });
-            });
-          } else {
-            aState.actor = tab.consoleActor;
-            aState.dbgClient.attachConsole(tab.consoleActor, aListeners,
-                                           _onAttachConsole.bind(null, aState));
-          }
-        });
-      });
-    } else {
-      aState.dbgClient.getProcess().then(response => {
-        aState.dbgClient.attachTab(response.form.actor, function () {
-          let consoleActor = response.form.consoleActor;
-          aState.actor = consoleActor;
-          aState.dbgClient.attachConsole(consoleActor, aListeners,
-                                         _onAttachConsole.bind(null, aState));
-        });
-      });
-    }
-  });
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
+
+const { MessageContainer } = require("devtools/client/webconsole/new-console-output/components/message-container");
+const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
+const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
+const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
+
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent,
+  shallowRenderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("MessageContainer component:", () => {
+  jsdom();
+  it("pipes data to children as expected", () => {
+    const message = stubConsoleMessages.get("console.log('foobar', 'test')");
+    const rendered = renderComponent(MessageContainer, {message});
+
+    expect(rendered.textContent.includes("foobar")).toBe(true);
+  });
+  it("picks correct child component", () => {
+    const messageTypes = [
+      {
+        component: ConsoleApiCall,
+        message: stubConsoleMessages.get("console.log('foobar', 'test')")
+      },
+      {
+        component: EvaluationResult,
+        message: stubConsoleMessages.get("new Date(0)")
+      },
+      {
+        component: PageError,
+        message: stubConsoleMessages.get("ReferenceError")
+      }
+    ];
+
+    messageTypes.forEach(info => {
+      const rendered = shallowRenderComponent(MessageContainer, {message: info.message});
+      expect(rendered.type).toBe(info.component);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+  SEVERITY_ERROR,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const { MessageIcon } = require("devtools/client/webconsole/new-console-output/components/message-icon");
+
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("MessageIcon component:", () => {
+  jsdom();
+
+  it("renders icon based on severity", () => {
+    const rendered = renderComponent(MessageIcon, { severity: SEVERITY_ERROR });
+
+    expect(rendered.classList.contains("icon")).toBe(true);
+    expect(rendered.getAttribute("title")).toBe("Error");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
+
+const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
+
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("PageError component:", () => {
+  jsdom();
+  it("renders a page error", () => {
+    const message = stubConsoleMessages.get("ReferenceError");
+    const rendered = renderComponent(PageError, {message});
+
+    const messageBody = getMessageBody(rendered);
+    expect(messageBody.textContent).toBe("ReferenceError: asdf is not defined");
+  });
+});
+
+function getMessageBody(rendered) {
+  const queryPath = "div.message span.message-body-wrapper.message-body.devtools-monospace";
+  return rendered.querySelector(queryPath);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/repeat.test.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { MessageRepeat } = require("devtools/client/webconsole/new-console-output/components/message-repeat");
+
+const jsdom = require("mocha-jsdom");
+const expect = require("expect");
+
+const {
+  renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("MessageRepeat component:", () => {
+  jsdom();
+
+  it("renders repeated value correctly", () => {
+    const rendered = renderComponent(MessageRepeat, { repeat: 99 });
+    expect(rendered.classList.contains("message-repeats")).toBe(true);
+    expect(rendered.style.visibility).toBe("visible");
+    expect(rendered.textContent).toBe("99");
+  });
+
+  it("renders an un-repeated value correctly", () => {
+    const rendered = renderComponent(MessageRepeat, { repeat: 1 });
+    expect(rendered.style.visibility).toBe("hidden");
+  });
+});
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_console-api-call.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for ConsoleApiCall component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for ConsoleApiCall component</p>
-
-<script type="text/javascript;version=1.8">
-const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
-const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
-
-window.onload = Task.async(function* () {
-  yield testConsoleLog();
-  yield testConsoleCount();
-
-  SimpleTest.finish()
-});
-
-function testConsoleLog() {
-  const packet = yield getPacket("console.log('foobar', 'test')", "consoleAPICall");
-  const message = prepareMessage(packet);
-  const rendered = renderComponent(ConsoleApiCall, {message});
-
-  const messageBody = getMessageBody(rendered);
-  // @TODO should output: foobar test
-  is(messageBody.textContent, "\"foobar\"\"test\"", "ConsoleApiCall outputs expected text");
-
-  const consoleStringNodes = messageBody.querySelectorAll(".objectBox");
-  is(consoleStringNodes.length, 2, "ConsoleApiCall outputs expected HTML structure");
-}
-
-function testConsoleCount() {
-  for (let i = 0; i < 3; i++) {
-    const packet = yield getPacket("console.count('bar')", "consoleAPICall");
-    const message = prepareMessage(packet);
-    const rendered = renderComponent(ConsoleApiCall, {message: message});
-    const messageBody = getMessageBody(rendered);
-
-    const expected = `bar: ${i + 1}`;
-    is(messageBody.textContent, expected,
-      `console.count has the expected text content: "${expected}"`);
-  }
-
-  const packet = yield getPacket("console.count()", "consoleAPICall")
-  const message = prepareMessage(packet);
-  const rendered = renderComponent(ConsoleApiCall, {message: message});
-  const messageBody = getMessageBody(rendered);
-  const expected = "<no label>: 1";
-  is(messageBody.textContent, expected,
-    `console.count without label has the expected text content: "${expected}"`);
-}
-
-function getMessageBody(renderedComponent) {
-  const queryPath = "div.message.cm-s-mozilla span span.message-flex-body span.message-body.devtools-monospace";
-  return renderedComponent.querySelector(queryPath);
-}
-
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_console-api-call_repeat.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for ConsoleApiCall component with repeats</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for ConsoleApiCall component with repeats</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
-  const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
-
-  const packet = yield getPacket("console.log('foobar', 'test')", "consoleAPICall");
-  const message = prepareMessage(packet).set("repeat", 107);
-  const rendered = renderComponent(ConsoleApiCall, {message});
-
-  const messageBodyPath = "span > span.message-flex-body > span.message-body.devtools-monospace";
-  const messageBody = rendered.querySelectorAll(messageBodyPath);
-  // @TODO Expected output should be: foobar test
-  is(messageBody[0].textContent, "\"foobar\"\"test\"", "ConsoleApiCall outputs expected text for repeated message");
-
-  const repeatPath = "span > span.message-flex-body > span.message-body.devtools-monospace + span.message-repeats";
-  const repeat = rendered.querySelectorAll(repeatPath);
-  is(repeat[0].textContent, `${message.repeat}`, "ConsoleApiCall outputs correct repeat count");
-
-  SimpleTest.finish()
-});
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_evaluation-result.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for EvaluationResult component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for EvaluationResult component</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
-  const {
-    EvaluationResult
-  } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
-
-  yield testDate();
-
-  SimpleTest.finish()
-
-  /**
-   * Test that evaluation result correctly outputs date results.
-   */
-  function testDate() {
-    const testCommand = testCommands.get("new Date()");
-    const packet = yield getPacket(testCommand.command, testCommand.commandType);
-    const message = prepareMessage(packet);
-    const props = {
-      message
-    };
-    const rendered = renderComponent(EvaluationResult, props);
-
-    const queryPathBase = "div.message.cm-s-mozilla span.message-body-wrapper.message-body.devtools-monospace span .objectBox";
-
-    const preview = rendered.querySelectorAll(queryPathBase);
-    is(preview[0].textContent, testCommand.expectedText, "EvaluationResult outputs expected text");
-  }
-});
-
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_message-container.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for MessageContainer component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for MessageContainer component</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
-
-  const { MessageContainer } = require("devtools/client/webconsole/new-console-output/components/message-container");
-  const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
-  const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
-  const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
-
-  yield testFullRender();
-  yield testGetMessageComponent();
-
-  SimpleTest.finish();
-
-  /**
-   * Test that passing in a message correctly wires up all the children.
-   *
-   * The different combinations of children are tested in separate per-component
-   * tests. This test just ensures that this component pipes data to its children.
-   */
-  function testFullRender() {
-    const testValue = testCommands.get("console.log()");
-    const packet = yield getPacket(testValue.command, testValue.commandType);
-    const message = prepareMessage(packet);
-    const props = {
-      message
-    };
-    const rendered = renderComponent(MessageContainer, props);
-
-    ok(rendered.textContent.includes(testValue.expectedText),
-      "MessageContainer pipes data to its children as expected");
-  }
-
-  /**
-   * Test that getMessageComponent() returns correct component for each message type.
-   */
-  function testGetMessageComponent() {
-    const testValues = [
-      {
-        commandObj: testCommands.get("console.log()"),
-        expectedComponent: ConsoleApiCall
-      },
-      {
-        commandObj: testCommands.get("new Date()"),
-        expectedComponent: EvaluationResult
-      },
-      {
-        commandObj: testCommands.get("pageError"),
-        expectedComponent: PageError
-      }
-    ];
-
-    for (let testValue of testValues) {
-      const { commandObj, expectedComponent } = testValue;
-      const packet = yield getPacket(commandObj.command, commandObj.commandType);
-      const message = prepareMessage(packet);
-      const rendered = shallowRenderComponent(MessageContainer, {message});
-      is(rendered.type, expectedComponent,
-        `MessageContainer nests ${expectedComponent} based on command: ${commandObj.command}`);
-    }
-  }
-});
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_message-icon.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for MessageRepeat component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for MessageIcon component</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const {
-    SEVERITY_ERROR,
-  } = require("devtools/client/webconsole/new-console-output/constants");
-  const { MessageIcon } = require("devtools/client/webconsole/new-console-output/components/message-icon");
-
-  let severity = SEVERITY_ERROR;
-  const iconRendered = renderComponent(MessageIcon, { severity });
-  ok(iconRendered.classList.contains("icon"), "MessageIcon has expected class");
-  is(iconRendered.getAttribute("title"), "Error",
-    "MessageIcon shows correct title attribute");
-
-  SimpleTest.finish();
-});
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_message-repeat.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for MessageRepeat component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for MessageRepeat component</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const { MessageRepeat } = require("devtools/client/webconsole/new-console-output/components/message-repeat");
-
-  const repeatRendered = renderComponent(MessageRepeat, { repeat: 99 });
-  ok(repeatRendered.classList.contains("message-repeats"), "MessageRepeat has expected class");
-  is(repeatRendered.style.visibility, "visible", "MessageRepeat with 2+ repeats is visible");
-  is(repeatRendered.textContent, "99", "MessageRepeat shows correct number of repeats");
-
-  const noRepeatRendered = renderComponent(MessageRepeat, { repeat: 1 });
-  is(noRepeatRendered.style.visibility, "hidden", "MessageRepeat with 1 repeat is hidden");
-  is(noRepeatRendered.textContent, "1", "MessageRepeat with 1 repeat shows correct number of repeats")
-
-  SimpleTest.finish();
-});
-</script>
-</body>
-</html>
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/components/test_page-error.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for PageError component</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript;version=1.8" src="head.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for PageError component</p>
-
-<script type="text/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
-  const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
-
-  const packet = yield getPacket(null, "pageError");
-  const message = prepareMessage(packet);
-  const rendered = renderComponent(PageError, {message});
-
-  const queryPath = "div.message span.message-body-wrapper.message-body.devtools-monospace";
-  const messageBody = rendered.querySelectorAll(queryPath);
-  is(messageBody.length, 1, "PageError outputs expected HTML structure");
-  is(messageBody[0].textContent, testCommands.get("pageError").expectedText, "PageError outputs expected text");
-
-  SimpleTest.finish()
-});
-</script>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/l10n.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// @TODO Load the actual strings from webconsole.properties instead.
+module.exports = {
+  getStr: str => {
+    switch (str) {
+      case "severity.error":
+        return "Error";
+    }
+    return str;
+  }
+};
--- a/devtools/client/webconsole/new-console-output/test/fixtures/moz.build
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/moz.build
@@ -1,8 +1,9 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'l10n.js',
     'stubs.js',
 )
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
@@ -91,9 +91,65 @@ exports.stubConsoleMessages = new Map([
         { type: "null" }
       ],
       repeat: 1,
       repeatId: null,
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     })
   ],
+  [
+    "console.count('bar')",
+    new ConsoleMessage({
+      allowRepeating: true,
+      source: MESSAGE_SOURCE.CONSOLE_API,
+      type: MESSAGE_TYPE.LOG,
+      level: MESSAGE_LEVEL.DEBUG,
+      messageText: "bar: 1",
+      parameters: null,
+      repeat: 1,
+      repeatId: null,
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    })
+  ],
+  [
+    "new Date(0)",
+    new ConsoleMessage({
+      allowRepeating: true,
+      source: MESSAGE_SOURCE.JAVASCRIPT,
+      type: MESSAGE_TYPE.RESULT,
+      level: MESSAGE_LEVEL.LOG,
+      messageText: null,
+      parameters: {
+        "type": "object",
+        "class": "Date",
+        "actor": "server2.conn0.obj115",
+        "extensible": true,
+        "frozen": false,
+        "sealed": false,
+        "ownPropertyLength": 0,
+        "preview": {
+          "timestamp": 0
+        }
+      },
+      repeat: 1,
+      repeatId: null,
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    })
+  ],
+  [
+    "ReferenceError",
+    new ConsoleMessage({
+      allowRepeating: true,
+      source: MESSAGE_SOURCE.JAVASCRIPT,
+      type: MESSAGE_TYPE.LOG,
+      level: MESSAGE_LEVEL.ERROR,
+      messageText: "ReferenceError: asdf is not defined",
+      parameters: null,
+      repeat: 1,
+      repeatId: null,
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    })
+  ]
 ]);
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/helpers.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let ReactDOM = require("devtools/client/shared/vendor/react-dom");
+let React = require("devtools/client/shared/vendor/react");
+var TestUtils = React.addons.TestUtils;
+
+function renderComponent(component, props) {
+  const el = React.createElement(component, props, {});
+  // By default, renderIntoDocument() won't work for stateless components, but
+  // it will work if the stateless component is wrapped in a stateful one.
+  // See https://github.com/facebook/react/issues/4839
+  const wrappedEl = React.DOM.span({}, [el]);
+  const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
+  return ReactDOM.findDOMNode(renderedComponent).children[0];
+}
+
+function shallowRenderComponent(component, props) {
+  const el = React.createElement(component, props);
+  const renderer = TestUtils.createRenderer();
+  renderer.render(el, {});
+  return renderer.getRenderOutput();
+}
+
+module.exports = {
+  renderComponent,
+  shallowRenderComponent
+};
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/chrome.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-
-support-files =
-  ../components/head.js
-
-[test_render_perf.html]
-skip-if = debug
--- a/devtools/client/webconsole/new-console-output/utils/messages.js
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -7,17 +7,17 @@
 "use strict";
 
 let l10n;
 try {
   const WebConsoleUtils = require("devtools/shared/webconsole/utils").Utils;
   const STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
   l10n = new WebConsoleUtils.L10n(STRINGS_URI);
 } catch (e) {
-  l10n = {};
+  l10n = require("devtools/client/webconsole/new-console-output/test/fixtures/l10n");
 }
 
 const {
   MESSAGE_SOURCE,
   MESSAGE_TYPE,
   MESSAGE_LEVEL,
   // Legacy
   CATEGORY_JS,
--- a/devtools/client/webconsole/package.json
+++ b/devtools/client/webconsole/package.json
@@ -1,13 +1,16 @@
 {
   "name": "webconsole",
   "version": "0.0.1",
   "devDependencies": {
+    "amd-loader": "0.0.5",
     "babel-preset-es2015": "^6.6.0",
     "babel-register": "^6.7.2",
     "expect": "^1.16.0",
-    "mocha": "^2.5.3"
+    "jsdom": "^9.4.1",
+    "mocha": "^2.5.3",
+    "mocha-jsdom": "^1.1.0"
   },
   "scripts": {
     "test": "NODE_PATH=`pwd`/../../../ mocha new-console-output/test/**/*.test.js --compilers js:babel-register"
   }
 }