summaryrefslogtreecommitdiffstats
path: root/devtools/client/jsonview/test/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/jsonview/test/head.js')
-rw-r--r--devtools/client/jsonview/test/head.js278
1 files changed, 278 insertions, 0 deletions
diff --git a/devtools/client/jsonview/test/head.js b/devtools/client/jsonview/test/head.js
new file mode 100644
index 0000000000..5c9ee85ea2
--- /dev/null
+++ b/devtools/client/jsonview/test/head.js
@@ -0,0 +1,278 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+
+"use strict";
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/head.js",
+ this
+);
+
+const JSON_VIEW_PREF = "devtools.jsonview.enabled";
+
+// Enable JSON View for the test
+Services.prefs.setBoolPref(JSON_VIEW_PREF, true);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(JSON_VIEW_PREF);
+});
+
+// XXX move some API into devtools/shared/test/shared-head.js
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url
+ * The url to be loaded in the new tab.
+ *
+ * @param {Object} [optional]
+ * An object with the following optional properties:
+ * - appReadyState: The readyState of the JSON Viewer app that you want to
+ * wait for. Its value can be one of:
+ * - "uninitialized": The converter has started the request.
+ * If JavaScript is disabled, there will be no more readyState changes.
+ * - "loading": RequireJS started loading the scripts for the JSON Viewer.
+ * If the load timeouts, there will be no more readyState changes.
+ * - "interactive": The JSON Viewer app loaded, but possibly not all the JSON
+ * data has been received.
+ * - "complete" (default): The app is fully loaded with all the JSON.
+ * - docReadyState: The standard readyState of the document that you want to
+ * wait for. Its value can be one of:
+ * - "loading": The JSON data has not been completely loaded (but the app might).
+ * - "interactive": All the JSON data has been received.
+ * - "complete" (default): Since there aren't sub-resources like images,
+ * behaves as "interactive". Note the app might not be loaded yet.
+ */
+async function addJsonViewTab(
+ url,
+ { appReadyState = "complete", docReadyState = "complete" } = {}
+) {
+ info("Adding a new JSON tab with URL: '" + url + "'");
+ const tabAdded = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ const tabLoaded = addTab(url);
+
+ // The `tabAdded` promise resolves when the JSON Viewer starts loading.
+ // This is usually what we want, however, it never resolves for unrecognized
+ // content types that trigger a download.
+ // On the other hand, `tabLoaded` always resolves, but not until the document
+ // is fully loaded, which is too late if `docReadyState !== "complete"`.
+ // Therefore, we race both promises.
+ const tab = await Promise.race([tabAdded, tabLoaded]);
+ const browser = tab.linkedBrowser;
+
+ const rootDir = getRootDirectory(gTestPath);
+
+ // Catch RequireJS errors (usually timeouts)
+ const error = tabLoaded.then(() =>
+ SpecialPowers.spawn(browser, [], function () {
+ return new Promise((resolve, reject) => {
+ const { requirejs } = content.wrappedJSObject;
+ if (requirejs) {
+ requirejs.onError = err => {
+ info(err);
+ ok(false, "RequireJS error");
+ reject(err);
+ };
+ }
+ });
+ })
+ );
+
+ const data = { rootDir, appReadyState, docReadyState };
+ await Promise.race([
+ error,
+ // eslint-disable-next-line no-shadow
+ ContentTask.spawn(browser, data, async function (data) {
+ // Check if there is a JSONView object.
+ const { JSONView } = content.wrappedJSObject;
+ if (!JSONView) {
+ throw new Error("The JSON Viewer did not load.");
+ }
+
+ const docReadyStates = ["loading", "interactive", "complete"];
+ const docReadyIndex = docReadyStates.indexOf(data.docReadyState);
+ const appReadyStates = ["uninitialized", ...docReadyStates];
+ const appReadyIndex = appReadyStates.indexOf(data.appReadyState);
+ if (docReadyIndex < 0 || appReadyIndex < 0) {
+ throw new Error("Invalid app or doc readyState parameter.");
+ }
+
+ // Wait until the document readyState suffices.
+ const { document } = content;
+ while (docReadyStates.indexOf(document.readyState) < docReadyIndex) {
+ info(
+ `DocReadyState is "${document.readyState}". Await "${data.docReadyState}"`
+ );
+ await new Promise(resolve => {
+ document.addEventListener("readystatechange", resolve, {
+ once: true,
+ });
+ });
+ }
+
+ // Wait until the app readyState suffices.
+ while (appReadyStates.indexOf(JSONView.readyState) < appReadyIndex) {
+ info(
+ `AppReadyState is "${JSONView.readyState}". Await "${data.appReadyState}"`
+ );
+ await new Promise(resolve => {
+ content.addEventListener("AppReadyStateChange", resolve, {
+ once: true,
+ });
+ });
+ }
+ }),
+ ]);
+
+ return tab;
+}
+
+/**
+ * Expanding a node in the JSON tree
+ */
+function clickJsonNode(selector) {
+ info("Expanding node: '" + selector + "'");
+
+ // eslint-disable-next-line no-shadow
+ return ContentTask.spawn(gBrowser.selectedBrowser, selector, selector => {
+ content.document.querySelector(selector).click();
+ });
+}
+
+/**
+ * Select JSON View tab (in the content).
+ */
+function selectJsonViewContentTab(name) {
+ info("Selecting tab: '" + name + "'");
+
+ // eslint-disable-next-line no-shadow
+ return ContentTask.spawn(gBrowser.selectedBrowser, name, async name => {
+ const selector = ".tabs-menu .tabs-menu-item." + CSS.escape(name) + " a";
+ const element = content.document.querySelector(selector);
+ is(element.getAttribute("aria-selected"), "false", "Tab not selected yet");
+ await new Promise(resolve => {
+ content.addEventListener("TabChanged", resolve, { once: true });
+ element.click();
+ });
+ is(element.getAttribute("aria-selected"), "true", "Tab is now selected");
+ });
+}
+
+function getElementCount(selector) {
+ info("Get element count: '" + selector + "'");
+
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector],
+ selectorChild => {
+ return content.document.querySelectorAll(selectorChild).length;
+ }
+ );
+}
+
+function getElementText(selector) {
+ info("Get element text: '" + selector + "'");
+
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector],
+ selectorChild => {
+ const element = content.document.querySelector(selectorChild);
+ return element ? element.textContent : null;
+ }
+ );
+}
+
+function getElementAttr(selector, attr) {
+ info("Get attribute '" + attr + "' for element '" + selector + "'");
+
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector, attr],
+ (selectorChild, attrChild) => {
+ const element = content.document.querySelector(selectorChild);
+ return element ? element.getAttribute(attrChild) : null;
+ }
+ );
+}
+
+function focusElement(selector) {
+ info("Focus element: '" + selector + "'");
+
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector],
+ selectorChild => {
+ const element = content.document.querySelector(selectorChild);
+ if (element) {
+ element.focus();
+ }
+ }
+ );
+}
+
+/**
+ * Send the string aStr to the focused element.
+ *
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
+ */
+function sendString(str, selector) {
+ info("Send string: '" + str + "'");
+
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector, str],
+ (selectorChild, strChild) => {
+ if (selectorChild) {
+ const element = content.document.querySelector(selectorChild);
+ if (element) {
+ element.focus();
+ }
+ }
+
+ EventUtils.sendString(strChild, content);
+ }
+ );
+}
+
+function waitForTime(delay) {
+ return new Promise(resolve => setTimeout(resolve, delay));
+}
+
+function waitForFilter() {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(resolve => {
+ const firstRow = content.document.querySelector(
+ ".jsonPanelBox .treeTable .treeRow"
+ );
+
+ // Check if the filter is already set.
+ if (firstRow.classList.contains("hidden")) {
+ resolve();
+ return;
+ }
+
+ // Wait till the first row has 'hidden' class set.
+ const observer = new content.MutationObserver(function (mutations) {
+ for (let i = 0; i < mutations.length; i++) {
+ const mutation = mutations[i];
+ if (mutation.attributeName == "class") {
+ if (firstRow.classList.contains("hidden")) {
+ observer.disconnect();
+ resolve();
+ break;
+ }
+ }
+ }
+ });
+
+ observer.observe(firstRow, { attributes: true });
+ });
+ });
+}
+
+function normalizeNewLines(value) {
+ return value.replace("(\r\n|\n)", "\n");
+}