diff --git a/common/Makefile b/common/Makefile index 846deb09..6d537643 100644 --- a/common/Makefile +++ b/common/Makefile @@ -36,7 +36,7 @@ XPI_BINS = $(JAR_BINS) XPI_NAME = $(NAME)-$(VERSION) XPI = ../downloads/$(XPI_NAME).xpi -XPI_PATH = $(TOP)$(XPI:%.xpi=%) +XPI_PATH = $(TOP)/$(XPI:%.xpi=%) RDF = ../downloads/update.rdf RDF_IN = $(RDF).in @@ -162,7 +162,7 @@ distclean: rm -rf $(BUILD_DIR) # TODO: generalize log path -test: $(XPI) +test: xpi @echo "Running functional tests..." $(MOZMILL) --show-all -l /tmp/dactyl-test.log -b $(HOSTAPP_PATH) --addons $(XPI) -t $(TEST_DIR) diff --git a/common/content/commandline.js b/common/content/commandline.js index 32999fd6..02d4073c 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -181,7 +181,9 @@ var CommandWidgets = Class("CommandWidgets", { [this.commandbar, this.statusbar].forEach(function (nodeSet) { let elem = nodeSet[obj.name]; - if (val != null) { + if (val == null) + elem.value = ""; + else { highlight.highlightNode(elem, (val[0] != null ? val[0] : obj.defaultGroup) .split(/\s/).filter(util.identity) diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 50fdf8f1..4263e64b 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -159,7 +159,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { if (type in this._observers) this._observers[type] = this._observers[type].filter(function (callback) { if (callback.get()) { - callback.get().apply(null, args); + try { + try { + callback.get().apply(null, args); + } + catch (e if e.message == "can't wrap XML objects") { + // Horrible kludge. + callback.get().apply(null, [String(args[0])].concat(args.slice(1))) + } + } + catch (e) { + dactyl.reportError(e); + } return true; } }); @@ -211,6 +222,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * 'visualbell' option. */ beep: function () { + this.triggerObserver("beep"); if (options["visualbell"]) { let elems = { bell: document.getElementById("dactyl-bell"), @@ -1091,6 +1103,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, + onExecute: function onExecute(event) { + let cmd = event.originalTarget.getAttribute("dactyl-execute"); + commands.execute(cmd, null, false, null, + { file: "[Command Line]", line: 1 }); + }, + /** * Opens one or more URLs. Returns true when load was initiated, or * false on error. @@ -1337,9 +1355,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { */ reportError: function reportError(error, echo) { if (error instanceof FailedAssertion || error.message === "Interrupted") { + let prefix = io.sourcing ? io.sourcing.file + ":" + io.sourcing.line + ": " : ""; + if (error.message && error.message.indexOf(prefix) !== 0) + error.message = prefix + error.message; + if (error.message) - dactyl.echoerr(template.linkifyHelp(prefix + error.message)); + dactyl.echoerr(template.linkifyHelp(error.message)); else dactyl.beep(); return; @@ -1434,6 +1456,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, { events: function () { events.addSessionListener(window, "click", dactyl.closure.onClick, true); + events.addSessionListener(window, "dactyl.execute", dactyl.closure.onExecute, true); }, // Only general options are added here, which are valid for all Dactyl extensions options: function () { @@ -1676,7 +1699,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { function (args) { try { let cmd = dactyl.userEval(args[0] || ""); - dactyl.execute(cmd, null, true); + dactyl.execute(cmd || "", null, true); } catch (e) { dactyl.echoerr(e); diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index 368f27ec..0907c02b 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -302,7 +302,7 @@ var AddonList = Class("AddonList", { if (addon && addon.id in this.addons) this.addons[addon.id].update(); if (this.ready) - this.modules.mow.resize(false); + this.modules.commandline.updateOutputHeight(false); }, onDisabled: function (addon) { this.update(addon); }, diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 9e04c492..8e0c6cc8 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -658,7 +658,7 @@ function Class() { var Constructor = eval(String.replace( - { template.map(context.contextList.filter(function (c) c.hasItems && c.items.length), - function (context) + { template.map(contexts, function (context) template.completionRow(context.title, "CompTitle") + template.map(context.items, function (item) context.createRow(item), null, 100)) } ); diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index 5dbbc897..ec985ee3 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -296,7 +296,7 @@ var DownloadList = Class("DownloadList", else { this.addDownload(download.id); - this.modules.mow.resize(false); + this.modules.commandline.updateOutputHeight(false); this.nodes.list.scrollIntoView(false); } this.update(); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 4459f597..94b3efaa 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -1344,13 +1344,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }); }, - maxErrors: 15, + errorCount: 0, errors: Class.memoize(function () []), + maxErrors: 15, reportError: function (error) { if (Cu.reportError) Cu.reportError(error); try { + this.errorCount++; + let obj = update({}, error, { toString: function () String(error), stack: <>{util.stackLines(String(error.stack || Error().stack)).join("\n").replace(/^/mg, "\t")} diff --git a/common/tests/functional/dactyl.js b/common/tests/functional/dactyl.js new file mode 100644 index 00000000..ae5a6853 --- /dev/null +++ b/common/tests/functional/dactyl.js @@ -0,0 +1,437 @@ + +var utils = require("utils"); +const { module, NS } = utils; + +var elementslib = module("resource://mozmill/modules/elementslib.js"); +var frame = module("resource://mozmill/modules/frame.js"); +var jumlib = module("resource://mozmill/modules/jum.js"); + +function wrapAssertNoErrors(func, message) { + return function wrapped(arg) this.assertNoErrors(func, this, arguments, message || arg); +} + +function assertMessage(funcName, want, got, message) { + if (typeof want === "string") + return utils.assertEqual(funcName, want, got, message); + else if (typeof want === "function") + return utils.test(want(got), { + function: funcName, + want: want, got: got, + comment: message + }); + else + return utils.test(want.test(got), { + function: funcName, + want: want, got: got, + comment: message + }); +} + +/** + * A controller for simulating Dactyl related user actions and for making + * assertions about the expected outcomes of such actions. + * + * @param {MozMillController} controller The browser's MozMill controller. + */ +function Controller(controller) { + var self = this; + this.controller = controller; + + /** + * @property {object} The dactyl modules namespace, to be used + * sparingly in tests. + */ + this.dactyl = controller.window.dactyl.modules; + + this.errorCount = 0; + + this._counBeep = function countBeep() { + self.beepCount++; + } + this._countError = function countError(message, highlight) { + if (/\bErrorMsg\b/.test(highlight)) + self.errorCount++; + } + this.dactyl.dactyl.registerObserver("beep", this._countBeep); + this.dactyl.dactyl.registerObserver("echoLine", this._countError); + this.dactyl.dactyl.registerObserver("echoMultiline", this._countError); + + this.resetErrorCount(); +} + +Controller.prototype = { + + teardown: function () { + this.dactyl.dactyl.unregisterObserver("beep", this._countBeep); + this.dactyl.dactyl.unregisterObserver("echoLine", this._countError); + this.dactyl.dactyl.unregisterObserver("echoMultiline", this._countError); + }, + + beepCount: 0, + errorCount: 0, + + /** + * Asserts that an error message is displayed during the execution + * of *func*. + * + * @param {function} func A function to call during before the + * assertion takes place. + * @param {object} self The 'this' object to be used during the call + * of *func*. @optional + * @param {Array} args Arguments to be passed to *func*. @optional + * @param {string} message The message to display upon assertion failure. @optional + */ + assertMessageError: function (func, self, args, message) { + let errorCount = this.errorCount; + this.assertNoErrors(func, self, args, message); + // dump("assertMessageError " + errorCount + " " + this.errorCount + "\n"); + return utils.assert('dactyl.assertMessageError', this.errorCount > errorCount, + "Expected error but got none" + (message ? ": " + message : "")); + }, + + /** + * Asserts that any output message text content matches *text*. + * + * @param {string|RegExp|function} want The expected text of the expected message line. + * @param {string} message The message to display upon assertion failure. + */ + assertMessage: function (want, message) { + return assertMessage('dactyl.assertMessage', want, + this.readMessageWindow() || this.readMessageLine(), + message); + }, + + /** + * Asserts that the output message line text content matches *text*. + * + * @param {string|RegExp|function} want The expected text of the expected message line. + * @param {string} message The message to display upon assertion failure. + */ + assertMessageLine: function (want, message) { + return assertMessage('dactyl.assertMessageLine', want, + this.readMessageLine(), + message); + }, + + /** + * Asserts that the output message window text content matches *text*. + * + * @param {string|RegExp|function} want The expected text of the message window. + * @param {string} message The message to display upon assertion failure. + */ + assertMessageWindow: function (want, message) { + return assertMessage('dactyl.assertMessageWindow', want, + this.readMessageWindow(), + message); + }, + + /** + * Asserts that the output message line text is an error and content matches *text*. + * + * @param {string|RegExp|function} want The expected text of the expected message line. + * @param {string} message The message to display upon assertion failure. + */ + assertErrorMessage: function (want, message) { + return assertMessage('dactyl.assertMessageError', want, + this.readMessageLine(), + message) && + assertMessage('dactyl.assertMessageError', /\bErrorMsg\b/, + this.elements.message.getAttributeNS(NS, "highlight"), + message); + }, + + /** + * Asserts that the multi-line output window is in the given state. + * + * @param {boolean} open True if the window is expected to be open. + * @param {string} message The message to display upon assertion failure. @optional + */ + assertMessageWindowOpen: function (open, message) { + return utils.assertEqual('dactyl.assertMessageWindowOpen', open, + !this.elements.multilineContainer.collapsed, + message || "Multi-line output not in the expected state"); + }, + + /** + * Asserts that the no errors have been reported since the last call + * to resetErrorCount. + * + * @param {function} func A function to call during before the + * assertion takes place. When present, the current error count + * is reset before execution. + * @optional + * @param {object} self The 'this' object to be used during the call + * of *func*. @optional + * @param {Array} args Arguments to be passed to *func*. @optional + * @param {string} message The message to display upon assertion failure. @optional + * @param {string} message The message to display upon assertion failure. @optional + */ + assertNoErrors: function (func, self, args, message) { + let msg = message ? ": " + message : ""; + let beepCount = this.beepCount; + let errorCount = this.errorCount; + + if (func) { + errorCount = this.dactyl.util.errorCount; + + try { + func.apply(self || this, args || []); + } + catch (e) { + this.dactyl.util.reportError(e); + } + } + + if (this.beepCount > beepCount) + this.frame.log({ + function: "dactyl.beepMonitor", + want: beepCount, got: this.beepCount, + comment: "Got " + (this.beepCount - beepCount) + " beeps during execution" + msg + }); + + if (errorCount != this.dactyl.util.errorCount) + var errors = this.dactyl.util.errors.slice(errorCount - this.dactyl.util.errorCount) + .join("\n"); + + return utils.assertEqual('dactyl.assertNoErrors', + errorCount, this.dactyl.util.errorCount, + "Errors were reported during the execution of this test" + msg + "\n" + errors); + }, + + /** + * Resets the error count used to determine whether new errors were + * reported during the execution of a test. + */ + resetErrorCount: function () { + this.errorCount = this.dactyl.util.errorCount; + }, + + /** + * Wraps the given function such that any errors triggered during + * its execution will trigger a failed assertion. + * + * @param {function} func The function to wrap. + * @param {string} message The message to display upon assertion failure. @optional + */ + wrapAssertNoErrors: function (func, message) { + let self = this; + return function wrapped() self.assertNoErrors(func, this, arguments, message); + }, + + /** + * Asserts that the current window selection matches *text*. + * + * @param {string|RegExp|function} text The expected text of the current selection. + * @param {string} message The message to display upon assertion failure. + */ + assertSelection: function (want, message) { + return assertMessage('dactyl.assertSelection', want, + String(this.controller.window.content.getSelection()), + message); + }, + + /** + * @property {string} The name of dactyl's current key handling + * mode. + */ + get currentMode() this.dactyl.modes.main.name, + + /** + * @property {object} A map of dactyl widgets to be used sparingly + * for focus assertions. + */ + get elements() let (self = this) ({ + /** + * @property {HTMLInputElement} The command line's command input box + */ + get commandInput() self.dactyl.commandline.widgets.active.command.inputField, + /** + * @property {Node|null} The currently focused node. + */ + get focused() self.controller.window.document.commandDispatcher.focusedElement, + /** + * @property {HTMLInputElement} The message bar's command input box + */ + get message() self.dactyl.commandline.widgets.active.message, + /** + * @property {Node} The multi-line output window. + */ + get multiline() self.dactyl.commandline.widgets.multilineOutput, + /** + * @property {Node} The multi-line output container. + */ + get multilineContainer() self.dactyl.commandline.widgets.mowContainer, + }), + + /** + * Returns dactyl to normal mode. + */ + setNormalMode: wrapAssertNoErrors(function () { + // XXX: Normal mode test + for (let i = 0; i < 15 && this.dactyl.modes.stack.length > 1; i++) + this.controller.keypress(null, "VK_ESCAPE", {}); + + this.controller.keypress(null, "l", { ctrlKey: true }); + + utils.assert("dactyl.setNormalMode", this.dactyl.modes.stack.length == 1, + "Failed to return to Normal mode"); + + this.assertMessageWindowOpen(false, "Returning to normal mode: Multi-line output not closed"); + this.assertMessageLine(function (msg) !msg, "Returning to normal mode: Message not cleared"); + }, "Returning to normal mode"), + + /** + * Returns dactyl to Ex mode. + */ + setExMode: wrapAssertNoErrors(function () { + if (this.currentMode !== "EX") { + this.setNormalMode(); + this.controller.keypress(null, ":", {}); + } + else { + this.elements.commandInput.value = ""; + } + }), + + /** + * Runs a Vi command. + * + * @param {string|Array} keys Either a string of simple keys suitable for + * {@link MozMillController#type} or an array of keysym - modifier + * pairs suitable for {@link MozMillController#keypress}. + */ + runViCommand: wrapAssertNoErrors(function (keys) { + if (typeof keys == "string") + keys = [[k] for each (k in keys)]; + keys.forEach(function ([key, modifiers]) { this.controller.keypress(null, key, modifiers || {}); }, this); + }), + + /** + * Runs an Ex command. + * + * @param {string} cmd The Ex command string as entered on the command + * line. + * @param {object} args An args object by means of which to execute + * the command. If absent *cmd* is parsed as a complete + * arguments string. @optional + */ + // TODO: Use execution code from commandline.js to catch more + // possible errors without being insanely inefficient after the + // merge. + runExCommand: wrapAssertNoErrors(function (cmd, args) { + this.setNormalMode(); + try { + // Force async commands to wait for their output to be ready + // before returning. + this.dactyl.commandline.savingOutput = true; + if (args) + this.dactyl.ex[cmd](args); + else if (true) + this.dactyl.commands.execute(cmd, null, false, null, + { file: "[Command Line]", line: 1 }); + else { + var doc = this.controller.window.document; + var event = doc.createEvent("Events"); + event.initEvent("dactyl.execute", false, false); + doc.documentElement.setAttribute("dactyl-execute", cmd); + doc.documentElement.dispatchEvent(event); + } + } + finally { + this.dactyl.commandline.savingOutput = false; + } + }), + + /** + * Triggers Ex completion for the given command string and ensures + * that no errors have occurred during the process. + * + * @param {string} cmd The Ex command string as entered on the command + * line. + */ + runExCompletion: wrapAssertNoErrors(function (cmd) { + this.setExMode(); + + utils.assertEqual("dactyl.runExCompletion", + this.elements.commandInput, + this.elements.focused, + "Running Ex Completion: The command line is not focused"); + + // dump("runExCompletion " + cmd + "\n"); + if (true) { + let input = this.elements.commandInput; + input.value = cmd; + + var event = input.ownerDocument.createEvent("Events"); + event.initEvent("change", true, false); + input.dispatchEvent(event); + } + else { + this.controller.type(null, cmd); + + utils.assertEqual("dactyl.runExCompletion", cmd, + this.elements.commandInput.editor.rootElement.firstChild.textContent, + "Command line does not have the expected value: " + cmd); + } + + this.controller.keypress(null, "VK_TAB", {}); + + // XXX + if (this.dactyl.commandline._tabTimer) + this.dactyl.commandline._tabTimer.flush(); + else if (this.dactyl.commandline.commandSession && this.dactyl.commandline.commandSession.completions) + this.dactyl.commandline.commandSession.completions.tabTimer.flush(); + }), + + /** + * Returns the text content of the output message line. + * + * @returns {string} The message line text content. + */ + readMessageLine: function () { + return this.elements.message.value; + }, + + /** + * Returns the text content of the output message window. + * + * @returns {string} The message window text content. + */ + readMessageWindow: function () { + if (!this.elements.multilineContainer.collapsed) + return this.elements.multiline.contentDocument.body.textContent; + return ""; + }, + + /** + * Opens the output message window by echoing a single newline character. + */ + openMessageWindow: wrapAssertNoErrors(function() { + this.dactyl.dactyl.echo("\n"); + }, "Opening message window"), + + /** + * Clears the current message. + */ + clearMessage: function() { + this.elements.message.value = ""; // XXX + }, + + /** + * Closes the output message window if open. + */ + closeMessageWindow: wrapAssertNoErrors(function() { + for (let i = 0; i < 15 && !this.elements.multilineContainer.collapsed; i++) + this.controller.keypress(null, "VK_ESCAPE", {}); + this.assertMessageWindowOpen(false, "Clearing message window failed"); + }, "Clearing message window"), + + /** + * @property {string} The specific Dactyl application. Eg. Pentadactyl + */ + get applicationName() this.dactyl.config.appName // XXX +}; + +exports.Controller = Controller; + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/testEchoCommands.js b/common/tests/functional/testEchoCommands.js new file mode 100644 index 00000000..d46694aa --- /dev/null +++ b/common/tests/functional/testEchoCommands.js @@ -0,0 +1,78 @@ +var dactyllib = require("dactyl"); + +var setupModule = function (module) { + controller = mozmill.getBrowserController(); + dactyl = new dactyllib.Controller(controller); +}; + +var teardownModule = function (module) { + dactyl.teardown(); +} + +var teardownTest = function (test) { + dactyl.closeMessageWindow(); +}; + +var testEchoCommand_SingleLineMessageAndClosedMOW_MessageDisplayedInMessageLine = function () { + const output = "foobar"; + + assertEchoGeneratesLineOutput({ + ECHO_COMMAND: "echo " + output.quote(), + EXPECTED_OUTPUT: output + }); +}; + +var testEchoCommand_SingleLineMessageAndOpenMOW_MessageAppendedToMOW = function () { + const output = "foobar"; + + dactyl.openMessageWindow(); + + assertEchoGeneratesWindowOutput({ + ECHO_COMMAND: "echo " + output.quote(), + EXPECTED_OUTPUT: RegExp(output) + }); +}; + +var testEchoCommand_MultilineMessageAndClosedMOW_MessageDisplayedInMOW = function () { + const output = "foo\nbar"; + + assertEchoGeneratesWindowOutput({ + ECHO_COMMAND: "echo " + output.quote(), + EXPECTED_OUTPUT: output + }); +}; + +var testEchoCommand_MultilineMessageAndOpenMOW_MessageAppendedToMOW = function () { + const output = "foo\nbar"; + + dactyl.openMessageWindow(); + + assertEchoGeneratesWindowOutput({ + ECHO_COMMAND: "echo " + output.quote(), + EXPECTED_OUTPUT: RegExp(output) + }); +}; + +var testEchoCommand_ObjectArgumentAndClosedMOW_MessageDisplayedInMOW = function () { + assertEchoGeneratesWindowOutput({ + ECHO_COMMAND: "echo var obj = { x: 1, y: 2 }; obj;", + EXPECTED_OUTPUT: "[object\u00A0Object]::\nx: 1\ny: 2\n" + }); +}; + +function executeCommand(command) { + dactyl.runViCommand(":" + command); + dactyl.runViCommand([["VK_RETURN"]]); +} + +function assertEchoGeneratesWindowOutput({ ECHO_COMMAND, EXPECTED_OUTPUT }) { + executeCommand(ECHO_COMMAND); + dactyl.assertMessageWindow(EXPECTED_OUTPUT); +} + +function assertEchoGeneratesLineOutput({ ECHO_COMMAND, EXPECTED_OUTPUT }) { + executeCommand(ECHO_COMMAND); + dactyl.assertMessageLine(EXPECTED_OUTPUT); +} + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/testFindCommands.js b/common/tests/functional/testFindCommands.js new file mode 100644 index 00000000..f567cdfa --- /dev/null +++ b/common/tests/functional/testFindCommands.js @@ -0,0 +1,49 @@ +var dactyllib = require("dactyl"); + +const FIND_TEST_PAGE = collector.addHttpResource("./data/") + "find.html"; + +var setupModule = function (module) { + controller = mozmill.getBrowserController(); + dactyl = new dactyllib.Controller(controller); +}; + +var teardownModule = function (module) { + dactyl.teardown(); +} + +var setupTest = function (test) { + controller.open(FIND_TEST_PAGE); + controller.waitForPageLoad(controller.tabs.activeTab); + controller.sleep(1000); +}; + +var testFindCommand_PresentAlphabeticText_TextSelected = function () { + assertTextFoundInPage("letter") +}; + +var testFindCommand_PresentNumericText_TextSelected = function () { + assertTextFoundInPage("3.141") +}; + +var testFindCommand_MissingText_ErrorMessageDisplayed = function () { + const MISSING_TEXT = "8c307545a017f60add90ef08955e148e"; + const PATTERN_NOT_FOUND_ERROR = "E486: Pattern not found: " + MISSING_TEXT; + + runTextSearchCommand(MISSING_TEXT); + + dactyl.assertErrorMessage(PATTERN_NOT_FOUND_ERROR); +}; + +function runTextSearchCommand(str) { + dactyl.runViCommand("/" + str); + dactyl.runViCommand([["VK_RETURN"]]); + + controller.sleep(0); +} + +function assertTextFoundInPage(text) { + runTextSearchCommand(text); + dactyl.assertSelection(text); +} + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/testHelpCommands.js b/common/tests/functional/testHelpCommands.js new file mode 100644 index 00000000..270e40c9 --- /dev/null +++ b/common/tests/functional/testHelpCommands.js @@ -0,0 +1,66 @@ +var jumlib = {}; Components.utils.import("resource://mozmill/modules/jum.js", jumlib); +var dactyllib = require("dactyl"); + +var setupModule = function (module) { + controller = mozmill.getBrowserController(); + dactyl = new dactyllib.Controller(controller); +}; + +var teardownModule = function (module) { + dactyl.teardown(); +} + +var setupTest = function (test) { + dactyl.runViCommand([["VK_ESCAPE"]]); +}; + +const HELP_FILES = ["all", "tutorial", "intro", "starting", "browsing", + "buffer", "cmdline", "insert", "options", "pattern", "tabs", "hints", + "map", "eval", "marks", "repeat", "autocommands", "print", "gui", + "styling", "message", "developer", "various", "faq", "index", "plugins"]; + +var testViHelpCommand_OpensIntroHelpPage = function () { + assertHelpOpensPageWithTag({ + HELP_COMMAND: function () { dactyl.runViCommand([["VK_F1"]]); }, + EXPECTED_HELP_TAG: "intro.xml" + }); +}; + +var testViHelpAllCommand_OpensAllHelpPage = function () { + assertHelpOpensPageWithTag({ + HELP_COMMAND: function () { dactyl.runViCommand([["VK_F1", { altKey: true }]]); }, + EXPECTED_HELP_TAG: "all.xml" + }); +}; + +var testExHelpCommand_NoArgs_OpensIntroHelpPage = function () { + assertHelpOpensPageWithTag({ + HELP_COMMAND: function () { dactyl.runExCommand("help"); }, + EXPECTED_HELP_TAG: "intro.xml" + }); +}; + +var testExHelpAllCommand_NoArgs_OpensAllHelpPage = function () { + assertHelpOpensPageWithTag({ + HELP_COMMAND: function () { dactyl.runExCommand("helpall"); }, + EXPECTED_HELP_TAG: "all.xml" + }); +}; + +var testExHelpCommand_PageTagArg_OpensHelpPageContainingTag = function () { + for (let [, file] in Iterator(HELP_FILES)) { + let tag = file + ".xml"; + assertHelpOpensPageWithTag({ + HELP_COMMAND: function () { dactyl.runExCommand("help " + tag); }, + EXPECTED_HELP_TAG: tag + }); + } +}; + +function assertHelpOpensPageWithTag({ HELP_COMMAND, EXPECTED_HELP_TAG }) { + HELP_COMMAND(); + controller.waitForPageLoad(controller.tabs.activeTab); + controller.assertNode(new elementslib.ID(controller.tabs.activeTab, EXPECTED_HELP_TAG)); +} + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/testShellCommands.js b/common/tests/functional/testShellCommands.js new file mode 100644 index 00000000..db8a48e3 --- /dev/null +++ b/common/tests/functional/testShellCommands.js @@ -0,0 +1,37 @@ +var dactyllib = require("dactyl"); + +var setupModule = function (module) { + controller = mozmill.getBrowserController(); + dactyl = new dactyllib.Controller(controller); +}; + +var teardownModule = function (module) { + dactyl.teardown(); +} + +var teardownTest = function (test) { + dactyl.closeMessageWindow(); +}; + +var testRunCommand_ExecutingOutputCommand_OutputDisplayed = function () { + const EXPECTED_OUTPUT = "foobar"; + const COMMAND = "run echo " + EXPECTED_OUTPUT; + + dactyl.runExCommand(COMMAND); + + dactyl.assertMessageWindow(RegExp(EXPECTED_OUTPUT)); +}; + +var testRunCommand_RepeatArg_LastCommandRepeated = function () { + const EXPECTED_OUTPUT = /foobar$/; // XXX + const COMMAND = "run echo 'foobar'"; + const REPEAT_COMMAND = "run!"; + + dactyl.runExCommand(COMMAND); + dactyl.closeMessageWindow(); + dactyl.runExCommand(REPEAT_COMMAND); + + dactyl.assertMessageWindow(EXPECTED_OUTPUT); +}; + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/testVersionCommand.js b/common/tests/functional/testVersionCommand.js new file mode 100644 index 00000000..12bf4b8e --- /dev/null +++ b/common/tests/functional/testVersionCommand.js @@ -0,0 +1,38 @@ +var dactyllib = require("dactyl"); + +var setupModule = function (module) { + controller = mozmill.getBrowserController(); + dactyl = new dactyllib.Controller(controller); +}; + +var teardownModule = function (module) { + dactyl.teardown(); +} + +var setupTest = function (test) { + dactyl.closeMessageWindow(); +}; + +var testVersionCommand_NoArg_VersionStringDisplayed = function () { + const EXPECTED_OUTPUT = RegExp(dactyl.applicationName + ".+ (.+) running on:.+"); // XXX + + dactyl.runExCommand("version"); + + dactyl.assertMessageWindow(EXPECTED_OUTPUT); +}; + +var testVersionCommand_BangArg_HostAppVersionPageDisplayed = function () { + const EXPECTED_URL = "about:"; + const EXPECTED_TITLE = "About:"; + const BLANK_PAGE_URL = "about:blank"; + + controller.open(BLANK_PAGE_URL); + controller.waitForPageLoad(controller.tabs.activeTab); + dactyl.runExCommand("version!"); + controller.waitForPageLoad(controller.tabs.activeTab); + + controller.assert(function () controller.tabs.activeTab.location.href === EXPECTED_URL); + controller.assert(function () controller.tabs.activeTab.title === EXPECTED_TITLE); +}; + +// vim: sw=4 ts=8 et: diff --git a/common/tests/functional/utils.js b/common/tests/functional/utils.js new file mode 100644 index 00000000..309fea11 --- /dev/null +++ b/common/tests/functional/utils.js @@ -0,0 +1,49 @@ +function module(uri) { + let obj = {}; + Components.utils.import(uri, obj); + return obj; +} + +var elementslib = module("resource://mozmill/modules/elementslib.js"); +var frame = module("resource://mozmill/modules/frame.js"); +var jumlib = module("resource://mozmill/modules/jum.js"); + +function toJSON(val) { + if (typeof val == "function") + return val.toSource(); + else + return val; +} + +function test(val, params) { + frame.events[val ? "pass" : "fail"](params); + return val; +} + +for (var [k, v] in Iterator({ + + NS: Namespace("dactyl", "http://vimperator.org/namespaces/liberator"), + + module: module, + + toJSON: toJSON, + + test: test, + + assert: function (funcName, value, comment) + test(value, { + function: funcName, + value: toJSON(value), + comment: toJSON(comment) + }), + + assertEqual: function (funcName, want, got, comment) + test(want == got, { + function: funcName, + want: toJSON(want), got: toJSON(got), + comment: toJSON(comment) + }) +})) + exports[k] = v; + +// vim: sw=4 ts=8 et: