diff options
Diffstat (limited to 'devtools/client/responsive.html/test/browser/head.js')
-rw-r--r-- | devtools/client/responsive.html/test/browser/head.js | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/devtools/client/responsive.html/test/browser/head.js b/devtools/client/responsive.html/test/browser/head.js new file mode 100644 index 000000000..3be69b0af --- /dev/null +++ b/devtools/client/responsive.html/test/browser/head.js @@ -0,0 +1,401 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../../framework/test/shared-head.js */ +/* import-globals-from ../../../framework/test/shared-redux-head.js */ +/* import-globals-from ../../../commandline/test/helpers.js */ +/* import-globals-from ../../../inspector/test/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", + this); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js", + this); + +// Import the GCLI test helper +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js", + this); + +// Import helpers registering the test-actor in remote targets +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js", + this); + +// Import helpers for the inspector that are also shared with others +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this); + +const E10S_MULTI_ENABLED = Services.prefs.getIntPref("dom.ipc.processCount") > 1; +const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/"; +const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL"; + +const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices"); +const { getOwnerWindow } = require("sdk/tabs/utils"); +const asyncStorage = require("devtools/shared/async-storage"); +const { addDevice, removeDevice } = require("devtools/client/shared/devices"); + +SimpleTest.requestCompleteLog(); +SimpleTest.waitForExplicitFinish(); + +// Toggling the RDM UI involves several docShell swap operations, which are somewhat slow +// on debug builds. Usually we are just barely over the limit, so a blanket factor of 2 +// should be enough. +requestLongerTimeout(2); + +flags.testing = true; +Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); +Services.prefs.setCharPref("devtools.devices.url", + TEST_URI_ROOT + "devices.json"); +Services.prefs.setBoolPref("devtools.responsive.html.enabled", true); + +registerCleanupFunction(() => { + flags.testing = false; + Services.prefs.clearUserPref("devtools.devices.url"); + Services.prefs.clearUserPref("devtools.responsive.html.enabled"); + Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); + asyncStorage.removeItem("devtools.devices.url_cache"); +}); + +// This depends on the "devtools.responsive.html.enabled" pref +const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm"); + +/** + * Open responsive design mode for the given tab. + */ +var openRDM = Task.async(function* (tab) { + info("Opening responsive design mode"); + let manager = ResponsiveUIManager; + let ui = yield manager.openIfNeeded(getOwnerWindow(tab), tab); + info("Responsive design mode opened"); + return { ui, manager }; +}); + +/** + * Close responsive design mode for the given tab. + */ +var closeRDM = Task.async(function* (tab, options) { + info("Closing responsive design mode"); + let manager = ResponsiveUIManager; + yield manager.closeIfNeeded(getOwnerWindow(tab), tab, options); + info("Responsive design mode closed"); +}); + +/** + * Adds a new test task that adds a tab with the given URL, opens responsive + * design mode, runs the given generator, closes responsive design mode, and + * removes the tab. + * + * Example usage: + * + * addRDMTask(TEST_URL, function*({ ui, manager }) { + * // Your tests go here... + * }); + */ +function addRDMTask(url, generator) { + add_task(function* () { + const tab = yield addTab(url); + const results = yield openRDM(tab); + + try { + yield* generator(results); + } catch (err) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(err)); + } + + yield closeRDM(tab); + yield removeTab(tab); + }); +} + +function spawnViewportTask(ui, args, task) { + return ContentTask.spawn(ui.getViewportBrowser(), args, task); +} + +function waitForFrameLoad(ui, targetURL) { + return spawnViewportTask(ui, { targetURL }, function* (args) { + if ((content.document.readyState == "complete" || + content.document.readyState == "interactive") && + content.location.href == args.targetURL) { + return; + } + yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded"); + }); +} + +function waitForViewportResizeTo(ui, width, height) { + return new Promise(Task.async(function* (resolve) { + let isSizeMatching = (data) => data.width == width && data.height == height; + + // If the viewport has already the expected size, we resolve the promise immediately. + let size = yield getContentSize(ui); + if (isSizeMatching(size)) { + resolve(); + return; + } + + // Otherwise, we'll listen to both content's resize event and browser's load end; + // since a racing condition can happen, where the content's listener is added after + // the resize, because the content's document was reloaded; therefore the test would + // hang forever. See bug 1302879. + let browser = ui.getViewportBrowser(); + + let onResize = (_, data) => { + if (!isSizeMatching(data)) { + return; + } + ui.off("content-resize", onResize); + browser.removeEventListener("mozbrowserloadend", onBrowserLoadEnd); + info(`Got content-resize to ${width} x ${height}`); + resolve(); + }; + + let onBrowserLoadEnd = Task.async(function* () { + let data = yield getContentSize(ui); + onResize(undefined, data); + }); + + info(`Waiting for content-resize to ${width} x ${height}`); + ui.on("content-resize", onResize); + browser.addEventListener("mozbrowserloadend", + onBrowserLoadEnd, { once: true }); + })); +} + +var setViewportSize = Task.async(function* (ui, manager, width, height) { + let size = ui.getViewportSize(); + info(`Current size: ${size.width} x ${size.height}, ` + + `set to: ${width} x ${height}`); + if (size.width != width || size.height != height) { + let resized = waitForViewportResizeTo(ui, width, height); + ui.setViewportSize({ width, height }); + yield resized; + } +}); + +function getElRect(selector, win) { + let el = win.document.querySelector(selector); + return el.getBoundingClientRect(); +} + +/** + * Drag an element identified by 'selector' by [x,y] amount. Returns + * the rect of the dragged element as it was before drag. + */ +function dragElementBy(selector, x, y, win) { + let React = win.require("devtools/client/shared/vendor/react"); + let { Simulate } = React.addons.TestUtils; + let rect = getElRect(selector, win); + let startPoint = { + clientX: rect.left + Math.floor(rect.width / 2), + clientY: rect.top + Math.floor(rect.height / 2), + }; + let endPoint = [ startPoint.clientX + x, startPoint.clientY + y ]; + + let elem = win.document.querySelector(selector); + + // mousedown is a React listener, need to use its testing tools to avoid races + Simulate.mouseDown(elem, startPoint); + + // mousemove and mouseup are regular DOM listeners + EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mousemove" }, win); + EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mouseup" }, win); + + return rect; +} + +function* testViewportResize(ui, selector, moveBy, + expectedViewportSize, expectedHandleMove) { + let win = ui.toolWindow; + let resized = waitForViewportResizeTo(ui, ...expectedViewportSize); + let startRect = dragElementBy(selector, ...moveBy, win); + yield resized; + + let endRect = getElRect(selector, win); + is(endRect.left - startRect.left, expectedHandleMove[0], + `The x move of ${selector} is as expected`); + is(endRect.top - startRect.top, expectedHandleMove[1], + `The y move of ${selector} is as expected`); +} + +function openDeviceModal({ toolWindow }) { + let { document } = toolWindow; + let React = toolWindow.require("devtools/client/shared/vendor/react"); + let { Simulate } = React.addons.TestUtils; + let select = document.querySelector(".viewport-device-selector"); + let modal = document.querySelector("#device-modal-wrapper"); + + info("Checking initial device modal state"); + ok(modal.classList.contains("closed") && !modal.classList.contains("opened"), + "The device modal is closed by default."); + + info("Opening device modal through device selector."); + select.value = OPEN_DEVICE_MODAL_VALUE; + Simulate.change(select); + ok(modal.classList.contains("opened") && !modal.classList.contains("closed"), + "The device modal is displayed."); +} + +function changeSelectValue({ toolWindow }, selector, value) { + info(`Selecting ${value} in ${selector}.`); + + return new Promise(resolve => { + let select = toolWindow.document.querySelector(selector); + isnot(select, null, `selector "${selector}" should match an existing element.`); + + let option = [...select.options].find(o => o.value === String(value)); + isnot(option, undefined, `value "${value}" should match an existing option.`); + + let event = new toolWindow.UIEvent("change", { + view: toolWindow, + bubbles: true, + cancelable: true + }); + + select.addEventListener("change", () => { + is(select.value, value, + `Select's option with value "${value}" should be selected.`); + resolve(); + }, { once: true }); + + select.value = value; + select.dispatchEvent(event); + }); +} + +const selectDevice = (ui, value) => Promise.all([ + once(ui, "device-changed"), + changeSelectValue(ui, ".viewport-device-selector", value) +]); + +const selectDPR = (ui, value) => + changeSelectValue(ui, "#global-dpr-selector > select", value); + +const selectNetworkThrottling = (ui, value) => Promise.all([ + once(ui, "network-throttling-changed"), + changeSelectValue(ui, "#global-network-throttling-selector", value) +]); + +function getSessionHistory(browser) { + return ContentTask.spawn(browser, {}, function* () { + /* eslint-disable no-undef */ + let { interfaces: Ci } = Components; + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let sessionHistory = webNav.sessionHistory; + let result = { + index: sessionHistory.index, + entries: [] + }; + + for (let i = 0; i < sessionHistory.count; i++) { + let entry = sessionHistory.getEntryAtIndex(i, false); + result.entries.push({ + uri: entry.URI.spec, + title: entry.title + }); + } + + return result; + /* eslint-enable no-undef */ + }); +} + +function getContentSize(ui) { + return spawnViewportTask(ui, {}, () => ({ + width: content.screen.width, + height: content.screen.height + })); +} + +function waitForPageShow(browser) { + let mm = browser.messageManager; + return new Promise(resolve => { + let onShow = message => { + if (message.target != browser) { + return; + } + mm.removeMessageListener("PageVisibility:Show", onShow); + resolve(); + }; + mm.addMessageListener("PageVisibility:Show", onShow); + }); +} + +function waitForViewportLoad(ui) { + return new Promise(resolve => { + let browser = ui.getViewportBrowser(); + browser.addEventListener("mozbrowserloadend", () => { + resolve(); + }, { once: true }); + }); +} + +function load(browser, url) { + let loaded = BrowserTestUtils.browserLoaded(browser, false, url); + browser.loadURI(url, null, null); + return loaded; +} + +function back(browser) { + let shown = waitForPageShow(browser); + browser.goBack(); + return shown; +} + +function forward(browser) { + let shown = waitForPageShow(browser); + browser.goForward(); + return shown; +} + +function addDeviceForTest(device) { + info(`Adding Test Device "${device.name}" to the list.`); + addDevice(device); + + registerCleanupFunction(() => { + // Note that assertions in cleanup functions are not displayed unless they failed. + ok(removeDevice(device), `Removed Test Device "${device.name}" from the list.`); + }); +} + +function waitForClientClose(ui) { + return new Promise(resolve => { + info("Waiting for RDM debugger client to close"); + ui.client.addOneTimeListener("closed", () => { + info("RDM's debugger client is now closed"); + resolve(); + }); + }); +} + +function* testTouchEventsOverride(ui, expected) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + + let flag = yield ui.emulationFront.getTouchEventsOverride(); + is(flag === Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED, expected, + `Touch events override should be ${expected ? "enabled" : "disabled"}`); + is(touchButton.classList.contains("active"), expected, + `Touch simulation button should be ${expected ? "" : "in"}active.`); +} + +function testViewportDeviceSelectLabel(ui, expected) { + info("Test viewport's device select label"); + + let select = ui.toolWindow.document.querySelector(".viewport-device-selector"); + is(select.selectedOptions[0].textContent, expected, + `Device Select value should be: ${expected}`); +} + +function* enableTouchSimulation(ui) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + let loaded = waitForViewportLoad(ui); + touchButton.click(); + yield loaded; +} |