diff options
Diffstat (limited to 'devtools/client/responsivedesign/test')
13 files changed, 1391 insertions, 0 deletions
diff --git a/devtools/client/responsivedesign/test/.eslintrc.js b/devtools/client/responsivedesign/test/.eslintrc.js new file mode 100644 index 000000000..ba1263286 --- /dev/null +++ b/devtools/client/responsivedesign/test/.eslintrc.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../.eslintrc.mochitests.js", + "globals": { + "ResponsiveUI": true, + "helpers": true + } +}; diff --git a/devtools/client/responsivedesign/test/browser.ini b/devtools/client/responsivedesign/test/browser.ini new file mode 100644 index 000000000..6a8f5a8d9 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser.ini @@ -0,0 +1,21 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + touch.html + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + +[browser_responsive_cmd.js] +[browser_responsivecomputedview.js] +skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s +[browser_responsiveruleview.js] +skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s +[browser_responsiveui.js] +[browser_responsiveui_touch.js] +[browser_responsiveuiaddcustompreset.js] +[browser_responsive_devicewidth.js] +[browser_responsiveui_customuseragent.js] +[browser_responsiveui_window_close.js] +skip-if = (os == 'linux') && e10s && debug # Bug 1277274 diff --git a/devtools/client/responsivedesign/test/browser_responsive_cmd.js b/devtools/client/responsivedesign/test/browser_responsive_cmd.js new file mode 100644 index 000000000..8c8e798d0 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsive_cmd.js @@ -0,0 +1,143 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy"); + +function test() { + let manager = ResponsiveUIManager; + let done; + + function isOpen() { + return gBrowser.getBrowserContainer(gBrowser.selectedBrowser) + .hasAttribute("responsivemode"); + } + + helpers.addTabWithToolbar("data:text/html;charset=utf-8,hi", (options) => { + return helpers.audit(options, [ + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize toggle"); + }, + check: { + input: "resize toggle", + hints: "", + markup: "VVVVVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize toggle"); + }, + check: { + input: "resize toggle", + hints: "", + markup: "VVVVVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize on"); + }, + check: { + input: "resize on", + hints: "", + markup: "VVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize off"); + }, + check: { + input: "resize off", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize to 400 400"); + }, + check: { + input: "resize to 400 400", + hints: "", + markup: "VVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + width: { value: 400 }, + height: { value: 400 }, + } + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize off"); + }, + check: { + input: "resize off", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + ]); + }).then(finish); +} diff --git a/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js b/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js new file mode 100644 index 000000000..604a20783 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsive_devicewidth.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + let tab = yield addTab("about:logo"); + let { rdm, manager } = yield openRDM(tab); + ok(rdm, "An instance of the RDM should be attached to the tab."); + yield setSize(rdm, manager, 110, 500); + + info("Checking initial width/height properties."); + yield doInitialChecks(); + + info("Changing the RDM size"); + yield setSize(rdm, manager, 90, 500); + + info("Checking for screen props"); + yield checkScreenProps(); + + info("Setting docShell.deviceSizeIsPageSize to false"); + yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docShell.deviceSizeIsPageSize = false; + }); + + info("Checking for screen props once again."); + yield checkScreenProps2(); + + yield closeRDM(rdm); +}); + +function* doInitialChecks() { + let {innerWidth, matchesMedia} = yield grabContentInfo(); + is(innerWidth, 110, "initial width should be 110px"); + ok(!matchesMedia, "media query shouldn't match."); +} + +function* checkScreenProps() { + let {matchesMedia, screen} = yield grabContentInfo(); + ok(matchesMedia, "media query should match"); + isnot(window.screen.width, screen.width, + "screen.width should not be the size of the screen."); + is(screen.width, 90, "screen.width should be the page width"); + is(screen.height, 500, "screen.height should be the page height"); +} + +function* checkScreenProps2() { + let {matchesMedia, screen} = yield grabContentInfo(); + ok(!matchesMedia, "media query should be re-evaluated."); + is(window.screen.width, screen.width, + "screen.width should be the size of the screen."); +} + +function grabContentInfo() { + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + return { + screen: { + width: content.screen.width, + height: content.screen.height + }, + innerWidth: content.innerWidth, + matchesMedia: content.matchMedia("(max-device-width:100px)").matches + }; + }); +} diff --git a/devtools/client/responsivedesign/test/browser_responsivecomputedview.js b/devtools/client/responsivedesign/test/browser_responsivecomputedview.js new file mode 100644 index 000000000..eee2dbc03 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsivecomputedview.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the computed-view refreshes. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +add_task(function* () { + yield addTab(TEST_URI); + + info("Open the responsive design mode and set its size to 500x500 to start"); + let { rdm, manager } = yield openRDM(); + yield setSize(rdm, manager, 500, 500); + + info("Open the inspector, computed-view and select the test node"); + let {inspector, view} = yield openComputedView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, inspector, rdm, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, inspector, rdm, manager); + + yield closeRDM(rdm); + yield closeToolbox(); +}); + +function* testShrink(computedView, inspector, rdm, manager) { + is(computedWidth(computedView), "500px", "Should show 500px initially."); + + let onRefresh = inspector.once("computed-view-refreshed"); + yield setSize(rdm, manager, 100, 100); + yield onRefresh; + + is(computedWidth(computedView), "100px", "Should be 100px after shrinking."); +} + +function* testGrow(computedView, inspector, rdm, manager) { + let onRefresh = inspector.once("computed-view-refreshed"); + yield setSize(rdm, manager, 500, 500); + yield onRefresh; + + is(computedWidth(computedView), "500px", "Should be 500px after growing."); +} + +function computedWidth(computedView) { + for (let prop of computedView.propertyViews) { + if (prop.name === "width") { + return prop.valueNode.textContent; + } + } + return null; +} diff --git a/devtools/client/responsivedesign/test/browser_responsiveruleview.js b/devtools/client/responsivedesign/test/browser_responsiveruleview.js new file mode 100644 index 000000000..5c3698e78 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveruleview.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the rule-view refreshes. +// Also test that ESC does open the split-console, and that the RDM menu item +// gets updated correctly when needed. +// TODO: split this test. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +add_task(function* () { + yield addTab(TEST_URI); + + info("Open the responsive design mode and set its size to 500x500 to start"); + let { rdm, manager } = yield openRDM(); + yield setSize(rdm, manager, 500, 500); + + info("Open the inspector, rule-view and select the test node"); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, rdm, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, rdm, manager); + + info("Check that ESC still opens the split console"); + yield testEscapeOpensSplitConsole(inspector); + + yield closeToolbox(); + + info("Test the state of the RDM menu item"); + yield testMenuItem(rdm); + + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); +}); + +function* testShrink(ruleView, rdm, manager) { + is(numberOfRules(ruleView), 2, "Should have two rules initially."); + + info("Resize to 100x100 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setSize(rdm, manager, 100, 100); + yield onRefresh; + + is(numberOfRules(ruleView), 3, "Should have three rules after shrinking."); +} + +function* testGrow(ruleView, rdm, manager) { + info("Resize to 500x500 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setSize(rdm, manager, 500, 500); + yield onRefresh; + + is(numberOfRules(ruleView), 2, "Should have two rules after growing."); +} + +function* testEscapeOpensSplitConsole(inspector) { + ok(!inspector._toolbox._splitConsole, "Console is not split."); + + info("Press escape"); + let onSplit = inspector._toolbox.once("split-console"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onSplit; + + ok(inspector._toolbox._splitConsole, "Console is split after pressing ESC."); +} + +function* testMenuItem(rdm) { + is(document.getElementById("menu_responsiveUI").getAttribute("checked"), + "true", "The menu item is checked"); + + yield closeRDM(rdm); + + is(document.getElementById("menu_responsiveUI").getAttribute("checked"), + "false", "The menu item is unchecked"); +} + +function numberOfRules(ruleView) { + return ruleView.element.querySelectorAll(".ruleview-code").length; +} diff --git a/devtools/client/responsivedesign/test/browser_responsiveui.js b/devtools/client/responsivedesign/test/browser_responsiveui.js new file mode 100644 index 000000000..283974d0f --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveui.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + let tab = yield addTab("data:text/html,mop"); + + let {rdm, manager} = yield openRDM(tab, "menu"); + let container = gBrowser.getBrowserContainer(); + is(container.getAttribute("responsivemode"), "true", + "Should be in responsive mode."); + is(document.getElementById("menu_responsiveUI").getAttribute("checked"), + "true", "Menu item should be checked"); + + ok(rdm, "An instance of the RDM should be attached to the tab."); + + let originalWidth = (yield getSizing()).width; + + let documentLoaded = waitForDocLoadComplete(); + gBrowser.loadURI("data:text/html;charset=utf-8,mop" + + "<div style%3D'height%3A5000px'><%2Fdiv>"); + yield documentLoaded; + + let newWidth = (yield getSizing()).width; + is(originalWidth, newWidth, "Floating scrollbars shouldn't change the width"); + + yield testPresets(rdm, manager); + + info("Testing mouse resizing"); + yield testManualMouseResize(rdm, manager); + + info("Testing mouse resizing with shift key"); + yield testManualMouseResize(rdm, manager, "shift"); + + info("Testing mouse resizing with ctrl key"); + yield testManualMouseResize(rdm, manager, "ctrl"); + + info("Testing resizing with user custom keyboard input"); + yield testResizeUsingCustomInput(rdm, manager); + + info("Testing invalid keyboard input"); + yield testInvalidUserInput(rdm); + + info("Testing rotation"); + yield testRotate(rdm, manager); + + let {width: widthBeforeClose, height: heightBeforeClose} = yield getSizing(); + + info("Restarting responsive mode"); + yield closeRDM(rdm); + + let resized = waitForResizeTo(manager, widthBeforeClose, heightBeforeClose); + ({rdm} = yield openRDM(tab, "keyboard")); + yield resized; + + let currentSize = yield getSizing(); + is(currentSize.width, widthBeforeClose, "width should be restored"); + is(currentSize.height, heightBeforeClose, "height should be restored"); + + container = gBrowser.getBrowserContainer(); + is(container.getAttribute("responsivemode"), "true", "In responsive mode."); + is(document.getElementById("menu_responsiveUI").getAttribute("checked"), + "true", "menu item should be checked"); + + let isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1; + if (!isWinXP) { + yield testScreenshot(rdm); + } + + yield closeRDM(rdm); + is(document.getElementById("menu_responsiveUI").getAttribute("checked"), + "false", "menu item should be unchecked"); +}); + +function* testPresets(rdm, manager) { + // Starting from length - 4 because last 3 items are not presets : + // the separator, the add button and the remove button + for (let c = rdm.menulist.firstChild.childNodes.length - 4; c >= 0; c--) { + let item = rdm.menulist.firstChild.childNodes[c]; + let [width, height] = extractSizeFromString(item.getAttribute("label")); + yield setPresetIndex(rdm, manager, c); + + let {width: contentWidth, height: contentHeight} = yield getSizing(); + is(contentWidth, width, "preset" + c + ": the width should be changed"); + is(contentHeight, height, "preset" + c + ": the height should be changed"); + } +} + +function* testManualMouseResize(rdm, manager, pressedKey) { + yield setSize(rdm, manager, 100, 100); + + let {width: initialWidth, height: initialHeight} = yield getSizing(); + is(initialWidth, 100, "Width should be reset to 100"); + is(initialHeight, 100, "Height should be reset to 100"); + + let x = 2, y = 2; + EventUtils.synthesizeMouse(rdm.resizer, x, y, {type: "mousedown"}, window); + + let mouseMoveParams = {type: "mousemove"}; + if (pressedKey == "shift") { + x += 23; y += 10; + mouseMoveParams.shiftKey = true; + } else if (pressedKey == "ctrl") { + x += 120; y += 60; + mouseMoveParams.ctrlKey = true; + } else { + x += 20; y += 10; + } + + EventUtils.synthesizeMouse(rdm.resizer, x, y, mouseMoveParams, window); + EventUtils.synthesizeMouse(rdm.resizer, x, y, {type: "mouseup"}, window); + + yield once(manager, "content-resize"); + + let expectedWidth = initialWidth + 20; + let expectedHeight = initialHeight + 10; + info("initial width: " + initialWidth); + info("initial height: " + initialHeight); + + yield verifyResize(rdm, expectedWidth, expectedHeight); +} + +function* testResizeUsingCustomInput(rdm, manager) { + let {width: initialWidth, height: initialHeight} = yield getSizing(); + let expectedWidth = initialWidth - 20, expectedHeight = initialHeight - 10; + + let userInput = expectedWidth + " x " + expectedHeight; + rdm.menulist.inputField.value = ""; + rdm.menulist.focus(); + processStringAsKey(userInput); + + // While typing, the size should not change + let currentSize = yield getSizing(); + is(currentSize.width, initialWidth, "Typing shouldn't change the width"); + is(currentSize.height, initialHeight, "Typing shouldn't change the height"); + + // Only the `change` event must change the size + EventUtils.synthesizeKey("VK_RETURN", {}); + + yield once(manager, "content-resize"); + + yield verifyResize(rdm, expectedWidth, expectedHeight); +} + +function* testInvalidUserInput(rdm) { + let {width: initialWidth, height: initialHeight} = yield getSizing(); + let index = rdm.menulist.selectedIndex; + let expectedValue = initialWidth + "\u00D7" + initialHeight; + let expectedLabel = rdm.menulist.firstChild.firstChild.getAttribute("label"); + + let userInput = "I'm wrong"; + + rdm.menulist.inputField.value = ""; + rdm.menulist.focus(); + processStringAsKey(userInput); + EventUtils.synthesizeKey("VK_RETURN", {}); + + let currentSize = yield getSizing(); + is(currentSize.width, initialWidth, "Width should not change"); + is(currentSize.height, initialHeight, "Height should not change"); + is(rdm.menulist.selectedIndex, index, "Selected item should not change."); + is(rdm.menulist.value, expectedValue, "Value should be reset"); + + let label = rdm.menulist.firstChild.firstChild.getAttribute("label"); + is(label, expectedLabel, "Custom menuitem's label should not change"); +} + +function* testRotate(rdm, manager) { + yield setSize(rdm, manager, 100, 200); + + let {width: initialWidth, height: initialHeight} = yield getSizing(); + rdm.rotate(); + + yield once(manager, "content-resize"); + + let newSize = yield getSizing(); + is(newSize.width, initialHeight, "The width should now be the height."); + is(newSize.height, initialWidth, "The height should now be the width."); + + let label = rdm.menulist.firstChild.firstChild.getAttribute("label"); + let [width, height] = extractSizeFromString(label); + is(width, initialHeight, "Width in label should be updated"); + is(height, initialWidth, "Height in label should be updated"); +} + +function* verifyResize(rdm, expectedWidth, expectedHeight) { + let currentSize = yield getSizing(); + is(currentSize.width, expectedWidth, "Width should now change"); + is(currentSize.height, expectedHeight, "Height should now change"); + + is(rdm.menulist.selectedIndex, -1, "Custom menuitem cannot be selected"); + + let label = rdm.menulist.firstChild.firstChild.getAttribute("label"); + let value = rdm.menulist.value; + isnot(label, value, + "The menulist item label should be different than the menulist value"); + + let [width, height] = extractSizeFromString(label); + is(width, expectedWidth, "Width in label should be updated"); + is(height, expectedHeight, "Height in label should be updated"); + + [width, height] = extractSizeFromString(value); + is(width, expectedWidth, "Value should be updated with new width"); + is(height, expectedHeight, "Value should be updated with new height"); +} + +function* testScreenshot(rdm) { + info("Testing screenshot"); + rdm.screenshot("responsiveui"); + let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); + + while (true) { + // while(true) until we find the file. + // no need for a timeout, the test will get killed anyway. + let file = FileUtils.getFile("DfltDwnld", [ "responsiveui.png" ]); + if (file.exists()) { + ok(true, "Screenshot file exists"); + file.remove(false); + break; + } + info("checking if file exists in 200ms"); + yield wait(200); + } +} + +function* getSizing() { + let browser = gBrowser.selectedBrowser; + let sizing = yield ContentTask.spawn(browser, {}, function* () { + return { + width: content.innerWidth, + height: content.innerHeight + }; + }); + return sizing; +} + +function extractSizeFromString(str) { + let numbers = str.match(/(\d+)[^\d]*(\d+)/); + if (numbers) { + return [numbers[1], numbers[2]]; + } + return [null, null]; +} + +function processStringAsKey(str) { + for (let i = 0, l = str.length; i < l; i++) { + EventUtils.synthesizeKey(str.charAt(i), {}); + } +} diff --git a/devtools/client/responsivedesign/test/browser_responsiveui_customuseragent.js b/devtools/client/responsivedesign/test/browser_responsiveui_customuseragent.js new file mode 100644 index 000000000..35efc4c14 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveui_customuseragent.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URI = "data:text/html, Custom User Agent test"; +const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"] + .getService(Ci.nsIHttpProtocolHandler) + .userAgent; +const CHROME_UA = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36" + + " (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"; +add_task(function* () { + yield addTab(TEST_URI); + + let {rdm, manager} = yield openRDM(); + yield testUserAgent(DEFAULT_UA); + + info("Setting UA to " + CHROME_UA); + yield setUserAgent(CHROME_UA, rdm, manager); + yield testUserAgent(CHROME_UA); + + info("Resetting UA"); + yield setUserAgent("", rdm, manager); + yield testUserAgent(DEFAULT_UA); + + info("Setting UA to " + CHROME_UA); + yield setUserAgent(CHROME_UA, rdm, manager); + yield testUserAgent(CHROME_UA); + + info("Closing responsive mode"); + + yield closeRDM(rdm); + yield testUserAgent(DEFAULT_UA); +}); + +function* setUserAgent(ua, rdm, manager) { + let input = rdm.userAgentInput; + input.focus(); + input.value = ua; + let onUAChanged = once(manager, "userAgentChanged"); + input.blur(); + yield onUAChanged; + + if (ua !== "") { + ok(input.hasAttribute("attention"), "UA input should be highlighted"); + } else { + ok(!input.hasAttribute("attention"), "UA input shouldn't be highlighted"); + } +} + +function* testUserAgent(value) { + let ua = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + return content.navigator.userAgent; + }); + is(ua, value, `UA should be set to ${value}`); +} diff --git a/devtools/client/responsivedesign/test/browser_responsiveui_touch.js b/devtools/client/responsivedesign/test/browser_responsiveui_touch.js new file mode 100644 index 000000000..c23d2dd12 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveui_touch.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URI = "http://mochi.test:8888/browser/devtools/client/" + + "responsivedesign/test/touch.html"; +const layoutReflowSynthMouseMove = "layout.reflow.synthMouseMove"; +const domViewportEnabled = "dom.meta-viewport.enabled"; + +add_task(function* () { + let tab = yield addTab(TEST_URI); + let {rdm} = yield openRDM(tab); + yield pushPrefs([layoutReflowSynthMouseMove, false]); + yield testWithNoTouch(); + yield rdm.enableTouch(); + yield testWithTouch(); + yield rdm.disableTouch(); + yield testWithNoTouch(); + yield closeRDM(rdm); +}); + +function* testWithNoTouch() { + let div = content.document.querySelector("div"); + let x = 0, y = 0; + + info("testWithNoTouch: Initial test parameter and mouse mouse outside div element"); + x = -1, y = -1; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + div.style.transform = "none"; + div.style.backgroundColor = ""; + + info("testWithNoTouch: Move mouse into the div element"); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { type: "mousemove", isSynthesized: false }, + gBrowser.selectedBrowser); + is(div.style.backgroundColor, "red", "mouseenter or mouseover should work"); + + info("testWithNoTouch: Drag the div element"); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { type: "mousedown", isSynthesized: false }, + gBrowser.selectedBrowser); + x = 100; y = 100; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + is(div.style.transform, "none", "touchmove shouldn't work"); + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mouseup", isSynthesized: false }, gBrowser.selectedBrowser); + + info("testWithNoTouch: Move mouse out of the div element"); + x = -1; y = -1; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + is(div.style.backgroundColor, "blue", "mouseout or mouseleave should work"); + + info("testWithNoTouch: Click the div element"); + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", "300ms delay between touch events and mouse events should not work"); +} + +function* testWithTouch() { + let div = content.document.querySelector("div"); + let x = 0, y = 0; + + info("testWithTouch: Initial test parameter and mouse mouse outside div element"); + x = -1, y = -1; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + div.style.transform = "none"; + div.style.backgroundColor = ""; + + info("testWithTouch: Move mouse into the div element"); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { type: "mousemove", isSynthesized: false }, + gBrowser.selectedBrowser); + isnot(div.style.backgroundColor, "red", "mouseenter or mouseover should not work"); + + info("testWithTouch: Drag the div element"); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { type: "mousedown", isSynthesized: false }, + gBrowser.selectedBrowser); + x = 100; y = 100; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + isnot(div.style.transform, "none", "touchmove should work"); + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mouseup", isSynthesized: false }, gBrowser.selectedBrowser); + + info("testWithTouch: Move mouse out of the div element"); + x = -1; y = -1; + yield BrowserTestUtils.synthesizeMouse("div", x, y, + { type: "mousemove", isSynthesized: false }, gBrowser.selectedBrowser); + isnot(div.style.backgroundColor, "blue", "mouseout or mouseleave should not work"); + + yield testWithMetaViewportEnabled(); + yield testWithMetaViewportDisabled(); +} + +function* testWithMetaViewportEnabled() { + yield pushPrefs([domViewportEnabled, true]); + let meta = content.document.querySelector("meta[name=viewport]"); + let div = content.document.querySelector("div"); + div.dataset.isDelay = "false"; + + info("testWithMetaViewportEnabled: click the div element with <meta name='viewport'>"); + meta.content = ""; + yield synthesizeClick(div); + is(div.dataset.isDelay, "true", "300ms delay between touch events and mouse events should work"); + + info("testWithMetaViewportEnabled: click the div element with <meta name='viewport' content='user-scalable=no'>"); + meta.content = "user-scalable=no"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", "300ms delay between touch events and mouse events should not work"); + + info("testWithMetaViewportEnabled: click the div element with <meta name='viewport' content='minimum-scale=maximum-scale'>"); + meta.content = "minimum-scale=maximum-scale"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", "300ms delay between touch events and mouse events should not work"); + + info("testWithMetaViewportEnabled: click the div element with <meta name='viewport' content='width=device-width'>"); + meta.content = "width=device-width"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", "300ms delay between touch events and mouse events should not work"); +} + +function* testWithMetaViewportDisabled() { + yield pushPrefs([domViewportEnabled, false]); + let meta = content.document.querySelector("meta[name=viewport]"); + let div = content.document.querySelector("div"); + div.dataset.isDelay = "false"; + + info("testWithMetaViewportDisabled: click the div element with <meta name='viewport'>"); + meta.content = ""; + yield synthesizeClick(div); + is(div.dataset.isDelay, "true", "300ms delay between touch events and mouse events should work"); +} + +function synthesizeClick(element) { + let waitForClickEvent = BrowserTestUtils.waitForEvent(element, "click"); + BrowserTestUtils.synthesizeMouseAtCenter(element, { type: "mousedown", isSynthesized: false }, + gBrowser.selectedBrowser); + BrowserTestUtils.synthesizeMouseAtCenter(element, { type: "mouseup", isSynthesized: false }, + gBrowser.selectedBrowser); + return waitForClickEvent; +} + +function pushPrefs(...aPrefs) { + let deferred = promise.defer(); + SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); + return deferred.promise; +} diff --git a/devtools/client/responsivedesign/test/browser_responsiveui_window_close.js b/devtools/client/responsivedesign/test/browser_responsiveui_window_close.js new file mode 100644 index 000000000..a5f890a86 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveui_window_close.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + let newWindowPromise = BrowserTestUtils.waitForNewWindow(); + window.open("about:blank", "_blank"); + let newWindow = yield newWindowPromise; + + newWindow.focus(); + yield once(newWindow.gBrowser, "load", true); + + let tab = newWindow.gBrowser.selectedTab; + yield ResponsiveUIManager.openIfNeeded(newWindow, tab); + + // Close the window on a tab with an active responsive design UI and + // wait for the UI to gracefully shutdown. This has leaked the window + // in the past. + ok(ResponsiveUIManager.isActiveForTab(tab), + "ResponsiveUI should be active for tab when the window is closed"); + let offPromise = once(ResponsiveUIManager, "off"); + yield BrowserTestUtils.closeWindow(newWindow); + yield offPromise; +}); diff --git a/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js b/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js new file mode 100644 index 000000000..3ab54b601 --- /dev/null +++ b/devtools/client/responsivedesign/test/browser_responsiveuiaddcustompreset.js @@ -0,0 +1,121 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + let tab = yield addTab("data:text/html;charset=utf8,Test RDM custom presets"); + + let { rdm, manager } = yield openRDM(tab); + + let oldPrompt = Services.prompt; + Services.prompt = { + value: "", + returnBool: true, + prompt: function (parent, dialogTitle, text, value, checkMsg, checkState) { + value.value = this.value; + return this.returnBool; + } + }; + + registerCleanupFunction(() => { + Services.prompt = oldPrompt; + }); + + // Is it open? + let container = gBrowser.getBrowserContainer(); + is(container.getAttribute("responsivemode"), "true", + "Should be in responsive mode."); + + ok(rdm, "RDM instance should be attached to the tab."); + + // Tries to add a custom preset and cancel the prompt + let idx = rdm.menulist.selectedIndex; + let presetCount = rdm.presets.length; + + Services.prompt.value = ""; + Services.prompt.returnBool = false; + rdm.addbutton.doCommand(); + + is(idx, rdm.menulist.selectedIndex, + "selected item shouldn't change after add preset and cancel"); + is(presetCount, rdm.presets.length, + "number of presets shouldn't change after add preset and cancel"); + + // Adds the custom preset with "Testing preset" + Services.prompt.value = "Testing preset"; + Services.prompt.returnBool = true; + + let resized = once(manager, "content-resize"); + let customHeight = 123, customWidth = 456; + rdm.startResizing({}); + rdm.setViewportSize({ + width: customWidth, + height: customHeight, + }); + rdm.stopResizing({}); + + rdm.addbutton.doCommand(); + yield resized; + + yield closeRDM(rdm); + + ({rdm} = yield openRDM(tab)); + is(container.getAttribute("responsivemode"), "true", + "Should be in responsive mode."); + + let presetLabel = "456" + "\u00D7" + "123 (Testing preset)"; + let customPresetIndex = yield getPresetIndex(rdm, manager, presetLabel); + ok(customPresetIndex >= 0, "(idx = " + customPresetIndex + ") should be the" + + " previously added preset in the list of items"); + + yield setPresetIndex(rdm, manager, customPresetIndex); + + let browser = gBrowser.selectedBrowser; + yield ContentTask.spawn(browser, null, function* () { + let {innerWidth, innerHeight} = content; + Assert.equal(innerWidth, 456, "Selecting preset should change the width"); + Assert.equal(innerHeight, 123, "Selecting preset should change the height"); + }); + + info(`menulist count: ${rdm.menulist.itemCount}`); + + rdm.removebutton.doCommand(); + + yield setPresetIndex(rdm, manager, 2); + let deletedPresetA = rdm.menulist.selectedItem.getAttribute("label"); + rdm.removebutton.doCommand(); + + yield setPresetIndex(rdm, manager, 2); + let deletedPresetB = rdm.menulist.selectedItem.getAttribute("label"); + rdm.removebutton.doCommand(); + + yield closeRDM(rdm); + ({rdm} = yield openRDM(tab)); + + customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetA); + is(customPresetIndex, -1, + "Deleted preset " + deletedPresetA + " should not be in the list anymore"); + + customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetB); + is(customPresetIndex, -1, + "Deleted preset " + deletedPresetB + " should not be in the list anymore"); + + yield closeRDM(rdm); +}); + +var getPresetIndex = Task.async(function* (rdm, manager, presetLabel) { + var testOnePreset = Task.async(function* (c) { + if (c == 0) { + return -1; + } + yield setPresetIndex(rdm, manager, c); + + let item = rdm.menulist.firstChild.childNodes[c]; + if (item.getAttribute("label") === presetLabel) { + return c; + } + return testOnePreset(c - 1); + }); + return testOnePreset(rdm.menulist.firstChild.childNodes.length - 4); +}); diff --git a/devtools/client/responsivedesign/test/head.js b/devtools/client/responsivedesign/test/head.js new file mode 100644 index 000000000..3228021f6 --- /dev/null +++ b/devtools/client/responsivedesign/test/head.js @@ -0,0 +1,302 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); +// shared-head.js handles imports, constants, and utility functions +let sharedHeadURI = testDir + "../../../framework/test/shared-head.js"; +Services.scriptloader.loadSubScript(sharedHeadURI, this); + +// Import the GCLI test helper +let gcliHelpersURI = testDir + "../../../commandline/test/helpers.js"; +Services.scriptloader.loadSubScript(gcliHelpersURI, this); + +flags.testing = true; +Services.prefs.setBoolPref("devtools.responsive.html.enabled", false); + +registerCleanupFunction(() => { + flags.testing = false; + Services.prefs.clearUserPref("devtools.responsive.html.enabled"); + Services.prefs.clearUserPref("devtools.responsiveUI.currentPreset"); + Services.prefs.clearUserPref("devtools.responsiveUI.customHeight"); + Services.prefs.clearUserPref("devtools.responsiveUI.customWidth"); + Services.prefs.clearUserPref("devtools.responsiveUI.presets"); + Services.prefs.clearUserPref("devtools.responsiveUI.rotate"); +}); + +SimpleTest.requestCompleteLog(); + +const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {}); + +/** + * Open the Responsive Design Mode + * @param {Tab} The browser tab to open it into (defaults to the selected tab). + * @param {method} The method to use to open the RDM (values: menu, keyboard) + * @return {rdm, manager} Returns the RUI instance and the manager + */ +var openRDM = Task.async(function* (tab = gBrowser.selectedTab, + method = "menu") { + let manager = ResponsiveUIManager; + + let opened = once(manager, "on"); + let resized = once(manager, "content-resize"); + if (method == "menu") { + document.getElementById("menu_responsiveUI").doCommand(); + } else { + synthesizeKeyFromKeyTag(document.getElementById("key_responsiveUI")); + } + yield opened; + + let rdm = manager.getResponsiveUIForTab(tab); + rdm.transitionsEnabled = false; + registerCleanupFunction(() => { + rdm.transitionsEnabled = true; + }); + + // Wait for content to resize. This is triggered async by the preset menu + // auto-selecting its default entry once it's in the document. + yield resized; + + return {rdm, manager}; +}); + +/** + * Close a responsive mode instance + * @param {rdm} ResponsiveUI instance for the tab + */ +var closeRDM = Task.async(function* (rdm) { + let manager = ResponsiveUIManager; + if (!rdm) { + rdm = manager.getResponsiveUIForTab(gBrowser.selectedTab); + } + let closed = once(manager, "off"); + let resized = once(manager, "content-resize"); + rdm.close(); + yield resized; + yield closed; +}); + +/** + * Open the toolbox, with the inspector tool visible. + * @return a promise that resolves when the inspector is ready + */ +var openInspector = Task.async(function* () { + info("Opening the inspector"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + + let inspector, toolbox; + + // Checking if the toolbox and the inspector are already loaded + // The inspector-updated event should only be waited for if the inspector + // isn't loaded yet + toolbox = gDevTools.getToolbox(target); + if (toolbox) { + inspector = toolbox.getPanel("inspector"); + if (inspector) { + info("Toolbox and inspector already open"); + return { + toolbox: toolbox, + inspector: inspector + }; + } + } + + info("Opening the toolbox"); + toolbox = yield gDevTools.showToolbox(target, "inspector"); + yield waitForToolboxFrameFocus(toolbox); + inspector = toolbox.getPanel("inspector"); + + info("Waiting for the inspector to update"); + if (inspector._updateProgress) { + yield inspector.once("inspector-updated"); + } + + return { + toolbox: toolbox, + inspector: inspector + }; +}); + +var closeToolbox = Task.async(function* () { + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); +}); + +/** + * Wait for the toolbox frame to receive focus after it loads + * @param {Toolbox} toolbox + * @return a promise that resolves when focus has been received + */ +function waitForToolboxFrameFocus(toolbox) { + info("Making sure that the toolbox's frame is focused"); + let def = promise.defer(); + waitForFocus(def.resolve, toolbox.win); + return def.promise; +} + +/** + * Open the toolbox, with the inspector tool visible, and the sidebar that + * corresponds to the given id selected + * @return a promise that resolves when the inspector is ready and the sidebar + * view is visible and ready + */ +var openInspectorSideBar = Task.async(function* (id) { + let {toolbox, inspector} = yield openInspector(); + + info("Selecting the " + id + " sidebar"); + inspector.sidebar.select(id); + + return { + toolbox: toolbox, + inspector: inspector, + view: inspector[id].view || inspector[id].computedView + }; +}); + +/** + * Checks whether the inspector's sidebar corresponding to the given id already + * exists + * @param {InspectorPanel} + * @param {String} + * @return {Boolean} + */ +function hasSideBarTab(inspector, id) { + return !!inspector.sidebar.getWindowForTab(id); +} + +/** + * Open the toolbox, with the inspector tool visible, and the computed-view + * sidebar tab selected. + * @return a promise that resolves when the inspector is ready and the computed + * view is visible and ready + */ +function openComputedView() { + return openInspectorSideBar("computedview"); +} + +/** + * Open the toolbox, with the inspector tool visible, and the rule-view + * sidebar tab selected. + * @return a promise that resolves when the inspector is ready and the rule + * view is visible and ready + */ +function openRuleView() { + return openInspectorSideBar("ruleview"); +} + +/** + * 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 + * @return a promise that resolves to the tab object when the url is loaded + */ +var addTab = Task.async(function* (url) { + info("Adding a new tab with URL: '" + url + "'"); + + window.focus(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + + yield BrowserTestUtils.browserLoaded(browser); + info("URL '" + url + "' loading complete"); + + return tab; +}); + +/** + * Waits for the next load to complete in the current browser. + * + * @return promise + */ +function waitForDocLoadComplete(aBrowser = gBrowser) { + let deferred = promise.defer(); + let progressListener = { + onStateChange: function (webProgress, req, flags, status) { + let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK | + Ci.nsIWebProgressListener.STATE_STOP; + info(`Saw state ${flags.toString(16)} and status ${status.toString(16)}`); + + // When a load needs to be retargetted to a new process it is cancelled + // with NS_BINDING_ABORTED so ignore that case + if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) { + aBrowser.removeProgressListener(progressListener); + info("Browser loaded"); + deferred.resolve(); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) + }; + aBrowser.addProgressListener(progressListener); + info("Waiting for browser load"); + return deferred.promise; +} + +/** + * Get the NodeFront for a node that matches a given css selector, via the + * protocol. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves to the NodeFront instance + */ +function getNodeFront(selector, {walker}) { + if (selector._form) { + return selector; + } + return walker.querySelector(walker.rootNode, selector); +} + +/** + * Set the inspector's current selection to the first match of the given css + * selector + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @param {String} reason Defaults to "test" which instructs the inspector not + * to highlight the node upon selection + * @return {Promise} Resolves when the inspector is updated with the new node + */ +var selectNode = Task.async(function* (selector, inspector, reason = "test") { + info("Selecting the node for '" + selector + "'"); + let nodeFront = yield getNodeFront(selector, inspector); + let updated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(nodeFront, reason); + yield updated; +}); + +function waitForResizeTo(manager, width, height) { + return new Promise(resolve => { + let onResize = (_, data) => { + if (data.width != width || data.height != height) { + return; + } + manager.off("content-resize", onResize); + info(`Got content-resize to ${width} x ${height}`); + resolve(); + }; + info(`Waiting for content-resize to ${width} x ${height}`); + manager.on("content-resize", onResize); + }); +} + +var setPresetIndex = Task.async(function* (rdm, manager, index) { + info(`Current preset: ${rdm.menulist.selectedIndex}, change to: ${index}`); + if (rdm.menulist.selectedIndex != index) { + let resized = once(manager, "content-resize"); + rdm.menulist.selectedIndex = index; + yield resized; + } +}); + +var setSize = Task.async(function* (rdm, manager, width, height) { + let size = rdm.getSize(); + info(`Current size: ${size.width} x ${size.height}, ` + + `set to: ${width} x ${height}`); + if (size.width != width || size.height != height) { + let resized = waitForResizeTo(manager, width, height); + rdm.setViewportSize({ width, height }); + yield resized; + } +}); diff --git a/devtools/client/responsivedesign/test/touch.html b/devtools/client/responsivedesign/test/touch.html new file mode 100644 index 000000000..f93d36f6c --- /dev/null +++ b/devtools/client/responsivedesign/test/touch.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> + +<meta charset="utf-8" /> +<meta name="viewport" /> +<title>test</title> + + +<style> + div { + border:1px solid red; + width: 100px; height: 100px; + } +</style> + +<div data-is-delay="false"></div> + +<script> + var div = document.querySelector("div"); + var initX, initY; + var previousEvent = "", touchendTime = 0; + var updatePreviousEvent = function(e){ + previousEvent = e.type; + }; + + div.style.transform = "none"; + div.style.backgroundColor = ""; + + div.addEventListener("touchstart", function(evt) { + var touch = evt.changedTouches[0]; + initX = touch.pageX; + initY = touch.pageY; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("touchmove", function(evt) { + var touch = evt.changedTouches[0]; + var deltaX = touch.pageX - initX; + var deltaY = touch.pageY - initY; + div.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("touchend", function(evt) { + if (!evt.touches.length) { + div.style.transform = "none"; + } + touchendTime = performance.now(); + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseenter", function(evt) { + div.style.backgroundColor = "red"; + updatePreviousEvent(evt); + }, true); + div.addEventListener("mouseover", function(evt) { + div.style.backgroundColor = "red"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseout", function(evt) { + div.style.backgroundColor = "blue"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseleave", function(evt) { + div.style.backgroundColor = "blue"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mousedown", function(evt){ + if (previousEvent === "touchend" && touchendTime !== 0) { + let now = performance.now(); + div.dataset.isDelay = ((now - touchendTime) >= 300) ? true : false; + } else { + div.dataset.isDelay = false; + } + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mousemove", updatePreviousEvent, true); + + div.addEventListener("mouseup", updatePreviousEvent, true); + + div.addEventListener("click", updatePreviousEvent, true); +</script> |