Bug 1024913 - Add tests for Reverse search; r=bgrins.
☠☠ backed out by bda5c062ad28 ☠ ☠
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Mon, 10 Sep 2018 05:16:25 +0000
changeset 435363 1c18e0422ffa24bea16a0e18c3298344ba8ee3d8
parent 435362 c4e5a7ff8e53cc3d8ad7d8eec573f6678de66ee8
child 435364 3dbc855f18d5ea8cee4af7fccca14f891b8b3816
push id68994
push usernchevobbe@mozilla.com
push dateMon, 10 Sep 2018 05:44:13 +0000
treeherderautoland@1c18e0422ffa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1024913
milestone64.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 1024913 - Add tests for Reverse search; r=bgrins. Depends on D3114 Differential Revision: https://phabricator.services.mozilla.com/D5153
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search.js
devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_keyboard_navigation.js
devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_mouse_navigation.js
devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_toggle.js
devtools/client/webconsole/test/mochitest/head.js
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -338,16 +338,20 @@ skip-if = (os == 'linux') || (os == 'win
 [browser_webconsole_output_copy.js]
 subsuite = clipboard
 [browser_webconsole_output_copy_newlines.js]
 subsuite = clipboard
 [browser_webconsole_output_order.js]
 [browser_webconsole_persist.js]
 [browser_webconsole_reopen_closed_tab.js]
 [browser_webconsole_repeat_different_objects.js]
+[browser_webconsole_reverse_search.js]
+[browser_webconsole_reverse_search_keyboard_navigation.js]
+[browser_webconsole_reverse_search_mouse_navigation.js]
+[browser_webconsole_reverse_search_toggle.js]
 [browser_webconsole_sandbox_update_after_navigation.js]
 [browser_webconsole_script_errordoc_urls.js]
 [browser_webconsole_scroll.js]
 [browser_webconsole_select_all.js]
 [browser_webconsole_show_subresource_security_errors.js]
 skip-if = verify
 [browser_webconsole_shows_reqs_from_netmonitor.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+// Tests reverse search features.
+
+"use strict";
+
+const TEST_URI = `data:text/html,<meta charset=utf8>Test reverse search`;
+const isMacOS = AppConstants.platform === "macosx";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  const jstermHistory = [
+    `document`,
+    `Dog = "Snoopy"`,
+    `document
+       .querySelectorAll("*")
+       .forEach(console.log)`,
+    `document`,
+    `"😎"`
+  ];
+
+  const onLastMessage = waitForMessage(hud, `"😎"`);
+  for (const input of jstermHistory) {
+    await hud.jsterm.execute(input);
+  }
+  await onLastMessage;
+
+  const jstermInitialValue = "initialValue";
+  hud.jsterm.setInputValue(jstermInitialValue);
+
+  info("Check that the reverse search toolbar as the expected initial state");
+  let reverseSearchElement = await openReverseSearch(hud);
+  ok(reverseSearchElement, "Reverse search is displayed with a keyboard shortcut");
+  ok(!getReverseSearchInfoElement(hud),
+    "The result info element is not displayed by default");
+  ok(
+    !reverseSearchElement.querySelector(".search-result-button-prev") &&
+    !reverseSearchElement.querySelector(".search-result-button-next"),
+    "The results navigation buttons are not displayed by default"
+  );
+  is(hud.jsterm.getInputValue(), jstermInitialValue,
+    "The jsterm value is not changed when opening reverse search");
+  is(isReverseSearchInputFocused(hud), true, "reverse search input is focused");
+
+  EventUtils.sendString("d");
+  let infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
+  is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text "
+    + "— duplicated results (`document`) are coalesced");
+
+  const previousButton = reverseSearchElement.querySelector(".search-result-button-prev");
+  const nextButton = reverseSearchElement.querySelector(".search-result-button-next");
+  ok(previousButton, "Previous navigation button is now displayed");
+  is(previousButton.title, `Previous result (${isMacOS ? "Ctrl + R" : "F9"})`,
+    "Previous navigation button has expected title");
+
+  ok(nextButton, "Next navigation button is now displayed");
+  is(nextButton.title, `Next result (${isMacOS ? "Ctrl + S" : "Shift + F9"})`,
+    "Next navigation button has expected title");
+  is(hud.jsterm.getInputValue(), "document", "JsTerm has the expected input");
+  is(hud.jsterm.autocompletePopup.isOpen, false,
+    "Setting the input value did not trigger the autocompletion");
+  is(isReverseSearchInputFocused(hud), true, "reverse search input is focused");
+
+  let onJsTermValueChanged = hud.jsterm.once("set-input-value");
+  EventUtils.sendString("og");
+  await onJsTermValueChanged;
+  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm input was updated");
+  is(infoElement.textContent, "1 result", "The reverse info has the expected text");
+  ok(
+    !reverseSearchElement.querySelector(".search-result-button-prev") &&
+    !reverseSearchElement.querySelector(".search-result-button-next"),
+    "The results navigation buttons are not displayed when there's only one result"
+  );
+
+  info("Check that the UI and results are updated when typing in the input");
+  onJsTermValueChanged = hud.jsterm.once("set-input-value");
+  EventUtils.sendString("g");
+  await waitFor(() => reverseSearchElement.classList.contains("no-result"));
+  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`,
+    "JsTerm input was not updated since there's no results");
+  is(infoElement.textContent, "No results", "The reverse info has the expected text");
+  ok(
+    !reverseSearchElement.querySelector(".search-result-button-prev") &&
+    !reverseSearchElement.querySelector(".search-result-button-next"),
+    "The results navigation buttons are not displayed when there's no result"
+  );
+
+  info("Check that Backspace updates the UI");
+  EventUtils.synthesizeKey("KEY_Backspace");
+  await waitFor(() => !reverseSearchElement.classList.contains("no-result"));
+  is(infoElement.textContent, "1 result", "The reverse info has the expected text");
+  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm kept its value");
+
+  info("Check that Escape does not affect the jsterm value");
+  EventUtils.synthesizeKey("KEY_Escape");
+  await waitFor(() => !getReverseSearchElement(hud));
+  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`,
+    "Closing the input did not changed the JsTerm value");
+  is(isJstermFocused(hud.jsterm), true, "JsTerm is focused");
+
+  info("Check that the search works with emojis");
+  reverseSearchElement = await openReverseSearch(hud);
+  onJsTermValueChanged = hud.jsterm.once("set-input-value");
+  EventUtils.sendString("😎");
+  infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
+  is(infoElement.textContent, "1 result", "The reverse info has the expected text");
+
+  info("Check that Enter evaluates the JsTerm and closes the UI");
+  const onMessage = waitForMessage(hud, `"😎"`);
+  const onReverseSearchClose = waitFor(() => !getReverseSearchElement(hud));
+  EventUtils.synthesizeKey("KEY_Enter");
+  await Promise.all([onMessage, onReverseSearchClose]);
+  ok(true, "Enter evaluates what's in the JsTerm and closes the reverse search UI");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_keyboard_navigation.js
@@ -0,0 +1,122 @@
+/* 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/. */
+
+// Tests reverse search results keyboard navigation.
+
+"use strict";
+
+const TEST_URI = `data:text/html,<meta charset=utf8>Test reverse search`;
+const isMacOS = AppConstants.platform === "macosx";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  const jstermHistory = [
+    `document`,
+    `document
+       .querySelectorAll("*")
+       .forEach(console.log)`,
+    `Dog = "Snoopy"`,
+  ];
+
+  const onLastMessage = waitForMessage(hud, `"Snoopy"`);
+  for (const input of jstermHistory) {
+    await hud.jsterm.execute(input);
+  }
+  await onLastMessage;
+
+  await openReverseSearch(hud);
+  EventUtils.sendString("d");
+  const infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
+  is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
+
+  is(hud.jsterm.getInputValue(), jstermHistory[2], "JsTerm has the expected input");
+  is(hud.jsterm.autocompletePopup.isOpen, false,
+    "Setting the input value did not trigger the autocompletion");
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "2 of 3 results",
+    expectedJsTermInputValue: jstermHistory[1]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "1 of 3 results",
+    expectedJsTermInputValue: jstermHistory[0]
+  });
+
+  info("Check that we go back to the last matching item if we were at the first");
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "3 of 3 results",
+    expectedJsTermInputValue: jstermHistory[2]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "1 of 3 results",
+    expectedJsTermInputValue: jstermHistory[0]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "2 of 3 results",
+    expectedJsTermInputValue: jstermHistory[1]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "3 of 3 results",
+    expectedJsTermInputValue: jstermHistory[2]
+  });
+
+  info("Check that trying to navigate when there's only 1 result does not throw");
+  EventUtils.sendString("og");
+  await waitFor(() => getReverseSearchInfoElement(hud).textContent === "1 result");
+  triggerPreviousResultShortcut();
+  triggerNextResultShortcut();
+
+  info("Check that trying to navigate when there's no result does not throw");
+  EventUtils.sendString("g");
+  await waitFor(() => getReverseSearchInfoElement(hud).textContent === "No results");
+  triggerPreviousResultShortcut();
+  triggerNextResultShortcut();
+});
+
+async function navigateResultsAndCheckState(hud, {
+  direction,
+  expectedInfoText,
+  expectedJsTermInputValue,
+}) {
+  const onJsTermValueChanged = hud.jsterm.once("set-input-value");
+  if (direction === "previous") {
+    triggerPreviousResultShortcut();
+  } else {
+    triggerNextResultShortcut();
+  }
+  await onJsTermValueChanged;
+
+  is(hud.jsterm.getInputValue(), expectedJsTermInputValue, "JsTerm has expected value");
+
+  const infoElement = getReverseSearchInfoElement(hud);
+  is(infoElement.textContent, expectedInfoText, "The reverse info has the expected text");
+  is(isReverseSearchInputFocused(hud), true, "reverse search input is still focused");
+}
+
+function triggerPreviousResultShortcut() {
+  if (isMacOS) {
+    EventUtils.synthesizeKey("r", {ctrlKey: true});
+  } else {
+    EventUtils.synthesizeKey("VK_F9");
+  }
+}
+
+function triggerNextResultShortcut() {
+  if (isMacOS) {
+    EventUtils.synthesizeKey("s", {ctrlKey: true});
+  } else {
+    EventUtils.synthesizeKey("VK_F9", {shiftKey: true});
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_mouse_navigation.js
@@ -0,0 +1,119 @@
+/* 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/. */
+
+// Tests reverse search results mouse navigation.
+
+"use strict";
+
+const TEST_URI = `data:text/html,<meta charset=utf8>Test reverse search`;
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  const jstermHistory = [
+    `document`,
+    `document
+       .querySelectorAll("*")
+       .forEach(console.log)`,
+    `Dog = "Snoopy"`,
+  ];
+
+  const onLastMessage = waitForMessage(hud, `"Snoopy"`);
+  for (const input of jstermHistory) {
+    await hud.jsterm.execute(input);
+  }
+  await onLastMessage;
+
+  await openReverseSearch(hud);
+  EventUtils.sendString("d");
+  const infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
+  is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
+
+  is(hud.jsterm.getInputValue(), jstermHistory[2], "JsTerm has the expected input");
+  is(hud.jsterm.autocompletePopup.isOpen, false,
+    "Setting the input value did not trigger the autocompletion");
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "2 of 3 results",
+    expectedJsTermInputValue: jstermHistory[1]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "1 of 3 results",
+    expectedJsTermInputValue: jstermHistory[0]
+  });
+
+  info("Check that we go back to the last matching item if we were at the first");
+  await navigateResultsAndCheckState(hud, {
+    direction: "previous",
+    expectedInfoText: "3 of 3 results",
+    expectedJsTermInputValue: jstermHistory[2]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "1 of 3 results",
+    expectedJsTermInputValue: jstermHistory[0]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "2 of 3 results",
+    expectedJsTermInputValue: jstermHistory[1]
+  });
+
+  await navigateResultsAndCheckState(hud, {
+    direction: "next",
+    expectedInfoText: "3 of 3 results",
+    expectedJsTermInputValue: jstermHistory[2]
+  });
+});
+
+async function navigateResultsAndCheckState(hud, {
+  direction,
+  expectedInfoText,
+  expectedJsTermInputValue,
+}) {
+  const onJsTermValueChanged = hud.jsterm.once("set-input-value");
+  if (direction === "previous") {
+    clickPreviousButton(hud);
+  } else {
+    clickNextButton(hud);
+  }
+  await onJsTermValueChanged;
+
+  is(hud.jsterm.getInputValue(), expectedJsTermInputValue, "JsTerm has expected value");
+
+  const infoElement = getReverseSearchInfoElement(hud);
+  is(infoElement.textContent, expectedInfoText, "The reverse info has the expected text");
+  is(isReverseSearchInputFocused(hud), true, "reverse search input is still focused");
+}
+
+function clickPreviousButton(hud) {
+  const reverseSearchElement = getReverseSearchElement(hud);
+  if (!reverseSearchElement) {
+    return;
+  }
+  const button = reverseSearchElement.querySelector(".search-result-button-prev");
+  if (!button) {
+    return;
+  }
+
+  button.click();
+}
+
+function clickNextButton(hud) {
+  const reverseSearchElement = getReverseSearchElement(hud);
+  if (!reverseSearchElement) {
+    return;
+  }
+  const button = reverseSearchElement.querySelector(".search-result-button-next");
+  if (!button) {
+    return;
+  }
+
+  button.click();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_toggle.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+// Tests showing and hiding the reverse search UI.
+
+"use strict";
+
+const TEST_URI = `data:text/html,<meta charset=utf8>Test reverse search toggle`;
+const isMacOS = AppConstants.platform === "macosx";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  info("Close the reverse search UI with ESC");
+  await openReverseSearch(hud);
+  let onReverseSearchUiClose = waitFor(() => getReverseSearchElement(hud) === null);
+  EventUtils.sendKey("ESCAPE");
+  await onReverseSearchUiClose;
+  ok(true, "Reverse search was closed with the Esc keyboard shortcut");
+
+  if (isMacOS) {
+    info("Close the reverse search UI with Ctrl + C on OSX");
+    await openReverseSearch(hud);
+    onReverseSearchUiClose = waitFor(() => getReverseSearchElement(hud) === null);
+    EventUtils.synthesizeKey("c", {ctrlKey: true});
+    await onReverseSearchUiClose;
+    ok(true, "Reverse search was closed with the Ctrl + C keyboard shortcut");
+  }
+
+  info("Close the reverse search UI with the close button");
+  const reverseSearchElement = await openReverseSearch(hud);
+  const closeButton = reverseSearchElement.querySelector(".reverse-search-close-button");
+  ok(closeButton, "The close button is displayed");
+  is(closeButton.title, `Close (Esc${isMacOS ? " | Ctrl + C" : ""})`,
+    "The close button has the expected tooltip");
+  onReverseSearchUiClose = waitFor(() => getReverseSearchElement(hud) === null);
+  closeButton.click();
+  await onReverseSearchUiClose;
+  ok(true, "Reverse search was closed by clicking on the close button");
+
+  info("Close the reverse search UI by clicking on the output");
+  await openReverseSearch(hud);
+  hud.ui.outputNode.querySelector(".jsterm-input-container").click();
+  ok(true, "Reverse search was closed by clicking in the output");
+});
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -61,17 +61,16 @@ registerCleanupFunction(async function()
  *        true (default) if the jsterm history should be cleared.
  * @return Promise
  *         Resolves when the tab has been added, loaded and the toolbox has been opened.
  *         Resolves to the toolbox.
  */
 async function openNewTabAndConsole(url, clearJstermHistory = true) {
   const toolbox = await openNewTabAndToolbox(url, "webconsole");
   const hud = toolbox.getCurrentPanel().hud;
-  hud.jsterm._lazyVariablesView = false;
 
   if (clearJstermHistory) {
     // Clearing history that might have been set in previous tests.
     await hud.ui.consoleOutput.dispatchClearHistory();
   }
 
   return hud;
 }
@@ -858,8 +857,57 @@ async function setFilterBarVisible(hud, 
  * @param {Object} hud
  */
 async function resetFilters(hud) {
   info("Resetting filters to their default state");
 
   const store = hud.ui.consoleOutput.getStore();
   store.dispatch(wcActions.filtersClear());
 }
+
+/**
+ * Open the reverse search input by simulating the appropriate keyboard shortcut.
+ *
+ * @param {Object} hud
+ * @returns {DOMNode} The reverse search dom node.
+ */
+async function openReverseSearch(hud) {
+  info("Open the reverse search UI with a keyboard shortcut");
+  const onReverseSearchUiOpen = waitFor(() => getReverseSearchElement(hud));
+  const isMacOS = AppConstants.platform === "macosx";
+  if (isMacOS) {
+    EventUtils.synthesizeKey("r", {ctrlKey: true});
+  } else {
+    EventUtils.synthesizeKey("VK_F9");
+  }
+
+  const element = await onReverseSearchUiOpen;
+  return element;
+}
+
+function getReverseSearchElement(hud) {
+  const {outputNode} = hud.ui;
+  return outputNode.querySelector(".reverse-search");
+}
+
+function getReverseSearchInfoElement(hud) {
+  const reverseSearchElement = getReverseSearchElement(hud);
+  if (!reverseSearchElement) {
+    return null;
+  }
+
+  return reverseSearchElement.querySelector(".reverse-search-info");
+}
+
+/**
+ * Returns a boolean indicating if the reverse search input is focused.
+ *
+ * @param {JsTerm} jsterm
+ * @returns {Boolean}
+ */
+function isReverseSearchInputFocused(hud) {
+  const {outputNode} = hud.ui;
+  const document = outputNode.ownerDocument;
+  const documentIsFocused = document.hasFocus();
+  const reverseSearchInput = outputNode.querySelector(".reverse-search-input");
+
+  return document.activeElement == reverseSearchInput && documentIsFocused;
+}