Bug 1354504 - Add test cases for SearchBox and AutocompletePopup react components. r=ntim, jdescottes.
☠☠ backed out by 2c4621d09ea9 ☠ ☠
authorRuturaj K. Vartak <ruturaj@gmail.com>
Tue, 16 May 2017 19:58:00 +0100
changeset 358810 f7b1a464331846b8e849367329913e883b4d3861
parent 358809 8cb64c677091fe3f8ae587e931a5ffe24b997972
child 358811 2c4621d09ea9fbf7fc8174876ada1498ae2447b0
push id31839
push userkwierso@gmail.com
push dateWed, 17 May 2017 21:55:52 +0000
treeherdermozilla-central@2c783a7b6d05 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersntim, jdescottes
bugs1354504
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1354504 - Add test cases for SearchBox and AutocompletePopup react components. r=ntim, jdescottes. [SearchBox] - SearchBox setup - initial empty value - initially notFocussed - setting value to inputbox - checking for focus on the input - onBlur - ClearButton hidden initialized - ClearButton shown on some value KeyDown emptyAutoComplete return undefined - ArrowDown - ArrowUp - PageDown - PageUp - Home - End - Tab - and hide box - Backspace - reactivates popup - Enter - and hide box - Escape [AutocompletePopup] - initialy DOM invisibility - focus on input and visibility - List compare - Initial selectedIndex = -1 - Blur hiding of autocomplete - filtering of Values - jumpToTop (by key events) - jumpToBottom (by key events) - jumpBy (by key events) - select (by key events) - Selection class - MouseDown MozReview-Commit-ID: 9PjGztW0PIy
devtools/client/shared/components/test/mochitest/chrome.ini
devtools/client/shared/components/test/mochitest/head.js
devtools/client/shared/components/test/mochitest/test_searchbox-with-autocomplete.html
devtools/client/shared/components/test/mochitest/test_searchbox.html
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -2,16 +2,18 @@
 support-files =
   head.js
 
 [test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
+[test_searchbox.html]
+[test_searchbox-with-autocomplete.html]
 [test_sidebar_toggle.html]
 [test_stack-trace.html]
 [test_tabs_accessibility.html]
 [test_tabs_menu.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -206,8 +206,30 @@ function renderComponent(component, prop
 }
 
 function shallowRenderComponent(component, props) {
   const el = React.createElement(component, props);
   const renderer = TestUtils.createRenderer();
   renderer.render(el, {});
   return renderer.getRenderOutput();
 }
+
+/**
+ * Creates a React Component for testing
+ *
+ * @param {string} factory - factory object of the component to be created
+ * @param {object} props - React props for the component
+ * @returns {object} - container Node, Object with React component
+ * and querySelector function with $ as name.
+ */
+async function createComponentTest(factory, props) {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+
+  const component = ReactDOM.render(factory(props), container);
+  await forceRender(component);
+
+  return {
+    container,
+    component,
+    $: (s) => container.querySelector(s),
+  };
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_searchbox-with-autocomplete.html
@@ -0,0 +1,192 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html>
+<!--
+Test the searchbox and autocomplete-popup components
+-->
+<head>
+  <meta charset="utf-8">
+  <title>SearchBox component test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+window.onload = async function () {
+  /**
+   * Takes a DOMNode with its children as list items,
+   * Typically UL > LI and each item's text value is
+   * compared with the reference item's value as a test
+   *
+   * @params {Node} - Node to be compared
+   * @reference {array} - Reference array for comparison
+   */
+  function compareAutocompleteList(list, reference) {
+    let items = [...list.children].map(el => el.textContent);
+    for (let i = 0; i < items.length; i++) {
+      let item = items[i];
+      let ref = reference[i];
+      is(item, ref, `Item ${i} in list is correct`);
+    }
+  }
+
+  let React = browserRequire("devtools/client/shared/vendor/react");
+  let SearchBox = React.createFactory(
+    browserRequire("devtools/client/shared/components/search-box")
+  );
+  const { component, $ } = await createComponentTest(SearchBox, {
+    type: "search",
+    autocompleteList: [
+      "foo",
+      "BAR",
+      "baZ",
+      "abc",
+      "pqr",
+      "xyz",
+      "ABC",
+    ],
+    onChange: () => null,
+  });
+  const { refs } = component;
+
+  async function testSearchBoxWithAutocomplete() {
+    ok(!$(".devtools-autocomplete-popup"), "Autocomplete list not visible");
+
+    $(".devtools-searchinput").focus();
+    await forceRender(component); // Wait for state update
+
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), [
+      "ABC",
+      "BAR",
+      "abc",
+      "baZ",
+      "foo",
+      "pqr",
+      "xyz",
+    ]);
+
+    is(refs.autocomplete.state.selectedIndex, -1, "Initialised selectedIndex is -1");
+
+    // Blur event
+    $(".devtools-searchinput").blur();
+    await forceRender(component);
+    ok(!component.state.focused, "focused state was properly set");
+    ok(!$(".devtools-autocomplete-popup"), "Autocomplete list removed from DOM");
+  }
+
+  async function testKeyEventsWithAutocomplete() {
+    // Filtering of list
+    $(".devtools-searchinput").focus();
+    await forceRender(component);
+    sendString("aB");
+    await forceRender(component);
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), ["ABC", "abc"]);
+
+    // Clear the initial input
+    synthesizeKey("VK_BACK_SPACE", {});
+    synthesizeKey("VK_BACK_SPACE", {});
+
+    // ArrowDown
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 0, "selectedIndex is 0");
+    ok($(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)")
+      .className.includes("autocomplete-selected"),
+      "Selection class applied");
+
+    // ArrowUp should roll back to the bottom of the list
+    synthesizeKey("VK_UP", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "ArrowUp works");
+
+    // PageDown should take -5 places up
+    synthesizeKey("VK_PAGE_UP", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 1, "PageUp works");
+
+    // PageDown should take +5 places down
+    synthesizeKey("VK_PAGE_DOWN", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "PageDown works");
+
+    // Home should take to the top of the list
+    synthesizeKey("VK_HOME", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 0, "Home works");
+
+    // End should take to the bottom of the list
+    synthesizeKey("VK_END", {});
+    await forceRender(component);
+    is(refs.autocomplete.state.selectedIndex, 6, "End works");
+
+    // Key down in existing state should rollover to the top
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    // Tab should select the component and hide popup
+    synthesizeKey("VK_TAB", {});
+    await forceRender(component);
+    is(component.state.value, "ABC", "Tab hit selects the item");
+    ok(!$(".devtools-autocomplete-popup"), "Tab hit hides the popup");
+
+    // Activate popup by removing a key
+    synthesizeKey("VK_BACK_SPACE", {});
+    await forceRender(component);
+    ok($(".devtools-autocomplete-popup"), "Popup is up");
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), ["ABC", "abc"]);
+
+    // Enter key selection
+    synthesizeKey("VK_UP", {});
+    await forceRender(component);
+    synthesizeKey("VK_RETURN", {});
+    is(component.state.value, "abc", "Enter selection");
+    ok(!$(".devtools-autocomplete-popup"), "Enter/Return hides the popup");
+
+    // Escape should remove the autocomplete component
+    synthesizeKey("VK_ESCAPE", {});
+    await forceRender(component);
+    ok(!$(".devtools-autocomplete-popup"),
+      "Autocomplete list removed from DOM on Escape");
+  }
+
+  async function testMouseEventsWithAutocomplete() {
+    $(".devtools-searchinput").focus();
+    await setState(component, {
+      value: "",
+      focused: true,
+    });
+    await forceRender(component);
+
+    // ArrowDown
+    synthesizeKey("VK_DOWN", {});
+    await forceRender(component);
+    synthesizeMouseAtCenter($(".devtools-searchinput"), {}, window);
+    await forceRender(component);
+    is(component.state.focused, true, "Component should now be focused");
+
+    sendString("pq");
+    await forceRender(component);
+    synthesizeMouseAtCenter(
+      $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"),
+      {}, window
+    );
+    await forceRender(component);
+    is(component.state.value, "pqr", "Mouse click selects the item.");
+    ok(!$(".devtools-autocomplete-popup"), "Mouse click on item hides the popup");
+  }
+
+  add_task(async function () {
+    await testSearchBoxWithAutocomplete();
+    await testKeyEventsWithAutocomplete();
+    await testMouseEventsWithAutocomplete();
+  });
+};
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_searchbox.html
@@ -0,0 +1,76 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html>
+<!--
+Test the searchbox component
+-->
+<head>
+  <meta charset="utf-8">
+  <title>SearchBox component test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+window.onload = function () {
+  let React = browserRequire("devtools/client/shared/vendor/react");
+  let SearchBox = React.createFactory(
+    browserRequire("devtools/client/shared/components/search-box")
+  );
+  ok(SearchBox, "Got the SearchBox factory");
+
+  async function testSimpleSearchBox() {
+    // Test initial state
+    const { component, $ } = await createComponentTest(SearchBox, {
+      type: "search",
+      keyShortcut: "CmdOrCtrl+F",
+      placeholder: "crazy placeholder",
+    });
+
+    is(component.state.value, "", "Initial value is blank");
+    ok(!component.state.focused, "Input isn't initially focused");
+    ok($(".devtools-searchinput-clear").hidden, "Clear button hidden");
+    is($(".devtools-searchinput").placeholder, "crazy placeholder",
+     "Placeholder is properly set");
+
+    synthesizeKey("f", { accelKey: true });
+    await forceRender(component); // Wait for state update
+    ok(component.state.focused, "Shortcut key focused the input box");
+
+    $(".devtools-searchinput").blur();
+    await forceRender(component);
+    ok(!component.state.focused, "`focused` state set to false after blur");
+
+    // Test changing value in state
+    await setState(component, {
+      value: "foo",
+    });
+
+    is(component.state.value, "foo", "value was properly set on state");
+    is($(".devtools-searchinput").value, "foo", "value was properly set on element");
+
+    // Filling input should show clear button
+    ok(!$(".devtools-searchinput-clear").hidden, "Clear button shown");
+
+    // Clearing value should hide clear button
+    await setState(component, {
+      value: "",
+    });
+    await forceRender(component);
+    ok($(".devtools-searchinput-clear").hidden, "Clear button was hidden");
+  }
+
+  add_task(async function () {
+    await testSimpleSearchBox();
+  });
+};
+</script>
+</body>
+</html>