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) {
43 this.controller = controller;
46 * @property {object} The dactyl modules namespace, to be used
49 this.modules = controller.window.dactyl.modules;
53 this._countBeep = () => {
57 this._countError = (message, highlight) => {
58 if (/\b(Error|Warning)Msg\b/.test(highlight))
59 this.errors.push(String(message));
61 this.modules.dactyl.registerObserver("beep", this._countBeep);
62 this.modules.dactyl.registerObserver("echoLine", this._countError);
63 this.modules.dactyl.registerObserver("echoMultiline", this._countError);
65 this.resetErrorCount();
68 Controller.prototype = {
70 teardown: function () {
71 this.modules.dactyl.unregisterObserver("beep", this._countBeep);
72 this.modules.dactyl.unregisterObserver("echoLine", this._countError);
73 this.modules.dactyl.unregisterObserver("echoMultiline", this._countError);
80 * Asserts that an error message is displayed during the execution
83 * @param {function} func A function to call during before the
84 * assertion takes place.
85 * @param {object} self The 'this' object to be used during the call
86 * of *func*. @optional
87 * @param {Array} args Arguments to be passed to *func*. @optional
88 * @param {string} message The message to display upon assertion failure. @optional
90 assertMessageError: function (func, self, args, message) {
91 let errorCount = this.errors.length;
92 this.assertNoErrors(func, self, args, message);
93 // dump("assertMessageError " + errorCount + " " + this.errorMessageCount + "\n");
94 return utils.assert('dactyl.assertMessageError', this.errors.length > errorCount,
95 "Expected error but got none" + (message ? ": " + message : ""));
99 * Asserts that any output message text content matches *text*.
101 * @param {string|RegExp|function} want The expected text of the expected message line.
102 * @param {string} message The message to display upon assertion failure.
104 assertMessage: function (want, message) {
105 return assertMessage('dactyl.assertMessage', want,
106 this.readMessageWindow() || this.readMessageLine(),
111 * Asserts that the output message line text content matches *text*.
113 * @param {string|RegExp|function} want The expected text of the expected message line.
114 * @param {string} message The message to display upon assertion failure.
116 assertMessageLine: function (want, message) {
117 return assertMessage('dactyl.assertMessageLine', want,
118 this.readMessageLine(),
123 * Asserts that the output message window text content matches *text*.
125 * @param {string|RegExp|function} want The expected text of the message window.
126 * @param {string} message The message to display upon assertion failure.
128 assertMessageWindow: function (want, message) {
129 return assertMessage('dactyl.assertMessageWindow', want,
130 this.readMessageWindow(),
135 * Asserts that the output message line text is an error and content matches *text*.
137 * @param {string|RegExp|function} want The expected text of the expected message line.
138 * @param {string} message The message to display upon assertion failure.
140 assertErrorMessage: function (want, message) {
141 return assertMessage('dactyl.assertMessageError', want,
142 this.readMessageLine(),
144 assertMessage('dactyl.assertMessageError', /\bErrorMsg\b/,
145 this.elements.message.getAttributeNS(NS, "highlight"),
150 * Asserts that the multi-line output window is in the given state.
152 * @param {boolean} open True if the window is expected to be open.
153 * @param {string} message The message to display upon assertion failure. @optional
155 assertMessageWindowOpen: function (open, message) {
156 return utils.assertEqual('dactyl.assertMessageWindowOpen', open,
157 !this.elements.multilineContainer.collapsed,
158 message || "Multi-line output not in the expected state");
162 * Asserts that the no errors have been reported since the last call
163 * to resetErrorCount.
165 * @param {function} func A function to call during before the
166 * assertion takes place. When present, the current error count
167 * is reset before execution.
169 * @param {object} self The 'this' object to be used during the call
170 * of *func*. @optional
171 * @param {Array} args Arguments to be passed to *func*. @optional
172 * @param {string} message The message to display upon assertion failure. @optional
174 assertNoErrors: function (func, self, args, message) {
175 let msg = message ? ": " + message : "";
177 let beepCount = this.beepCount;
178 let errorCount = this.errorCount;
180 errorCount = this.modules.util.errorCount;
183 var returnVal = func.apply(self || this, args || []);
186 this.modules.util.reportError(e);
190 if (this.beepCount > beepCount)
192 function: "dactyl.beepMonitor",
193 want: beepCount, got: this.beepCount,
194 comment: "Got " + (this.beepCount - beepCount) + " beeps during execution" + msg
197 if (errorCount != this.modules.util.errorCount)
198 var errors = this.modules.util.errors.slice(errorCount - this.modules.util.errorCount)
201 var res = utils.assertEqual('dactyl.assertNoErrors',
202 errorCount, this.modules.util.errorCount,
203 "Errors were reported during the execution of this test" + msg + "\n" + errors);
205 return returnVal === undefined ? res : returnVal;
209 * Asserts that the no error messages are reported during the call
212 * @param {function} func A function to call during before the
213 * assertion takes place. When present, the current error count
214 * is reset before execution.
216 * @param {object} self The 'this' object to be used during the call
217 * of *func*. @optional
218 * @param {Array} args Arguments to be passed to *func*. @optional
219 * @param {string} message The message to display upon assertion failure. @optional
221 assertNoErrorMessages: function (func, self, args, message) {
222 let msg = message ? ": " + message : "";
223 let count = this.errors.length;
226 func.apply(self || this, args || []);
229 this.modules.util.reportError(e);
232 return utils.assertEqual('dactyl.assertNoErrorMessages', count, this.errors.length,
233 "Error messsages were reported" + msg + ":\n\t" +
234 this.errors.slice(count).join("\n\t"));
238 * Resets the error count used to determine whether new errors were
239 * reported during the execution of a test.
241 resetErrorCount: function () {
242 this.errorCount = this.modules.util.errorCount;
246 * Wraps the given function such that any errors triggered during
247 * its execution will trigger a failed assertion.
249 * @param {function} func The function to wrap.
250 * @param {string} message The message to display upon assertion failure. @optional
252 wrapAssertNoErrors: function (func, message) {
253 return () => this.assertNoErrors(func, this, arguments, message);
257 * Asserts that the current window selection matches *text*.
259 * @param {string|RegExp|function} text The expected text of the current selection.
260 * @param {string} message The message to display upon assertion failure.
262 assertSelection: function (want, message) {
263 return assertMessage('dactyl.assertSelection', want,
264 String(this.controller.window.content.getSelection()),
269 * @property {string} The name of dactyl's current key handling
272 get currentMode() this.modules.modes.main.name,
275 * @property {object} A map of dactyl widgets to be used sparingly
276 * for focus assertions.
278 get elements() let (self = this) ({
280 * @property {HTMLInputElement} The command line's command input box
282 get commandInput() self.modules.commandline.widgets.active.command.inputField,
284 * @property {Node|null} The currently focused node.
286 get focused() self.controller.window.document.commandDispatcher.focusedElement,
288 * @property {HTMLInputElement} The message bar's command input box
290 get message() self.modules.commandline.widgets.active.message,
292 * @property {Node} The multi-line output window.
294 get multiline() self.modules.commandline.widgets.multilineOutput,
296 * @property {Node} The multi-line output container.
298 get multilineContainer() self.modules.commandline.widgets.mowContainer,
302 * Returns dactyl to Normal mode.
304 setNormalMode: wrapAssertNoErrors(function () {
305 // XXX: Normal mode test
306 for (let i = 0; i < 15 && this.modules.modes.stack.length > 1; i++)
307 this.controller.keypress(null, "VK_ESCAPE", {});
309 this.controller.keypress(null, "l", { ctrlKey: true });
311 utils.assert("dactyl.setNormalMode", this.modules.modes.stack.length == 1,
312 "Failed to return to Normal mode");
314 this.assertMessageWindowOpen(false, "Returning to Normal mode: Multi-line output not closed");
315 this.assertMessageLine(function (msg) !msg, "Returning to Normal mode: Message not cleared");
316 }, "Returning to Normal mode"),
319 * Returns dactyl to Ex mode.
321 setExMode: wrapAssertNoErrors(function () {
322 if (this.currentMode !== "EX") {
323 this.setNormalMode();
324 this.controller.keypress(null, ":", {});
327 this.elements.commandInput.value = "";
334 * @param {string|Array} keys Either a string of simple keys suitable for
335 * {@link MozMillController#type} or an array of keysym - modifier
336 * pairs suitable for {@link MozMillController#keypress}.
338 runViCommand: wrapAssertNoErrors(function (keys) {
339 if (typeof keys == "string")
340 keys = [[k] for each (k in keys)];
341 keys.forEach(function ([key, modifiers]) { this.controller.keypress(null, key, modifiers || {}); }, this);
345 * Runs an Ex command.
347 * @param {string} cmd The Ex command string as entered on the command
349 * @param {object} args An args object by means of which to execute
350 * the command. If absent *cmd* is parsed as a complete
351 * arguments string. @optional
353 // TODO: Use execution code from commandline.js to catch more
354 // possible errors without being insanely inefficient after the
356 runExCommand: wrapAssertNoErrors(function (cmd, args) {
357 this.setNormalMode();
359 // Force async commands to wait for their output to be ready
361 this.modules.commandline.savingOutput = true;
363 this.modules.ex[cmd](args);
365 this.modules.commands.execute(cmd, null, false, null,
366 { file: "[Command Line]", line: 1 });
368 var doc = this.controller.window.document;
369 var event = doc.createEvent("Events");
370 event.initEvent("dactyl.execute", false, false);
371 doc.documentElement.setAttribute("dactyl-execute", cmd);
372 doc.documentElement.dispatchEvent(event);
376 this.modules.commandline.savingOutput = false;
381 * Triggers a completion function with the given arguments an
382 * ensures that no errors have occurred during the process.
384 * @param {object} self The 'this' object for which to trigger the
386 * @param {function|string} func The method or method name to call.
387 * @param {string} string The method or method name to call. @optional
388 * @param {string} message The message to display upon assertion failure. @optional
389 * @param {...} Extra arguments are passed to the completion
392 testCompleter: wrapAssertNoErrors(function testCompleter(self, func, string, message, ...args) {
393 var context = this.modules.CompletionContext(string || "");
394 context.tabPressed = true;
395 context.forkapply("completions", 0, self, func, args);
397 utils.assert("dactyl.runCompletions", context.wait(5000),
398 message || "Completion failed: " + self + "." + func);
400 for (var [, ctxt] in Iterator(context.contextList))
401 for (var [, item] in Iterator(ctxt.items))
402 ctxt.createRow(item);
408 * Triggers Ex completion for the given command string and ensures
409 * that no errors have occurred during the process.
411 * @param {string} cmd The Ex command string as entered on the command
413 * @param {boolean} longWay Whether to test the completion by
414 * entering it into the command line and dispatching a <Tab> key
417 runExCompletion: wrapAssertNoErrors(function (cmd, longWay) {
418 // dump("runExCompletion " + cmd + "\n");
420 var context = this.modules.CompletionContext(cmd);
421 context.tabPressed = true;
422 context.fork("ex", 0, this.modules.completion, "ex");
424 utils.assert("dactyl.runCompletions", context.wait(5000),
425 "Completion failed: " + cmd.quote());
427 for (var [, ctxt] in Iterator(context.contextList))
428 for (var [, item] in Iterator(ctxt.items))
429 ctxt.createRow(item);
436 utils.assertEqual("dactyl.assertCommandLineFocused",
437 this.elements.commandInput,
438 this.elements.focused,
439 "Running Ex Completion: The command line is not focused");
442 let input = this.elements.commandInput;
445 var event = input.ownerDocument.createEvent("Events");
446 event.initEvent("change", true, false);
447 input.dispatchEvent(event);
450 this.controller.type(null, cmd);
452 utils.assertEqual("dactyl.runExCompletion", cmd,
453 this.elements.commandInput.editor.rootElement.firstChild.textContent,
454 "Command line does not have the expected value: " + cmd);
457 this.controller.keypress(null, "VK_TAB", {});
460 if (this.modules.commandline._tabTimer)
461 this.modules.commandline._tabTimer.flush();
462 else if (this.modules.commandline.commandSession && this.modules.commandline.commandSession.completions)
463 this.modules.commandline.commandSession.completions.tabTimer.flush();
468 * Returns the text content of the output message line.
470 * @returns {string} The message line text content.
472 readMessageLine: function () {
473 return this.elements.message.value;
477 * Returns the text content of the output message window.
479 * @returns {string} The message window text content.
481 readMessageWindow: function () {
482 if (!this.elements.multilineContainer.collapsed)
483 return this.elements.multiline.contentDocument.body.textContent;
488 * Opens the output message window by echoing a single newline character.
490 openMessageWindow: wrapAssertNoErrors(function () {
491 this.modules.dactyl.echo("\n");
492 }, "Opening message window"),
495 * Clears the current message.
497 clearMessage: function () {
498 this.elements.message.value = ""; // XXX
502 * Closes the output message window if open.
504 closeMessageWindow: wrapAssertNoErrors(function () {
505 for (let i = 0; i < 15 && !this.elements.multilineContainer.collapsed; i++)
506 this.controller.keypress(null, "VK_ESCAPE", {});
507 this.assertMessageWindowOpen(false, "Clearing message window failed");
508 }, "Clearing message window"),
511 * @property {string} The specific Dactyl application. Eg. Pentadactyl
513 get applicationName() this.modules.config.appName // XXX
516 // vim: sw=4 ts=8 et ft=javascript: