diff options
Diffstat (limited to 'devtools/client/shared/test/head.js')
-rw-r--r-- | devtools/client/shared/test/head.js | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/devtools/client/shared/test/head.js b/devtools/client/shared/test/head.js new file mode 100644 index 000000000..fbf0e84af --- /dev/null +++ b/devtools/client/shared/test/head.js @@ -0,0 +1,346 @@ +/* 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/. */ +/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */ +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); + +const {DOMHelpers} = Cu.import("resource://devtools/client/shared/DOMHelpers.jsm", {}); +const {Hosts} = require("devtools/client/framework/toolbox-hosts"); + +const TEST_URI_ROOT = "http://example.com/browser/devtools/client/shared/test/"; +const OPTIONS_VIEW_URL = TEST_URI_ROOT + "doc_options-view.xul"; + +function catchFail(func) { + return function () { + try { + return func.apply(null, arguments); + } catch (ex) { + ok(false, ex); + console.error(ex); + finish(); + throw ex; + } + }; +} + +/** + * Polls a given function waiting for the given value. + * + * @param object options + * Options object with the following properties: + * - validator + * A validator function that should return the expected value. This is + * called every few milliseconds to check if the result is the expected + * one. When the returned result is the expected one, then the |success| + * function is called and polling stops. If |validator| never returns + * the expected value, then polling timeouts after several tries and + * a failure is recorded - the given |failure| function is invoked. + * - success + * A function called when the validator function returns the expected + * value. + * - failure + * A function called if the validator function timeouts - fails to return + * the expected value in the given time. + * - name + * Name of test. This is used to generate the success and failure + * messages. + * - timeout + * Timeout for validator function, in milliseconds. Default is 5000 ms. + * - value + * The expected value. If this option is omitted then the |validator| + * function must return a trueish value. + * Each of the provided callback functions will receive two arguments: + * the |options| object and the last value returned by |validator|. + */ +function waitForValue(options) { + let start = Date.now(); + let timeout = options.timeout || 5000; + let lastValue; + + function wait(validatorFn, successFn, failureFn) { + if ((Date.now() - start) > timeout) { + // Log the failure. + ok(false, "Timed out while waiting for: " + options.name); + let expected = "value" in options ? + "'" + options.value + "'" : + "a trueish value"; + info("timeout info :: got '" + lastValue + "', expected " + expected); + failureFn(options, lastValue); + return; + } + + lastValue = validatorFn(options, lastValue); + let successful = "value" in options ? + lastValue == options.value : + lastValue; + if (successful) { + ok(true, options.name); + successFn(options, lastValue); + } else { + setTimeout(() => { + wait(validatorFn, successFn, failureFn); + }, 100); + } + } + + wait(options.validator, options.success, options.failure); +} + +function oneTimeObserve(name, callback) { + return new Promise((resolve) => { + let func = function () { + Services.obs.removeObserver(func, name); + if (callback) { + callback(); + } + resolve(); + }; + Services.obs.addObserver(func, name, false); + }); +} + +let createHost = +Task.async(function* (type = "bottom", src = "data:text/html;charset=utf-8,") { + let host = new Hosts[type](gBrowser.selectedTab); + let iframe = yield host.create(); + + yield new Promise(resolve => { + let domHelper = new DOMHelpers(iframe.contentWindow); + iframe.setAttribute("src", src); + domHelper.onceDOMReady(resolve); + }); + + return [host, iframe.contentWindow, iframe.contentDocument]; +}); + +/** + * Check the correctness of the data recorded in Telemetry after + * loadTelemetryAndRecordLogs was called. + */ +function checkTelemetryResults(Telemetry) { + let result = Telemetry.prototype.telemetryInfo; + + for (let histId in result) { + let value = result[histId]; + + if (histId.endsWith("OPENED_COUNT")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function (element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function (element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } +} + +/** + * Open and close the toolbox in the current browser tab, several times, waiting + * some amount of time in between. + * @param {Number} nbOfTimes + * @param {Number} usageTime in milliseconds + * @param {String} toolId + */ +function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) { + for (let i = 0; i < nbOfTimes; i++) { + info("Opening toolbox " + (i + 1)); + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.showToolbox(target, toolId); + + // We use a timeout to check the toolbox's active time + yield new Promise(resolve => setTimeout(resolve, usageTime)); + + info("Closing toolbox " + (i + 1)); + yield gDevTools.closeToolbox(target); + } +} + +/** + * Synthesize a profile for testing. + */ +function synthesizeProfileForTest(samples) { + const RecordingUtils = require("devtools/shared/performance/recording-utils"); + + samples.unshift({ + time: 0, + frames: [] + }); + + let uniqueStacks = new RecordingUtils.UniqueStacks(); + return RecordingUtils.deflateThread({ + samples: samples, + markers: [] + }, uniqueStacks); +} + +/** + * Waits until a predicate returns true. + * + * @param function predicate + * Invoked once in a while until it returns true. + * @param number interval [optional] + * How often the predicate is invoked, in milliseconds. + */ +function waitUntil(predicate, interval = 10) { + if (predicate()) { + return Promise.resolve(true); + } + return new Promise(resolve => { + setTimeout(function () { + waitUntil(predicate).then(() => resolve(true)); + }, interval); + }); +} + +/** + * Show the presets list sidebar in the cssfilter widget popup + * @param {CSSFilterWidget} widget + * @return {Promise} + */ +function showFilterPopupPresets(widget) { + let onRender = widget.once("render"); + widget._togglePresets(); + return onRender; +} + +/** + * Show presets list and create a sample preset with the name and value provided + * @param {CSSFilterWidget} widget + * @param {string} name + * @param {string} value + * @return {Promise} + */ +let showFilterPopupPresetsAndCreatePreset = +Task.async(function* (widget, name, value) { + yield showFilterPopupPresets(widget); + + let onRender = widget.once("render"); + widget.setCssValue(value); + yield onRender; + + let footer = widget.el.querySelector(".presets-list .footer"); + footer.querySelector("input").value = name; + + onRender = widget.once("render"); + widget._savePreset({ + preventDefault: () => {} + }); + + yield onRender; +}); + +/** + * Utility function for testing CSS code samples that have been + * syntax-highlighted. + * + * The CSS syntax highlighter emits a collection of DOM nodes that have + * CSS classes applied to them. This function checks that those nodes + * are what we expect. + * + * @param {array} expectedNodes + * A representation of the nodes we expect to see. + * Each node is an object containing two properties: + * - type: a string which can be one of: + * - text, comment, property-name, property-value + * - text: the textContent of the node + * + * For example, given a string like this: + * "<comment> The part we want </comment>\n this: is-the-part-we-want;" + * + * we would represent the expected output like this: + * [{type: "comment", text: "<comment> The part we want </comment>"}, + * {type: "text", text: "\n"}, + * {type: "property-name", text: "this"}, + * {type: "text", text: ":"}, + * {type: "text", text: " "}, + * {type: "property-value", text: "is-the-part-we-want"}, + * {type: "text", text: ";"}]; + * + * @param {Node} parent + * The DOM node whose children are the output of the syntax highlighter. + */ +function checkCssSyntaxHighlighterOutput(expectedNodes, parent) { + /** + * The classes applied to the output nodes by the syntax highlighter. + * These must be same as the definitions in MdnDocsWidget.js. + */ + const PROPERTY_NAME_COLOR = "theme-fg-color5"; + const PROPERTY_VALUE_COLOR = "theme-fg-color1"; + const COMMENT_COLOR = "theme-comment"; + + /** + * Check the type and content of a single node. + */ + function checkNode(expected, actual) { + ok(actual.textContent == expected.text, + "Check that node has the expected textContent"); + info("Expected text content: [" + expected.text + "]"); + info("Actual text content: [" + actual.textContent + "]"); + + info("Check that node has the expected type"); + if (expected.type == "text") { + ok(actual.nodeType == 3, "Check that node is a text node"); + } else { + ok(actual.tagName.toUpperCase() == "SPAN", "Check that node is a SPAN"); + } + + info("Check that node has the expected className"); + + let expectedClassName = null; + let actualClassName = null; + + switch (expected.type) { + case "property-name": + expectedClassName = PROPERTY_NAME_COLOR; + break; + case "property-value": + expectedClassName = PROPERTY_VALUE_COLOR; + break; + case "comment": + expectedClassName = COMMENT_COLOR; + break; + default: + ok(!actual.classList, "No className expected"); + return; + } + + ok(actual.classList.length == 1, "One className expected"); + actualClassName = actual.classList[0]; + + ok(expectedClassName == actualClassName, "Check className value"); + info("Expected className: " + expectedClassName); + info("Actual className: " + actualClassName); + } + + info("Logging the actual nodes we have:"); + for (let j = 0; j < parent.childNodes.length; j++) { + let n = parent.childNodes[j]; + info(j + " / " + + "nodeType: " + n.nodeType + " / " + + "textContent: " + n.textContent); + } + + ok(parent.childNodes.length == parent.childNodes.length, + "Check we have the expected number of nodes"); + info("Expected node count " + expectedNodes.length); + info("Actual node count " + expectedNodes.length); + + for (let i = 0; i < expectedNodes.length; i++) { + info("Check node " + i); + checkNode(expectedNodes[i], parent.childNodes[i]); + } +} |