diff --git a/browser/devtools/commandline/test/Makefile.in b/browser/devtools/commandline/test/Makefile.in index ede2b0e3b799..29ef18e62e4f 100644 --- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -24,7 +24,6 @@ MOCHITEST_BROWSER_FILES = \ browser_cmd_pagemod_export.js \ browser_cmd_pref.js \ browser_cmd_restart.js \ - browser_cmd_screenshot.js \ browser_cmd_settings.js \ browser_gcli_canon.js \ browser_gcli_cli.js \ @@ -52,6 +51,17 @@ MOCHITEST_BROWSER_FILES = \ mockCommands.js \ $(NULL) +ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING +MOCHITEST_BROWSER_FILES += \ + browser_cmd_screenshot_perwindowpb.js \ + helpers_perwindowpb.js \ + $(NULL) +else +MOCHITEST_BROWSER_FILES += \ + browser_cmd_screenshot.js \ + $(NULL) +endif + MOCHITEST_BROWSER_FILES += \ browser_dbg_cmd_break.html \ browser_dbg_cmd.html \ diff --git a/browser/devtools/commandline/test/browser_cmd_screenshot_perwindowpb.js b/browser/devtools/commandline/test/browser_cmd_screenshot_perwindowpb.js new file mode 100644 index 000000000000..b6e7ed2ecd17 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_screenshot_perwindowpb.js @@ -0,0 +1,169 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that screenshot command works properly +const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" + + "test/browser_cmd_screenshot.html"; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +let FileUtils = Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils; + +function test() { + waitForExplicitFinish(); + + function testOnWindow(aPrivate, aCallback) { + let win = OpenBrowserWindow({private: aPrivate}); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + executeSoon(function() aCallback(win)); + }, false); + }; + + testOnWindow(false, function(win) { + DeveloperToolbarTestPW.test(win, TEST_URI, [ testInput, testCapture ], null, function() { + win.close(); + testOnWindow(true, function(win) { + executeSoon(function() { + DeveloperToolbarTestPW.test(win, TEST_URI, [ testInput, testCapture ], null, function() { + win.close(); + finish(); + }); + }) + }); + }); + }); + +} + +function testInput(aWindow) { + helpers_perwindowpb.setInput('screenshot'); + helpers_perwindowpb.check({ + input: 'screenshot', + markup: 'VVVVVVVVVV', + status: 'VALID', + args: { + } + }); + + helpers_perwindowpb.setInput('screenshot abc.png'); + helpers_perwindowpb.check({ + input: 'screenshot abc.png', + markup: 'VVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + filename: { value: "abc.png"}, + } + }); + + helpers_perwindowpb.setInput('screenshot --fullpage'); + helpers_perwindowpb.check({ + input: 'screenshot --fullpage', + markup: 'VVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + fullpage: { value: true}, + } + }); + + helpers_perwindowpb.setInput('screenshot abc --delay 5'); + helpers_perwindowpb.check({ + input: 'screenshot abc --delay 5', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + filename: { value: "abc"}, + delay: { value: "5"}, + } + }); + + helpers_perwindowpb.setInput('screenshot --selector img#testImage'); + helpers_perwindowpb.check({ + input: 'screenshot --selector img#testImage', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { value: aWindow.content.document.getElementById("testImage")}, + } + }); +} + +function testCapture(aWindow) { + function checkTemporaryFile() { + // Create a temporary file. + let gFile = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]); + if (gFile.exists()) { + gFile.remove(false); + return true; + } + else { + return false; + } + } + + function checkClipboard() { + try { + let clipid = Ci.nsIClipboard; + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + let trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + trans.init(null); + trans.addDataFlavor("image/png"); + clip.getData(trans, clipid.kGlobalClipboard); + let str = new Object(); + let strLength = new Object(); + trans.getTransferData("image/png", str, strLength); + if (str.value && strLength.value > 0) { + return true; + } + } + catch (ex) {} + return false; + } + + let path = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]).path; + + DeveloperToolbarTestPW.exec(aWindow, { + typed: "screenshot " + path, + args: { + delay: 0, + filename: "" + path, + fullpage: false, + clipboard: false, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Saved to "), + }); + + ok(checkTemporaryFile(), "Screenshot got created"); + + DeveloperToolbarTestPW.exec(aWindow, { + typed: "screenshot --fullpage --clipboard", + args: { + delay: 0, + filename: " ", + fullpage: true, + clipboard: true, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Copied to clipboard.$"), + }); + + ok(checkClipboard(), "Screenshot got created and copied"); + + DeveloperToolbarTestPW.exec(aWindow, { + typed: "screenshot --clipboard", + args: { + delay: 0, + filename: " ", + fullpage: false, + clipboard: true, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Copied to clipboard.$"), + }); + + ok(checkClipboard(), "Screenshot present in clipboard"); +} diff --git a/browser/devtools/commandline/test/head.js b/browser/devtools/commandline/test/head.js index b62434c18397..86318f7a79b6 100644 --- a/browser/devtools/commandline/test/head.js +++ b/browser/devtools/commandline/test/head.js @@ -16,6 +16,7 @@ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/helpers.js", this); Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this); +Services.scriptloader.loadSubScript(testDir + "/helpers_perwindowpb.js", this); /** * Open a new tab at a URL and call a callback on load diff --git a/browser/devtools/commandline/test/helpers_perwindowpb.js b/browser/devtools/commandline/test/helpers_perwindowpb.js new file mode 100644 index 000000000000..227fe692b257 --- /dev/null +++ b/browser/devtools/commandline/test/helpers_perwindowpb.js @@ -0,0 +1,905 @@ +/* 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/. */ + + +/* + * + * DO NOT ALTER THIS FILE WITHOUT KEEPING IT IN SYNC WITH THE OTHER COPIES + * OF THIS FILE. + * + * UNAUTHORIZED ALTERATION WILL RESULT IN THE ALTEREE BEING SENT TO SIT ON + * THE NAUGHTY STEP. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * FOR A LONG TIME. + * + */ + + +/* + * Use as a JSM + * ------------ + * helpers_perwindowpb._createDebugCheck() and maybe other functions in this file can be + * useful at runtime, so it is possible to use helpers_perwindowpb.js as a JSM. + * Copy commandline/test/helpers_perwindowpb.js to shared/helpers_perwindowpb.jsm, and then add to + * DeveloperToolbar.jsm the following: + * + * XPCOMUtils.defineLazyModuleGetter(this, "helpers_perwindowpb", + * "resource:///modules/devtools/helpers_perwindowpb.jsm"); + * + * At the bottom of DeveloperToolbar.prototype._onload add this: + * + * var options = { display: this.display }; + * this._input.onkeypress = function(ev) { + * helpers_perwindowpb.setup(options); + * dump(helpers_perwindowpb._createDebugCheck() + '\n\n'); + * }; + * + * Now GCLI will emit output on every keypress that both explains the state + * of GCLI and can be run as a test case. + */ + +this.EXPORTED_SYMBOLS = [ 'helpers_perwindowpb' ]; + +var test = { }; + +/** + * Various functions for testing DeveloperToolbar. + * Parts of this code exist in: + * - browser/devtools/commandline/test/head.js + * - browser/devtools/shared/test/head.js + */ +let DeveloperToolbarTestPW = { }; + +/** + * Paranoid DeveloperToolbar.show(); + */ +DeveloperToolbarTestPW.show = function DTTPW_show(aWindow, aCallback) { + if (aWindow.DeveloperToolbar.visible) { + ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar"); + } + else { + aWindow.DeveloperToolbar.show(true, aCallback); + } +}; + +/** + * Paranoid DeveloperToolbar.hide(); + */ +DeveloperToolbarTestPW.hide = function DTTPW_hide(aWindow) { + if (!aWindow.DeveloperToolbar.visible) { + ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar"); + } + else { + aWindow.DeveloperToolbar.display.inputter.setInput(""); + aWindow.DeveloperToolbar.hide(); + } +}; + +/** + * check() is the new status. Similar API except that it doesn't attempt to + * alter the display/requisition at all, and it makes extra checks. + * Test inputs + * typed: The text to type at the input + * Available checks: + * input: The text displayed in the input field + * cursor: The position of the start of the cursor + * status: One of "VALID", "ERROR", "INCOMPLETE" + * emptyParameters: Array of parameters still to type. e.g. [ "" ] + * directTabText: Simple completion text + * arrowTabText: When the completion is not an extension (without arrow) + * markup: What state should the error markup be in. e.g. "VVVIIIEEE" + * args: Maps of checks to make against the arguments: + * value: i.e. assignment.value (which ignores defaultValue) + * type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned + * Care should be taken with this since it's something of an + * implementation detail + * arg: The toString value of the argument + * status: i.e. assignment.getStatus + * message: i.e. assignment.getMessage + * name: For commands - checks assignment.value.name + */ +DeveloperToolbarTestPW.checkInputStatus = function DTTPW_checkInputStatus(aWindow, checks) { + if (!checks.emptyParameters) { + checks.emptyParameters = []; + } + if (!checks.directTabText) { + checks.directTabText = ''; + } + if (!checks.arrowTabText) { + checks.arrowTabText = ''; + } + + var display = aWindow.DeveloperToolbar.display; + + if (checks.typed) { + info('Starting tests for ' + checks.typed); + display.inputter.setInput(checks.typed); + } + else { + ok(false, "Missing typed for " + JSON.stringify(checks)); + return; + } + + if (checks.cursor) { + display.inputter.setCursor(checks.cursor) + } + + var cursor = checks.cursor ? checks.cursor.start : checks.typed.length; + + var requisition = display.requisition; + var completer = display.completer; + var actual = completer._getCompleterTemplateData(); + + /* + if (checks.input) { + is(display.inputter.element.value, + checks.input, + 'input'); + } + + if (checks.cursor) { + is(display.inputter.element.selectionStart, + checks.cursor, + 'cursor'); + } + */ + + if (checks.status) { + is(requisition.getStatus().toString(), + checks.status, + 'status'); + } + + if (checks.markup) { + var statusMarkup = requisition.getInputStatusMarkup(cursor); + var actualMarkup = statusMarkup.map(function(s) { + return Array(s.string.length + 1).join(s.status.toString()[0]); + }).join(''); + + is(checks.markup, + actualMarkup, + 'markup'); + } + + if (checks.emptyParameters) { + var actualParams = actual.emptyParameters; + is(actualParams.length, + checks.emptyParameters.length, + 'emptyParameters.length'); + + if (actualParams.length === checks.emptyParameters.length) { + for (var i = 0; i < actualParams.length; i++) { + is(actualParams[i].replace(/\u00a0/g, ' '), + checks.emptyParameters[i], + 'emptyParameters[' + i + ']'); + } + } + else { + info('Expected: [ \"' + actualParams.join('", "') + '" ]'); + } + } + + if (checks.directTabText) { + is(actual.directTabText, + checks.directTabText, + 'directTabText'); + } + + if (checks.arrowTabText) { + is(actual.arrowTabText, + ' \u00a0\u21E5 ' + checks.arrowTabText, + 'arrowTabText'); + } + + if (checks.args) { + Object.keys(checks.args).forEach(function(paramName) { + var check = checks.args[paramName]; + + var assignment; + if (paramName === 'command') { + assignment = requisition.commandAssignment; + } + else { + assignment = requisition.getAssignment(paramName); + } + + if (assignment == null) { + ok(false, 'Unknown parameter: ' + paramName); + return; + } + + if (check.value) { + is(assignment.value, + check.value, + 'checkStatus value for ' + paramName); + } + + if (check.name) { + is(assignment.value.name, + check.name, + 'checkStatus name for ' + paramName); + } + + if (check.type) { + is(assignment.arg.type, + check.type, + 'checkStatus type for ' + paramName); + } + + if (check.arg) { + is(assignment.arg.toString(), + check.arg, + 'checkStatus arg for ' + paramName); + } + + if (check.status) { + is(assignment.getStatus().toString(), + check.status, + 'checkStatus status for ' + paramName); + } + + if (check.message) { + is(assignment.getMessage(), + check.message, + 'checkStatus message for ' + paramName); + } + }); + } +}; + +/** + * Execute a command: + * + * DeveloperToolbarTestPW.exec({ + * // Test inputs + * typed: "echo hi", // Optional, uses existing if undefined + * + * // Thing to check + * args: { message: "hi" }, // Check that the args were understood properly + * outputMatch: /^hi$/, // RegExp to test against textContent of output + * // (can also be array of RegExps) + * blankOutput: true, // Special checks when there is no output + * }); + */ +DeveloperToolbarTestPW.exec = function DTTPW_exec(aWindow, tests) { + tests = tests || {}; + + if (tests.typed) { + aWindow.DeveloperToolbar.display.inputter.setInput(tests.typed); + } + + let typed = aWindow.DeveloperToolbar.display.inputter.getInputState().typed; + let output = aWindow.DeveloperToolbar.display.requisition.exec(); + + is(typed, output.typed, 'output.command for: ' + typed); + + if (tests.completed !== false) { + ok(output.completed, 'output.completed false for: ' + typed); + } + else { + // It is actually an error if we say something is async and it turns + // out not to be? For now we're saying 'no' + // ok(!output.completed, 'output.completed true for: ' + typed); + } + + if (tests.args != null) { + is(Object.keys(tests.args).length, Object.keys(output.args).length, + 'arg count for ' + typed); + + Object.keys(output.args).forEach(function(arg) { + let expectedArg = tests.args[arg]; + let actualArg = output.args[arg]; + + if (typeof expectedArg === 'function') { + ok(expectedArg(actualArg), 'failed test func. ' + typed + '/' + arg); + } + else { + if (Array.isArray(expectedArg)) { + if (!Array.isArray(actualArg)) { + ok(false, 'actual is not an array. ' + typed + '/' + arg); + return; + } + + is(expectedArg.length, actualArg.length, + 'array length: ' + typed + '/' + arg); + for (let i = 0; i < expectedArg.length; i++) { + is(expectedArg[i], actualArg[i], + 'member: "' + typed + '/' + arg + '/' + i); + } + } + else { + is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg); + } + } + }); + } + + let displayed = aWindow.DeveloperToolbar.outputPanel._div.textContent; + + if (tests.outputMatch) { + var doTest = function(match, against) { + if (!match.test(against)) { + ok(false, "html output for " + typed + " against " + match.source + + " (textContent sent to info)"); + info("Actual textContent"); + info(against); + } + } + if (Array.isArray(tests.outputMatch)) { + tests.outputMatch.forEach(function(match) { + doTest(match, displayed); + }); + } + else { + doTest(tests.outputMatch, displayed); + } + } + + if (tests.blankOutput != null) { + if (!/^$/.test(displayed)) { + ok(false, "html output for " + typed + " (textContent sent to info)"); + info("Actual textContent"); + info(displayed); + } + } +}; + +/** + * Quick wrapper around the things you need to do to run DeveloperToolbar + * command tests: + * - Set the pref 'devtools.toolbar.enabled' to true + * - Add a tab pointing at |uri| + * - Open the DeveloperToolbar + * - Register a cleanup function to undo the above + * - Run the tests + * + * @param uri The uri of a page to load. Can be 'about:blank' or 'data:...' + * @param target Either a function or array of functions containing the tests + * to run. If an array of test function is passed then we will clear up after + * the tests have completed. If a single test function is passed then this + * function should arrange for 'finish()' to be called on completion. + */ +DeveloperToolbarTestPW.test = function DTTPW_test(aWindow, uri, target, isGcli, aCallback) { + let menuItem = aWindow.document.getElementById("menu_devToolbar"); + let command = aWindow.document.getElementById("Tools:DevToolbar"); + let appMenuItem = aWindow.document.getElementById("appmenu_devToolbar"); + + function cleanup() { + DeveloperToolbarTestPW.hide(aWindow); + + // a.k.a Services.prefs.clearUserPref("devtools.toolbar.enabled"); + if (menuItem) { + menuItem.hidden = true; + } + if (command) { + command.setAttribute("disabled", "true"); + } + if (appMenuItem) { + appMenuItem.hidden = true; + } + + // leakHunt({ DeveloperToolbar: DeveloperToolbar }); + }; + + // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true); + if (menuItem) { + menuItem.hidden = false; + } + if (command) { + command.removeAttribute("disabled"); + } + if (appMenuItem) { + appMenuItem.hidden = false; + } + + aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(); + aWindow.content.location = uri; + + let tab = aWindow.gBrowser.selectedTab; + let browser = aWindow.gBrowser.getBrowserForTab(tab); + + var onTabLoad = function() { + browser.removeEventListener("load", onTabLoad, true); + + DeveloperToolbarTestPW.show(aWindow, function() { + var options = { + window: aWindow.content, + display: aWindow.DeveloperToolbar.display, + isFirefox: true + }; + + if (helpers_perwindowpb && !isGcli) { + helpers_perwindowpb.setup(options); + } + + if (Array.isArray(target)) { + try { + target.forEach(function(func) { + var args = isGcli ? [ options ] : [ aWindow ]; + func.apply(null, args); + }); + cleanup(); + aCallback(); + } + catch (ex) { + ok(false, "" + ex); + DeveloperToolbarTestPW._finish(aWindow); + throw ex; + } + } + else { + try { + target(aWindow); + cleanup(); + aCallback(); + } + catch (ex) { + ok(false, "" + ex); + DeveloperToolbarTestPW._finish(aWindow); + throw ex; + } + } + }); + } + + browser.addEventListener("load", onTabLoad, true); +}; + +DeveloperToolbarTestPW._outstanding = []; + +DeveloperToolbarTestPW._checkFinish = function(aWindow) { + info('_checkFinish. ' + DeveloperToolbarTestPW._outstanding.length + ' outstanding'); + if (DeveloperToolbarTestPW._outstanding.length == 0) { + DeveloperToolbarTestPW._finish(aWindow); + } +} + +DeveloperToolbarTestPW._finish = function(aWindow) { + info('Finish'); + DeveloperToolbarTestPW.closeAllTabs(aWindow); + finish(); +} + +DeveloperToolbarTestPW.checkCalled = function(aWindow, aFunc, aScope) { + var todo = function() { + var reply = aFunc.apply(aScope, arguments); + DeveloperToolbarTestPW._outstanding = DeveloperToolbarTestPW._outstanding.filter(function(aJob) { + return aJob != todo; + }); + DeveloperToolbarTestPW._checkFinish(aWindow); + return reply; + } + DeveloperToolbarTestPW._outstanding.push(todo); + return todo; +}; + +DeveloperToolbarTestPW.checkNotCalled = function(aMsg, aFunc, aScope) { + return function() { + ok(false, aMsg); + return aFunc.apply(aScope, arguments); + } +}; + +/** + * + */ +DeveloperToolbarTestPW.closeAllTabs = function(aWindow) { + while (aWindow.gBrowser.tabs.length > 1) { + aWindow.gBrowser.removeCurrentTab(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +this.helpers_perwindowpb = {}; + +var assert = { ok: ok, is: is, log: info }; + +helpers_perwindowpb._display = undefined; +helpers_perwindowpb._options = undefined; + +helpers_perwindowpb.setup = function(options) { + helpers_perwindowpb._options = options; + helpers_perwindowpb._display = options.display; +}; + +helpers_perwindowpb.shutdown = function(options) { + helpers_perwindowpb._options = undefined; + helpers_perwindowpb._display = undefined; +}; + +/** + * Various functions to return the actual state of the command line + */ +helpers_perwindowpb._actual = { + input: function() { + return helpers_perwindowpb._display.inputter.element.value; + }, + + hints: function() { + var templateData = helpers_perwindowpb._display.completer._getCompleterTemplateData(); + var actualHints = templateData.directTabText + + templateData.emptyParameters.join('') + + templateData.arrowTabText; + return actualHints.replace(/\u00a0/g, ' ') + .replace(/\u21E5/, '->') + .replace(/ $/, ''); + }, + + markup: function() { + var cursor = helpers_perwindowpb._display.inputter.element.selectionStart; + var statusMarkup = helpers_perwindowpb._display.requisition.getInputStatusMarkup(cursor); + return statusMarkup.map(function(s) { + return Array(s.string.length + 1).join(s.status.toString()[0]); + }).join(''); + }, + + cursor: function() { + return helpers_perwindowpb._display.inputter.element.selectionStart; + }, + + current: function() { + return helpers_perwindowpb._display.requisition.getAssignmentAt(helpers_perwindowpb._actual.cursor()).param.name; + }, + + status: function() { + return helpers_perwindowpb._display.requisition.getStatus().toString(); + }, + + outputState: function() { + var outputData = helpers_perwindowpb._display.focusManager._shouldShowOutput(); + return outputData.visible + ':' + outputData.reason; + }, + + tooltipState: function() { + var tooltipData = helpers_perwindowpb._display.focusManager._shouldShowTooltip(); + return tooltipData.visible + ':' + tooltipData.reason; + } +}; + +helpers_perwindowpb._directToString = [ 'boolean', 'undefined', 'number' ]; + +helpers_perwindowpb._createDebugCheck = function() { + var requisition = helpers_perwindowpb._display.requisition; + var command = requisition.commandAssignment.value; + var input = helpers_perwindowpb._actual.input(); + var padding = Array(input.length + 1).join(' '); + + var output = ''; + output += 'helpers_perwindowpb.setInput(\'' + input + '\');\n'; + output += 'helpers_perwindowpb.check({\n'; + output += ' input: \'' + input + '\',\n'; + output += ' hints: ' + padding + '\'' + helpers_perwindowpb._actual.hints() + '\',\n'; + output += ' markup: \'' + helpers_perwindowpb._actual.markup() + '\',\n'; + output += ' cursor: ' + helpers_perwindowpb._actual.cursor() + ',\n'; + output += ' current: \'' + helpers_perwindowpb._actual.current() + '\',\n'; + output += ' status: \'' + helpers_perwindowpb._actual.status() + '\',\n'; + output += ' outputState: \'' + helpers_perwindowpb._actual.outputState() + '\',\n'; + + if (command) { + output += ' tooltipState: \'' + helpers_perwindowpb._actual.tooltipState() + '\',\n'; + output += ' args: {\n'; + output += ' command: { name: \'' + command.name + '\' },\n'; + + requisition.getAssignments().forEach(function(assignment) { + output += ' ' + assignment.param.name + ': { '; + + if (typeof assignment.value === 'string') { + output += 'value: \'' + assignment.value + '\', '; + } + else if (helpers_perwindowpb._directToString.indexOf(typeof assignment.value) !== -1) { + output += 'value: ' + assignment.value + ', '; + } + else if (assignment.value === null) { + output += 'value: ' + assignment.value + ', '; + } + else { + output += '/*value:' + assignment.value + ',*/ '; + } + + output += 'arg: \'' + assignment.arg + '\', '; + output += 'status: \'' + assignment.getStatus().toString() + '\', '; + output += 'message: \'' + assignment.getMessage() + '\''; + output += ' },\n'; + }); + + output += ' }\n'; + } + else { + output += ' tooltipState: \'' + helpers_perwindowpb._actual.tooltipState() + '\'\n'; + } + output += '});'; + + return output; +}; + +/** + * We're splitting status into setup() which alters the state of the system + * and check() which ensures that things are in the right place afterwards. + */ +helpers_perwindowpb.setInput = function(typed, cursor) { + helpers_perwindowpb._display.inputter.setInput(typed); + + if (cursor) { + helpers_perwindowpb._display.inputter.setCursor({ start: cursor, end: cursor }); + } + else { + // This is a hack because jsdom appears to not handle cursor updates + // in the same way as most browsers. + if (helpers_perwindowpb._options.isJsdom) { + helpers_perwindowpb._display.inputter.setCursor({ + start: typed.length, + end: typed.length + }); + } + } + + helpers_perwindowpb._display.focusManager.onInputChange(); + + // Firefox testing is noisy and distant, so logging helps + if (helpers_perwindowpb._options.isFirefox) { + assert.log('setInput("' + typed + '"' + (cursor == null ? '' : ', ' + cursor) + ')'); + } +}; + +/** + * Simulate focusing the input field + */ +helpers_perwindowpb.focusInput = function() { + helpers_perwindowpb._display.inputter.focus(); +}; + +/** + * Simulate pressing TAB in the input field + */ +helpers_perwindowpb.pressTab = function() { + helpers_perwindowpb.pressKey(9 /*KeyEvent.DOM_VK_TAB*/); +}; + +/** + * Simulate pressing RETURN in the input field + */ +helpers_perwindowpb.pressReturn = function() { + helpers_perwindowpb.pressKey(13 /*KeyEvent.DOM_VK_RETURN*/); +}; + +/** + * Simulate pressing a key by keyCode in the input field + */ +helpers_perwindowpb.pressKey = function(keyCode) { + var fakeEvent = { + keyCode: keyCode, + preventDefault: function() { }, + timeStamp: new Date().getTime() + }; + helpers_perwindowpb._display.inputter.onKeyDown(fakeEvent); + helpers_perwindowpb._display.inputter.onKeyUp(fakeEvent); +}; + +/** + * check() is the new status. Similar API except that it doesn't attempt to + * alter the display/requisition at all, and it makes extra checks. + * Available checks: + * input: The text displayed in the input field + * cursor: The position of the start of the cursor + * status: One of "VALID", "ERROR", "INCOMPLETE" + * hints: The hint text, i.e. a concatenation of the directTabText, the + * emptyParameters and the arrowTabText. The text as inserted into the UI + * will include NBSP and Unicode RARR characters, these should be + * represented using normal space and '->' for the arrow + * markup: What state should the error markup be in. e.g. "VVVIIIEEE" + * args: Maps of checks to make against the arguments: + * value: i.e. assignment.value (which ignores defaultValue) + * type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned + * Care should be taken with this since it's something of an + * implementation detail + * arg: The toString value of the argument + * status: i.e. assignment.getStatus + * message: i.e. assignment.getMessage + * name: For commands - checks assignment.value.name + */ +helpers_perwindowpb.check = function(checks) { + if ('input' in checks) { + assert.is(helpers_perwindowpb._actual.input(), checks.input, 'input'); + } + + if ('cursor' in checks) { + assert.is(helpers_perwindowpb._actual.cursor(), checks.cursor, 'cursor'); + } + + if ('current' in checks) { + assert.is(helpers_perwindowpb._actual.current(), checks.current, 'current'); + } + + if ('status' in checks) { + assert.is(helpers_perwindowpb._actual.status(), checks.status, 'status'); + } + + if ('markup' in checks) { + assert.is(helpers_perwindowpb._actual.markup(), checks.markup, 'markup'); + } + + if ('hints' in checks) { + assert.is(helpers_perwindowpb._actual.hints(), checks.hints, 'hints'); + } + + if ('tooltipState' in checks) { + assert.is(helpers_perwindowpb._actual.tooltipState(), checks.tooltipState, 'tooltipState'); + } + + if ('outputState' in checks) { + assert.is(helpers_perwindowpb._actual.outputState(), checks.outputState, 'outputState'); + } + + if (checks.args != null) { + var requisition = helpers_perwindowpb._display.requisition; + Object.keys(checks.args).forEach(function(paramName) { + var check = checks.args[paramName]; + + var assignment; + if (paramName === 'command') { + assignment = requisition.commandAssignment; + } + else { + assignment = requisition.getAssignment(paramName); + } + + if (assignment == null) { + assert.ok(false, 'Unknown arg: ' + paramName); + return; + } + + if ('value' in check) { + assert.is(assignment.value, + check.value, + 'arg.' + paramName + '.value'); + } + + if ('name' in check) { + assert.is(assignment.value.name, + check.name, + 'arg.' + paramName + '.name'); + } + + if ('type' in check) { + assert.is(assignment.arg.type, + check.type, + 'arg.' + paramName + '.type'); + } + + if ('arg' in check) { + assert.is(assignment.arg.toString(), + check.arg, + 'arg.' + paramName + '.arg'); + } + + if ('status' in check) { + assert.is(assignment.getStatus().toString(), + check.status, + 'arg.' + paramName + '.status'); + } + + if ('message' in check) { + assert.is(assignment.getMessage(), + check.message, + 'arg.' + paramName + '.message'); + } + }); + } +}; + +/** + * Execute a command: + * + * helpers_perwindowpb.exec({ + * // Test inputs + * typed: "echo hi", // Optional, uses existing if undefined + * + * // Thing to check + * args: { message: "hi" }, // Check that the args were understood properly + * outputMatch: /^hi$/, // Regex to test against textContent of output + * blankOutput: true, // Special checks when there is no output + * }); + */ +helpers_perwindowpb.exec = function(tests) { + var requisition = helpers_perwindowpb._display.requisition; + var inputter = helpers_perwindowpb._display.inputter; + + tests = tests || {}; + + if (tests.typed) { + inputter.setInput(tests.typed); + } + + var typed = inputter.getInputState().typed; + var output = requisition.exec({ hidden: true }); + + assert.is(typed, output.typed, 'output.command for: ' + typed); + + if (tests.completed !== false) { + assert.ok(output.completed, 'output.completed false for: ' + typed); + } + else { + // It is actually an error if we say something is async and it turns + // out not to be? For now we're saying 'no' + // assert.ok(!output.completed, 'output.completed true for: ' + typed); + } + + if (tests.args != null) { + assert.is(Object.keys(tests.args).length, Object.keys(output.args).length, + 'arg count for ' + typed); + + Object.keys(output.args).forEach(function(arg) { + var expectedArg = tests.args[arg]; + var actualArg = output.args[arg]; + + if (Array.isArray(expectedArg)) { + if (!Array.isArray(actualArg)) { + assert.ok(false, 'actual is not an array. ' + typed + '/' + arg); + return; + } + + assert.is(expectedArg.length, actualArg.length, + 'array length: ' + typed + '/' + arg); + for (var i = 0; i < expectedArg.length; i++) { + assert.is(expectedArg[i], actualArg[i], + 'member: "' + typed + '/' + arg + '/' + i); + } + } + else { + assert.is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg); + } + }); + } + + if (!helpers_perwindowpb._options.window.document.createElement) { + assert.log('skipping output tests (missing doc.createElement) for ' + typed); + return; + } + + var div = helpers_perwindowpb._options.window.document.createElement('div'); + output.toDom(div); + var displayed = div.textContent.trim(); + + if (tests.outputMatch) { + var doTest = function(match, against) { + if (!match.test(against)) { + assert.ok(false, "html output for " + typed + " against " + match.source); + console.log("Actual textContent"); + console.log(against); + } + } + if (Array.isArray(tests.outputMatch)) { + tests.outputMatch.forEach(function(match) { + doTest(match, displayed); + }); + } + else { + doTest(tests.outputMatch, displayed); + } + } + + if (tests.blankOutput != null) { + if (!/^$/.test(displayed)) { + assert.ok(false, "html for " + typed + " (textContent sent to info)"); + console.log("Actual textContent"); + console.log(displayed); + } + } +};