1 var utils = {}; Components.utils.import(/([^ ]+\/)[^\/]+$/.exec(Components.stack.filename)[1] + "utils.jsm", utils);
3 var EXPORTED_SYMBOLS = ["Controller"];
5 const { module, NS } = utils;
7 var elementslib = module("resource://mozmill/modules/elementslib.js");
8 var frame = module("resource://mozmill/modules/frame.js");
9 var jumlib = module("resource://mozmill/modules/jum.js");
11 function wrapAssertNoErrors(func, message) {
12 return function wrapped(arg) this.assertNoErrors(func, this, arguments, message || arg);
15 function assertMessage(funcName, want, got, message) {
16 if (typeof want === "string")
17 return utils.assertEqual(funcName, want, got, message);
18 else if (typeof want === "function") {
20 if (res === undefined)
22 return utils.test(res, {
29 return utils.test(want.test(got), {
37 * A controller for simulating Dactyl related user actions and for making
38 * assertions about the expected outcomes of such actions.
40 * @param {MozMillController} controller The browser's MozMill controller.
42 function Controller(controller) {
44 this.controller = controller;
47 * @property {object} The dactyl modules namespace, to be used
50 this.modules = controller.window.dactyl.modules;
54 this._countBeep = function countBeep() {
58 this._countError = function countError(message, highlight) {
59 if (/\b(Error|Warning)Msg\b/.test(highlight))
60 self.errors.push(String(message));
62 this.modules.dactyl.registerObserver("beep", this._countBeep);
63 this.modules.dactyl.registerObserver("echoLine", this._countError);
64 this.modules.dactyl.registerObserver("echoMultiline", this._countError);
66 this.resetErrorCount();
69 Controller.prototype = {
71 teardown: function () {
72 this.modules.dactyl.unregisterObserver("beep", this._countBeep);
73 this.modules.dactyl.unregisterObserver("echoLine", this._countError);
74 this.modules.dactyl.unregisterObserver("echoMultiline", this._countError);
81 * Asserts that an error message is displayed during the execution
84 * @param {function} func A function to call during before the
85 * assertion takes place.
86 * @param {object} self The 'this' object to be used during the call
87 * of *func*. @optional
88 * @param {Array} args Arguments to be passed to *func*. @optional
89 * @param {string} message The message to display upon assertion failure. @optional
91 assertMessageError: function (func, self, args, message) {
92 let errorCount = this.errors.length;
93 this.assertNoErrors(func, self, args, message);
94 // dump("assertMessageError " + errorCount + " " + this.errorMessageCount + "\n");
95 return utils.assert('dactyl.assertMessageError', this.errors.length > errorCount,
96 "Expected error but got none" + (message ? ": " + message : ""));
100 * Asserts that any output message text content matches *text*.
102 * @param {string|RegExp|function} want The expected text of the expected message line.
103 * @param {string} message The message to display upon assertion failure.
105 assertMessage: function (want, message) {
106 return assertMessage('dactyl.assertMessage', want,
107 this.readMessageWindow() || this.readMessageLine(),
112 * Asserts that the output message line text content matches *text*.
114 * @param {string|RegExp|function} want The expected text of the expected message line.
115 * @param {string} message The message to display upon assertion failure.
117 assertMessageLine: function (want, message) {
118 return assertMessage('dactyl.assertMessageLine', want,
119 this.readMessageLine(),
124 * Asserts that the output message window text content matches *text*.
126 * @param {string|RegExp|function} want The expected text of the message window.
127 * @param {string} message The message to display upon assertion failure.
129 assertMessageWindow: function (want, message) {
130 return assertMessage('dactyl.assertMessageWindow', want,
131 this.readMessageWindow(),
136 * Asserts that the output message line text is an error and content matches *text*.
138 * @param {string|RegExp|function} want The expected text of the expected message line.
139 * @param {string} message The message to display upon assertion failure.
141 assertErrorMessage: function (want, message) {
142 return assertMessage('dactyl.assertMessageError', want,
143 this.readMessageLine(),
145 assertMessage('dactyl.assertMessageError', /\bErrorMsg\b/,
146 this.elements.message.getAttributeNS(NS, "highlight"),
151 * Asserts that the multi-line output window is in the given state.
153 * @param {boolean} open True if the window is expected to be open.
154 * @param {string} message The message to display upon assertion failure. @optional
156 assertMessageWindowOpen: function (open, message) {
157 return utils.assertEqual('dactyl.assertMessageWindowOpen', open,
158 !this.elements.multilineContainer.collapsed,
159 message || "Multi-line output not in the expected state");
163 * Asserts that the no errors have been reported since the last call
164 * to resetErrorCount.
166 * @param {function} func A function to call during before the
167 * assertion takes place. When present, the current error count
168 * is reset before execution.
170 * @param {object} self The 'this' object to be used during the call
171 * of *func*. @optional
172 * @param {Array} args Arguments to be passed to *func*. @optional
173 * @param {string} message The message to display upon assertion failure. @optional
175 assertNoErrors: function (func, self, args, message) {
176 let msg = message ? ": " + message : "";
178 let beepCount = this.beepCount;
179 let errorCount = this.errorCount;
181 errorCount = this.modules.util.errorCount;
184 var returnVal = func.apply(self || this, args || []);
187 this.modules.util.reportError(e);
191 if (this.beepCount > beepCount)
193 function: "dactyl.beepMonitor",
194 want: beepCount, got: this.beepCount,
195 comment: "Got " + (this.beepCount - beepCount) + " beeps during execution" + msg
198 if (errorCount != this.modules.util.errorCount)
199 var errors = this.modules.util.errors.slice(errorCount - this.modules.util.errorCount)
202 var res = utils.assertEqual('dactyl.assertNoErrors',
203 errorCount, this.modules.util.errorCount,
204 "Errors were reported during the execution of this test" + msg + "\n" + errors);
206 return returnVal === undefined ? res : returnVal;
210 * Asserts that the no error messages are reported during the call
213 * @param {function} func A function to call during before the
214 * assertion takes place. When present, the current error count
215 * is reset before execution.
217 * @param {object} self The 'this' object to be used during the call
218 * of *func*. @optional
219 * @param {Array} args Arguments to be passed to *func*. @optional
220 * @param {string} message The message to display upon assertion failure. @optional
222 assertNoErrorMessages: function (func, self, args, message) {
223 let msg = message ? ": " + message : "";
224 let count = this.errors.length;
227 func.apply(self || this, args || []);
230 this.modules.util.reportError(e);
233 return utils.assertEqual('dactyl.assertNoErrorMessages', count, this.errors.length,
234 "Error messsages were reported" + msg + ":\n\t" +
235 this.errors.slice(count).join("\n\t"));
239 * Resets the error count used to determine whether new errors were
240 * reported during the execution of a test.
242 resetErrorCount: function () {
243 this.errorCount = this.modules.util.errorCount;
247 * Wraps the given function such that any errors triggered during
248 * its execution will trigger a failed assertion.
250 * @param {function} func The function to wrap.
251 * @param {string} message The message to display upon assertion failure. @optional
253 wrapAssertNoErrors: function (func, message) {
255 return function wrapped() self.assertNoErrors(func, this, arguments, message);
259 * Asserts that the current window selection matches *text*.
261 * @param {string|RegExp|function} text The expected text of the current selection.
262 * @param {string} message The message to display upon assertion failure.
264 assertSelection: function (want, message) {
265 return assertMessage('dactyl.assertSelection', want,
266 String(this.controller.window.content.getSelection()),
271 * @property {string} The name of dactyl's current key handling
274 get currentMode() this.modules.modes.main.name,
277 * @property {object} A map of dactyl widgets to be used sparingly
278 * for focus assertions.
280 get elements() let (self = this) ({
282 * @property {HTMLInputElement} The command line's command input box
284 get commandInput() self.modules.commandline.widgets.active.command.inputField,
286 * @property {Node|null} The currently focused node.
288 get focused() self.controller.window.document.commandDispatcher.focusedElement,
290 * @property {HTMLInputElement} The message bar's command input box
292 get message() self.modules.commandline.widgets.active.message,
294 * @property {Node} The multi-line output window.
296 get multiline() self.modules.commandline.widgets.multilineOutput,
298 * @property {Node} The multi-line output container.
300 get multilineContainer() self.modules.commandline.widgets.mowContainer,
304 * Returns dactyl to normal mode.
306 setNormalMode: wrapAssertNoErrors(function () {
307 // XXX: Normal mode test
308 for (let i = 0; i < 15 && this.modules.modes.stack.length > 1; i++)
309 this.controller.keypress(null, "VK_ESCAPE", {});
311 this.controller.keypress(null, "l", { ctrlKey: true });
313 utils.assert("dactyl.setNormalMode", this.modules.modes.stack.length == 1,
314 "Failed to return to Normal mode");
316 this.assertMessageWindowOpen(false, "Returning to normal mode: Multi-line output not closed");
317 this.assertMessageLine(function (msg) !msg, "Returning to normal mode: Message not cleared");
318 }, "Returning to normal mode"),
321 * Returns dactyl to Ex mode.
323 setExMode: wrapAssertNoErrors(function () {
324 if (this.currentMode !== "EX") {
325 this.setNormalMode();
326 this.controller.keypress(null, ":", {});
329 this.elements.commandInput.value = "";
336 * @param {string|Array} keys Either a string of simple keys suitable for
337 * {@link MozMillController#type} or an array of keysym - modifier
338 * pairs suitable for {@link MozMillController#keypress}.
340 runViCommand: wrapAssertNoErrors(function (keys) {
341 if (typeof keys == "string")
342 keys = [[k] for each (k in keys)];
343 keys.forEach(function ([key, modifiers]) { this.controller.keypress(null, key, modifiers || {}); }, this);
347 * Runs an Ex command.
349 * @param {string} cmd The Ex command string as entered on the command
351 * @param {object} args An args object by means of which to execute
352 * the command. If absent *cmd* is parsed as a complete
353 * arguments string. @optional
355 // TODO: Use execution code from commandline.js to catch more
356 // possible errors without being insanely inefficient after the
358 runExCommand: wrapAssertNoErrors(function (cmd, args) {
359 this.setNormalMode();
361 // Force async commands to wait for their output to be ready
363 this.modules.commandline.savingOutput = true;
365 this.modules.ex[cmd](args);
367 this.modules.commands.execute(cmd, null, false, null,
368 { file: "[Command Line]", line: 1 });
370 var doc = this.controller.window.document;
371 var event = doc.createEvent("Events");
372 event.initEvent("dactyl.execute", false, false);
373 doc.documentElement.setAttribute("dactyl-execute", cmd);
374 doc.documentElement.dispatchEvent(event);
378 this.modules.commandline.savingOutput = false;
383 * Triggers a completion function with the given arguments an
384 * ensures that no errors have occurred during the process.
386 * @param {object} self The 'this' object for which to trigger the
388 * @param {function|string} func The method or method name to call.
389 * @param {string} string The method or method name to call. @optional
390 * @param {string} message The message to display upon assertion failure. @optional
391 * @param {...} Extra arguments are passed to the completion
394 testCompleter: wrapAssertNoErrors(function testCompleter(self, func, string, message) {
395 var context = this.modules.CompletionContext(string || "");
396 context.tabPressed = true;
397 context.forkapply("completions", 0, self, func, Array.slice(arguments, testCompleter.length));
399 utils.assert("dactyl.runCompletions", context.wait(5000),
400 message || "Completion failed: " + self + "." + func);
402 for (var [, ctxt] in Iterator(context.contextList))
403 for (var [, item] in Iterator(ctxt.items))
404 ctxt.createRow(item);
410 * Triggers Ex completion for the given command string and ensures
411 * that no errors have occurred during the process.
413 * @param {string} cmd The Ex command string as entered on the command
415 * @param {boolean} longWay Whether to test the completion by
416 * entering it into the command line and dispatching a <Tab> key
419 runExCompletion: wrapAssertNoErrors(function (cmd, longWay) {
420 // dump("runExCompletion " + cmd + "\n");
422 var context = this.modules.CompletionContext(cmd);
423 context.tabPressed = true;
424 context.fork("ex", 0, this.modules.completion, "ex");
426 utils.assert("dactyl.runCompletions", context.wait(5000),
427 "Completion failed: " + cmd.quote());
429 for (var [, ctxt] in Iterator(context.contextList))
430 for (var [, item] in Iterator(ctxt.items))
431 ctxt.createRow(item);
438 utils.assertEqual("dactyl.assertCommandLineFocused",
439 this.elements.commandInput,
440 this.elements.focused,
441 "Running Ex Completion: The command line is not focused");
444 let input = this.elements.commandInput;
447 var event = input.ownerDocument.createEvent("Events");
448 event.initEvent("change", true, false);
449 input.dispatchEvent(event);
452 this.controller.type(null, cmd);
454 utils.assertEqual("dactyl.runExCompletion", cmd,
455 this.elements.commandInput.editor.rootElement.firstChild.textContent,
456 "Command line does not have the expected value: " + cmd);
459 this.controller.keypress(null, "VK_TAB", {});
462 if (this.modules.commandline._tabTimer)
463 this.modules.commandline._tabTimer.flush();
464 else if (this.modules.commandline.commandSession && this.modules.commandline.commandSession.completions)
465 this.modules.commandline.commandSession.completions.tabTimer.flush();
470 * Returns the text content of the output message line.
472 * @returns {string} The message line text content.
474 readMessageLine: function () {
475 return this.elements.message.value;
479 * Returns the text content of the output message window.
481 * @returns {string} The message window text content.
483 readMessageWindow: function () {
484 if (!this.elements.multilineContainer.collapsed)
485 return this.elements.multiline.contentDocument.body.textContent;
490 * Opens the output message window by echoing a single newline character.
492 openMessageWindow: wrapAssertNoErrors(function () {
493 this.modules.dactyl.echo("\n");
494 }, "Opening message window"),
497 * Clears the current message.
499 clearMessage: function () {
500 this.elements.message.value = ""; // XXX
504 * Closes the output message window if open.
506 closeMessageWindow: wrapAssertNoErrors(function () {
507 for (let i = 0; i < 15 && !this.elements.multilineContainer.collapsed; i++)
508 this.controller.keypress(null, "VK_ESCAPE", {});
509 this.assertMessageWindowOpen(false, "Clearing message window failed");
510 }, "Clearing message window"),
513 * @property {string} The specific Dactyl application. Eg. Pentadactyl
515 get applicationName() this.modules.config.appName // XXX
518 // vim: sw=4 ts=8 et ft=javascript: