diff options
Diffstat (limited to 'devtools/client/jsonview/test/head.js')
-rw-r--r-- | devtools/client/jsonview/test/head.js | 278 |
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"); +} |