]> git.donarmstrong.com Git - dactyl.git/blob - common/tests/functional/testCommands.js
59724b559a9c54c898fea7c9d76281e34b6bfebd
[dactyl.git] / common / tests / functional / testCommands.js
1 // Runs a slew of generic command tests
2
3 var utils = require("utils");
4 const { module } = utils;
5 var dactyllib = module("dactyl");
6 var jumlib = module("resource://mozmill/modules/jum.js");
7
8 var setupModule = function (module) {
9     controller = mozmill.getBrowserController();
10     dactyl = new dactyllib.Controller(controller);
11
12     dactyl.modules.options["autocomplete"] = [];
13     dactyl.modules.options["wildmode"] = ["list"];
14
15     dactyl.modules.prefs.set("browser.tabs.closeWindowWithLastTab", false);
16     dactyl.elements.multilineContainer.setAttribute("moz-collapsed", "true");
17 };
18 var teardownModule = function (module) {
19     dactyl.elements.multilineContainer.removeAttribute("moz-collapsed");
20     dactyl.teardown();
21 }
22
23 function $(selector) controller.window.document.querySelector(selector);
24
25 function hasNItems(nItems)
26     function hasNItems(context) {
27         utils.assertEqual("testCommand.hasNItems", nItems, context.allItems.items.length);
28     };
29
30 function hasItems(context) context.allItems.items.length;
31
32 function hasntNullItems(context) hasItems(context) &&
33     !context.allItems.items.some(function ({ text, description }) [text, description].some(function (text) /^\[object/.test(text)));
34
35 function sidebarState(state)
36     function sidebarState() {
37         utils.assertEqual("testCommand.sidebarState", state,
38                           typeof state == "string" ? $("#sidebar-title").value
39                                                    : !$("#sidebar-box").hidden);
40     };
41 function toolbarState(selector, state)
42     function toolbarState() {
43         utils.assertEqual("testCommand.toolbarState", state, !$(selector).collapsed)
44     };
45
46 var tests = {
47     "!": {
48         multiOutput: ["echo foo"]
49     },
50     abbreviate: {
51         someOutput: ["", "abc"],
52         noOutput: ["abc def", "-js abc def"],
53         completions: ["", "abc ", "-js abc "]
54     },
55     addons: {
56         multiOutput: ["", "dactyl", "-type=extension", "-type=extension dactyl"],
57         completions: [
58             "",
59             ["-types=", hasItems]
60         ]
61     },
62     autocmd: {
63         multiOutput: ["", "DOMLoad", "DOMLoad foo"],
64         noOutput: ["DOMLoad foo bar", "-js DOMLoad foo bar"],
65         completions: [
66             ["", hasntNullItems],
67             "DOMLoad foo ",
68             "-js DOMLoad foo "
69         ]
70     },
71     back: { noOutput: [""] },
72     bdelete: {
73         init: ["tabopen about:pentadactyl", "tabopen about:pentadactyl"],
74         noOutput: [""],
75         anyOutput: ["about:pentadactyl"],
76         completions: [["", hasItems]]
77     },
78     bmark: {
79         singleOutput: ["", "-tags=foo -title=bar -keyword=baz -charset=UTF-8 -post=quux about:pentadactyl"],
80         error: ["-tags=foo -title=bar -keyword=baz -charset=nonExistentCharset -post=quux about:pentadactyl"],
81         completions: [
82             "-max=1 -keyword=",
83             "-max=1 -keyword=foo -tags=",
84             "-max=1 -keyword=foo -tags=bar -title=",
85             ["-max=1 -keyword=foo -tags=bar -title=baz -charset=", hasItems],
86             "-max=1 -keyword=foo -tags=bar -title=baz -charset= about:"
87         ]
88     },
89     bmarks: {
90         multiOutput: ["-max=1", "-max=1 -keyword=foo -tags=bar -title=baz about:pentadactyl"],
91         completions: [
92             "-max=1 -keyword=",
93             "-max=1 -keyword=foo -tags=",
94             "-max=1 -keyword=foo -tags=bar -title=",
95             "-max=1 -keyword=foo -tags=bar -title=baz about:"
96         ]
97     },
98     buffer: {
99         anyOutput: ["", "1"],
100         noOutput: ["!", "! 1"],
101         completions: [
102             ["", hasItems],
103             ["1", hasItems]
104         ]
105     },
106     buffers: {
107         multiOutput: ["", "1"],
108         completions: ["", "1"]
109     },
110     cd: {
111         singleOutput: ["", "~/"],
112         completions: ["", "~/"]
113     },
114     colorscheme: {
115         error: ["", "some-nonexistent-scheme"]
116     },
117     command: {
118         init: ["delc!"],
119         singleOutput: ["", "foobar"],
120         noOutput: ["foo bar", "-js bar baz"],
121         multiOutput: [""],
122         error: [
123             "foo bar",
124             "-js bar baz",
125             "-group=builtin baz quux",
126             "! -group=builtin baz quux",
127         ],
128         completions: [
129             ["", hasItems],
130             ["-group=", hasItems],
131             ["-group=user ", hasItems]
132         ]
133     },
134     contexts: {}, // Not testable in this manner
135     cookies: {
136         anyOutput: ["dactyl.sf.net", "dactyl.sf.net list"],
137         error: [""],
138         completions: [
139             "",
140             ["dactyl.sf.net ", hasItems]
141         ]
142     },
143     delbmarks: { anyOutput: ["", "about:pentadactyl"] },
144     delcommand: [
145         {
146             init: ["delcommand!", "command foo bar"],
147             error: [""],
148             completions: [
149                 ["", hasItems],
150                 ["-group=", hasItems],
151                 ["-group=user ", hasItems]
152             ],
153             noOutput: ["foo", "! "]
154         },
155         {
156             init: ["delcommand!"],
157             error: ["foo"]
158         }
159     ],
160     delmacros: {
161         error: [""],
162         noOutput: ["x"],
163         completions: ["", "x"]
164     },
165     get delmarks() this.delmacros,
166     get delqmarks() this.delmacros,
167     delstyle: {
168         completions: ["", "-name=", "-name=foo ", "-index=", "-index="]
169     },
170     dialog: {
171         // Skip implementation for now
172         completions: [
173             ["", hasntNullItems]
174         ]
175     },
176     doautoall: {}, // Skip for now
177     doautocmd: {}, // Skip for now
178     downloads: {
179         multiOutput: ["", "dactyl", "dactyl"]
180     },
181     echo: {
182         singleOutput: [
183             ["' - '", " - "]
184         ],
185         multiOutput: [
186             ["'\\n'", /\n/],
187             ["window", /\[object\sChromeWindow\]/]
188         ],
189         completions: [
190             "",
191             "window",
192             "window.",
193             "window['",
194             "commands.get('"
195         ]
196     },
197     get echoerr() ({
198         errorsOk: true,
199         __proto__: this.echo,
200     }),
201     get echomsg() this.echo,
202     else: {}, // Skip for now
203     elseif: {}, // Skip for now
204     emenu: {
205         noOutput: ["View.Zoom.Zoom In", "View.Zoom.Zoom Out"],
206         error: [""],
207         completions: [
208             ["", hasItems],
209             ["View.", hasItems]
210         ]
211     },
212     endif: {}, // Skip for now
213     execute: {
214         noOutput: ["", "'js " + "".quote() + "'"],
215         someOutput: ["'ls'"],
216         completions: [["", hasItems]]
217     },
218     extadd: {
219         completions: [["", hasItems]],
220         error: [""]
221     },
222     extdelete: {
223         completions: [["", hasItems]],
224         error: [""]
225     },
226     get extdisable() this.extdelete,
227     extenable: {
228         completions: [""],
229         error: [""]
230     },
231     extoptions: {
232         completions: [""],
233         error: [""]
234     },
235     get extrehash() this.extdelete,
236     get exttoggle() this.extdelete,
237     get extupdate() this.extdelete,
238     feedkeys: {
239         noOutput: ["<Esc>"],
240         error: [""]
241     },
242     finish: { noOutput: [""] },
243     forward: { noOutput: [""] },
244     frameonly: { noOutput: [""] },
245     delgroup: {
246         error: ["builtin"],
247         completions: [""]
248     },
249     group: {
250         multiOutput: [""],
251         noOutput: [
252             "foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'",
253             "! foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'",
254             "foo",
255             "user"
256         ],
257         error: ["builtin"],
258         completions: [
259             "",
260             "foo "
261         ],
262         cleanup: ["delmapgroup foo"]
263     },
264     hardcopy: {}, // Skip for now
265     help: {
266         noOutput: ["", "intro"],
267         cleanup: ["tabdelete", "tabdelete"],
268         completions: [
269             ["", hasItems],
270             ["'wild", hasItems]
271         ]
272     },
273     get helpall() this.help,
274     highlight: {
275         multiOutput: ["", "Help"],
276         noOutput: [
277             "Help foo: bar;",
278             "Help -group=FontCode",
279             "Help -group=FontCode foo: bar;"
280         ],
281         completions: [
282             ["", hasItems],
283             ["Help", hasItems],
284             ["Help ", hasItems],
285             ["Help -group=", hasItems],
286             ["Help -group=FontCode ", hasItems],
287             ["Help foo: bar; -moz", hasItems]
288         ]
289     },
290     history: {
291         init: ["open about:pentadactyl"],
292         anyOutput: ["-max=1", "-max=1 -sort=+date", "-max=1 dactyl"],
293         completions: [
294             ["", hasItems],
295             "about:",
296             ["-sort=+", hasItems],
297             ["-sort=-", hasItems],
298             ["-sort=+date ", hasItems],
299             "-sort=+date about:"
300         ]
301     },
302     if: {}, // Skip for now
303     javascript: {
304         noOutput: ["''", "'\\n'", "<pre>foo bar</pre>", "window"],
305         completions: [
306             ["", hasItems],
307             ["window", hasItems],
308             ["window.", hasItems],
309             ["window['", hasItems],
310             ["File('", hasItems],
311             ["File.expandPath('", hasItems],
312             "autocommands.user.get('",
313             ["commands.get('", hasItems],
314             ["commands.builtin.get('", hasItems],
315             ["highlight.get('", hasItems],
316             ["highlight.highlightNode(null, '", hasItems],
317             ["mappings.get(modes.NORMAL, '", hasItems],
318             // ["mappings.builtin.get(modes.NORMAL, '", hasItems],
319             ["options.get('", hasItems],
320             ["prefs.get('", hasItems],
321             ["prefs.defaults.get('", hasItems],
322             ["localPrefs.get('", hasItems],
323             ["localPrefs.defaults.get('", hasItems],
324             ["styles.system.get('", hasItems],
325         ]
326     },
327     jumps: {
328         multiOutput: [""]
329     },
330     keepalt: {
331         error: [""],
332         noOutput: ["js ''"],
333         anyOutput: ["echo 'foo'"]
334     },
335     let: {}, // Deprecated. Fuck it.
336     listcommands: {
337         anyOutput: ["", "in"],
338         completions: [
339             ["", hasItems],
340             "in "
341         ]
342     },
343     get listkeys() this.listcommands,
344     get listoptions() this.listcommands,
345     loadplugins: {},
346     macros: {
347         multiOutput: [""],
348         completions: [""]
349     },
350     map: {
351         init: ["unmap!"],
352         anyOutput: [""],
353         singleOutput: ["i"],
354         noOutput: [
355             "i j",
356             "-builtin i j",
357             "-group=user -b i j",
358             "-js i j()",
359             "-ex i :j",
360             "-silent i :j",
361             "-mode=ex -b <C-a> <C-a>"
362         ],
363         multiOutput: ["", "i"],
364         error: [
365             "-mode=some-nonexistent-mode <C-a> <C-a>",
366             "-group=some-nonexistent-group <C-a> <C-a>",
367             "-group=builtin <C-a> <C-a>"
368         ],
369         completions: [
370             ["", hasItems],
371             ["-", hasItems],
372             ["-mode=ex ", hasItems],
373             ["-mode=", hasItems],
374             ["-group=", hasItems],
375             ["-builtin i ", hasItems],
376             ["-ex i ", hasItems],
377             ["-javascript i ", hasItems]
378         ]
379     },
380     mark: {
381         error: ["", "#", "xy"],
382         noOutput: ["y"],
383         completions: [""]
384     },
385     marks: {
386         init: ["delmarks q"],
387         multiOutput: ["", "y"],
388         error: ["q", "#"],
389         completions: [""]
390     },
391     messages: {
392         anyOutput: ["messages"]
393     },
394     messclear: {
395         error: ["q"],
396         noOutput: [""]
397     },
398     mkpentadactylrc: {
399         noOutput: [
400             "some-nonexistent-rc.penta",
401             "! some-nonexistent-rc.penta"
402         ],
403         error: ["some-nonexistent-rc.penta"],
404         completions: [""],
405         cleanup: ["silent !rm some-nonexistent-rc.penta"]
406     },
407     mksyntax: {
408         noOutput: [
409             "some-nonexistent-pentadactyl-dir/",
410             "! some-nonexistent-pentadactyl-dir/",
411             "some-nonexistent-pentadactyl-dir/foo.vim",
412             "! some-nonexistent-pentadactyl-dir/foo.vim",
413         ],
414         error: [
415             "some-nonexistent-pentadactyl-dir/",
416             "some-nonexistent-pentadactyl-dir/foo.vim"
417         ],
418         completions: [
419             ["", hasItems]
420         ],
421         cleanup: ["silent !rm -r some-nonexistent-pentadactyl-dir/"]
422     },
423     normal: {
424         noOutput: ["<Nop>"],
425         singleOutput: ["<C-g>"],
426         multiOutput: ["g<C-g>"]
427     },
428     open: {
429         noOutput: ["about:blank | about:home"],
430         completions: [
431             ["", hasItems],
432             ["./", hasItems],
433             ["./ | ", hasItems], // FIXME: broken feature
434             ["chrome://", hasItems],
435             ["chrome://browser/", hasItems],
436             ["chrome://browser/content/", hasItems],
437             ["about:", hasItems],
438             ["resource://", hasItems],
439             ["resource://dactyl/", hasItems]
440         ]
441     },
442     pageinfo: {
443         multiOutput: ["", "fgm"],
444         completions: [["", hasItems]],
445         error: ["abcdefghijklmnopqrstuvwxyz", "f g m"]
446     },
447     pagestyle: {
448         completions: [""]
449     },
450     preferences: {}, // Skip for now
451     pwd: {
452         singleOutput: [""]
453     },
454     qmark: {
455         singleOutput: [
456             "m",
457             "m foo bar"
458         ],
459         error: ["", "#"],
460         completions: [
461             ["", hasItems],
462             ["m ", hasItems]
463         ]
464     },
465     qmarks: [
466         {
467             init: ["delqmarks a-zA-Z0-9"],
468             error: ["", "x"],
469         },
470         {
471             init: ["qmark x"],
472             multiOutput: ["", "m", "x"],
473             completions: [["", hasItems]]
474         }
475     ],
476     quit: {}, // Skip for now
477     quitall: {}, // Skip for now
478     redraw: {
479         noOutput: [""]
480     },
481     rehash: {}, // Skip for now
482     reload: {
483         noOutput: [""]
484     },
485     reloadall: {
486         noOutput: [""]
487     },
488     restart: {}, // Skip
489     runtime: {
490         init: [
491             "js File('~/.pentadactyl/some-nonexistent/good.css').write('')",
492             "js File('~/.pentadactyl/some-nonexistent/good.js').write('')",
493             "js File('~/.pentadactyl/some-nonexistent/bad.js').write('dactyl.echoerr(\"error\")')",
494             "js File('~/.pentadactyl/some-nonexistent/good.penta').write('')",
495             "js File('~/.pentadactyl/some-nonexistent/bad.penta').write('echoerr \"error\"')",
496         ],
497         cleanup: ["js File('~/.pentadactyl/some-nonexistent').remove(true)"],
498         noOutput: [
499             "some-nonexistent/good.css",
500             "some-nonexistent/good.js",
501             "some-nonexistent/good.penta"
502         ],
503         error: [
504             "some-nonexistent/bad.js",
505             "some-nonexistent/bad.penta"
506         ],
507         singleOutput: ["some-nonexistent-file.js"],
508         completions: [
509             ["", hasItems],
510             ["some-nonexistent/", hasItems],
511             ["info/", hasItems]
512         ]
513     },
514     sanitize: {
515         // Skip details for now.
516         completions: [
517             ["", function (context) ["all",
518                                      "cache",
519                                      "downloads",
520                                      "formdata",
521                                      "offlineapps",
522                                      "passwords",
523                                      "sessions",
524                                      "cookies",
525                                      "history",
526                                      "host",
527                                      "sitesettings",
528                                      "commandline",
529                                      "messages",
530                                      "macros",
531                                      "marks",
532                                      "options"
533                 ].every(function (item) context.allItems.items.some(function ({ text }) item == text))
534             ],
535             "-",
536             "-host=",
537             "-timespan="
538         ]
539     },
540     saveas: {},
541     sbclose: {
542         noOutput: [""]
543     },
544     scriptnames: {},
545     set: {
546         multiOutput: [
547             "vb?", "cpt?", "messages?", "titlestring?", "au?", "eht?",
548             "cpt", "messages", "titlestring", "au", "eht"
549         ],
550         noOutput: ["vb", "novb"],
551         completions: [
552             ["", hasItems],
553             ["c", hasItems],
554             ["cpt=", hasItems],
555             ["cpt=l", hasItems],
556             ["cpt+=", hasItems],
557             ["cpt+=f", hasItems],
558             ["activate=", hasItems],
559             ["activate=links,", hasItems],
560             ["activate+=", hasItems],
561             ["activate+=links,", hasItems],
562             ["activate^=", hasItems],
563             ["activate^=links,", hasItems],
564             ["activate-=", hasItems],
565             ["activate-=links,", hasItems],
566             ["activate!=", hasItems],
567             ["activate!=links,", hasItems]
568         ]
569     },
570     get setglobal() this.set,
571     get setlocal() this.set,
572     sidebar: {
573         error: ["!", ""],
574         test: function (name) [
575             ["! " + name, sidebarState(name)],
576             [name, sidebarState(name)],
577             ["! " + name, sidebarState(false)]
578         ],
579         get noOutput()
580             Array.concat.apply([],
581                 ["Add-ons", // Final "! Add-ons" currently failing
582                  "Bookmarks",
583                  "Downloads",
584                  "Console",
585                  "History",
586                  "Preferences"]
587             .map(this.test))
588             .concat([
589                 ["Preferences", sidebarState("Preferences")],
590                 ["!", sidebarState(false)]
591             ]),
592         completions: [
593             ["", hasntNullItems],
594             "! "
595         ]
596     },
597     silent: {
598         noOutput: [
599             "echo 'foo'",
600             "echo " + "foo\nbar".quote(),
601             "echoerr 'foo'",
602             "echoerr " + "foo\nbar".quote()
603         ],
604         completions: [["", hasItems]]
605     },
606     get source() ({
607         init: this.runtime.init,
608         cleanup: this.runtime.cleanup,
609         noOutput: [
610             "! .pentadactyl/some-nonexistent/really-nonexistent.js",
611             ".pentadactyl/some-nonexistent/good.css",
612             ".pentadactyl/some-nonexistent/good.js",
613             ".pentadactyl/some-nonexistent/good.penta"
614         ],
615         error: [
616             ".pentadactyl/some-nonexistent/really-nonexistent.js",
617             "~/.pentadactyl/some-nonexistent/bad.js",
618             "~/.pentadactyl/some-nonexistent/bad.penta",
619             "./.pentadactyl/some-nonexistent/bad.js",
620             "./.pentadactyl/some-nonexistent/bad.penta",
621             ".pentadactyl/some-nonexistent/bad.js",
622             ".pentadactyl/some-nonexistent/bad.penta",
623             ".pentadactyl/some-nonexistent-file.js"
624         ],
625         completions: [
626             ["", hasItems],
627             [".pentadactyl/some-nonexistent/", hasItems],
628             ["chrome://browser/content/", hasItems],
629             ["resource://dactyl/", hasItems]
630         ]
631     }),
632     stop: { noOutput: [""] },
633     stopall: { noOutput: [""] },
634     style: {
635         cleanup: ["delstyle -n foo"],
636         noOutput: [
637             "-name=foo http://does.not.exist/* div { display: inline; }",
638             "-name=foo -append http://does.not.exist/* span { display: block; }"
639         ],
640         multiOutput: [
641             "",
642             "-name=foo"
643         ],
644         completions: [
645             ["", hasItems],
646             ["-name=", hasItems],
647             ["http:* div { -moz", hasItems],
648             ["http:* div { foo: bar; -moz", hasItems],
649             ["http:* div { foo: bar; } span { -moz", hasItems],
650             ["http:* div { foo: bar; } span { foo: bar; -moz", hasItems]
651         ]
652     },
653     styledisable: {
654         init: ["style -n foo http:* div {}", "style -n bar ftp:* div", "styledisable -n bar"],
655         cleanup: ["delstyle -n foo", "delstyle -n bar"],
656         completions: [
657             ["", hasItems],
658             ["-name=", hasNItems(1)],
659             ["-index=", hasNItems(1)]
660         ],
661         noOutput: ["-name=foo", "-name=bar"]
662     },
663     get styleenable() this.styledisable,
664     styletoggle: {
665         init: ["style -n foo http:* div {}", "style -n bar ftp:* div", "styledisable -n bar"],
666         cleanup: ["delstyle -n foo", "delstyle -n bar"],
667         noOutput: ["-name=foo"],
668         completions: [
669             ["", hasItems],
670             ["-name=", hasNItems(2)],
671             ["-index=", hasNItems(2)]
672         ]
673     },
674     tab: {},
675     tabattach: {},
676     tabdetach: {},
677     tabdo: {},
678     tabduplicate: {},
679     tablast: {},
680     tabmove: {},
681     tabnext: {},
682     tabonly: {},
683     tabopen: {},
684     tabprevious: {},
685     tabrewind: {},
686     time: {},
687     toolbarhide: {
688         init: [
689             ["tbs Navigation Toolbar", toolbarState("#nav-bar", true)],
690             ["tbs Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)]
691         ],
692         completions: [["", hasItems]],
693         noOutput: [
694             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
695             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
696         ],
697         error: ["", "foo"]
698     },
699     toolbarshow: {
700         completions: [["", hasItems]],
701         noOutput: [
702             ["Navigation Toolbar", toolbarState("#nav-bar", true)],
703             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)]
704         ],
705         error: ["", "foo"]
706     },
707     toolbartoggle: {
708         completions: [["", hasItems]],
709         noOutput: [
710             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
711             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)],
712             ["Navigation Toolbar", toolbarState("#nav-bar", true)],
713             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)],
714             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
715             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
716         ],
717         error: ["", "foo"]
718     },
719     unabbreviate: {
720         noOutput: ["abc", "! "],
721         error: [""]
722     },
723     undo: {},
724     undoall: {},
725     unlet: {},
726     unmap: {
727         noOutput: [
728             "i",
729             "! "
730         ],
731         error: [
732             "i",
733             "-group=builtin k",
734             "! -group=builtin"
735         ],
736         completions: [
737             "",
738             "-group="
739         ]
740     },
741     verbose: {},
742     version: {
743         multiOutput: [
744             ["", function (msg) {
745                 var res = /(\w+dactyl) (\S+) \(([\^)]+)\) running on:\nMozilla/;
746                 return res && res[2] != "null" && res[3] != "null";
747             }]
748         ]
749     },
750     viewsource: {},
751     winclose: {},
752     window: {},
753     winonly: {},
754     winopen: {},
755     wqall: {},
756     yank: {
757         multiOutput: [
758             ["foo".quote(), /foo/],
759             [":echo " + "bar".quote(), /bar/],
760             [":addons", /Pentadactyl/]
761         ],
762         error: [
763             ":echoerr " + "foo".quote()
764         ],
765         completions: [
766             ["", hasItems],
767             [":", hasItems]
768         ]
769     },
770     zoom: {}
771 };
772
773 var global = this;
774 function addTest(cmdName, testName, func) {
775     global["testCommand_" + cmdName + "_" + testName] = func;
776 }
777
778 function runCommands(cmdName, testName, commands, test, forbidErrors) {
779     addTest(cmdName, testName, function () {
780         commands.forEach(function (val) {
781             var [cmd, testVal] = Array.concat(val);
782
783             dump("CMD: " + testName + " " + cmdName + " " + cmd + "\n");
784             dactyl.clearMessage();
785             dactyl.closeMessageWindow();
786
787             cmd = cmdName + cmd.replace(/^(!?) ?/, "$1 ");
788             if (forbidErrors)
789                 dactyl.assertNoErrorMessages(function () { dactyl.runExCommand(cmd) },
790                                              null, [], cmd);
791             else
792                 dactyl.runExCommand(cmd);
793             controller.waitForPageLoad(controller.tabs.activeTab);
794
795             test(cmd, testVal);
796         });
797     });
798 }
799 function _runCommands(cmdName, testName, commands) {
800     addTest(cmdName, testName, function () {
801         commands.forEach(function (value) {
802             var [cmd, test] = Array.concat(value);
803
804             dump("CMD: " + testName + " " + cmdName + " " + cmd + "\n");
805             var res = dactyl.runExCommand(cmd);
806             controller.waitForPageLoad(controller.tabs.activeTab);
807             runTest("Initializing for " + cmdName + " tests failed: " + cmd.quote() + " " + test,
808                     test);
809         });
810     });
811 }
812
813 function runTest(message, test) {
814     if (test)
815         var res = test.apply(null, Array.slice(arguments, runTest.length));
816     if (res !== undefined)
817         jumlib.assert(res, message);
818 }
819
820 for (var val in Iterator(tests)) (function ([command, paramsList]) {
821     Array.concat(paramsList).forEach(function (params, i) {
822         if (params.init)
823             _runCommands(command, "init" + (i || ""), params.init);
824
825         // Goddamn stupid fucking MozMill and its stupid fucking sandboxes with their ancient fucking JS versions.
826         for (var val in Iterator(params)) (function ([test, commands]) {
827             var testName = test + (i || "");
828
829             switch (test) {
830             case "noOutput":
831                 runCommands(command, testName, commands, function (cmd, test) {
832                     var res = dactyl.assertMessage(function (msg) !msg, "Unexpected command output: " + cmd);
833                     if (res && test)
834                         dactyl.assertMessage(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
835                 });
836                 break;
837             case "anyOutput":
838                 runCommands(command, testName, commands, function (cmd, test) {
839                     if (test)
840                         dactyl.assertMessage(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
841                 });
842                 break;
843             case "someOutput":
844                 runCommands(command, testName, commands, function (cmd, test) {
845                     var res = dactyl.assertMessage(/./, "Expected command output: " + cmd);
846                     if (res && test != null)
847                         dactyl.assertMessage(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
848                 });
849                 break;
850             case "singleOutput":
851                 runCommands(command, testName, commands, function (cmd, test) {
852                     var res = dactyl.assertMessageLine(/./, "Expected command output: " + cmd);
853                     if (res && test != null)
854                         dactyl.assertMessageLine(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
855                 }, !params.errorsOk);
856                 break;
857             case "multiOutput":
858                 runCommands(command, testName, commands, function (cmd, test) {
859                     var res = dactyl.assertMessageWindowOpen(true, "Expected command output: " + cmd);
860                     if (res && test != null)
861                         dactyl.assertMessageWindow(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
862                 }, !params.errorsOk);
863                 break;
864             case "error":
865                 addTest(command, testName, function () {
866                     commands.forEach(function (val) {
867                         var [cmd, test] = Array.concat(val);
868                         cmd = command + cmd.replace(/^(!?) ?/, "$1 ");
869
870                         var res = dactyl.assertMessageError(function () {
871                             dactyl.runExCommand(cmd);
872                             controller.waitForPageLoad(controller.tabs.activeTab);
873                         }, null, [], cmd);
874
875                         if (res && test != null)
876                             dactyl.assertMessage(test, "Running " + testName + " tests failed: " + cmd.quote() + " " + test.toSource());
877                     });
878                 });
879                 break;
880             case "completions":
881                 addTest(command, testName, function () {
882                     commands.forEach(function (val) {
883                         var [cmd, test] = Array.concat(val);
884                         cmd = command + cmd.replace(/^(!?) ?/, "$1 ");
885
886                         dactyl.assertNoErrorMessages(function () {
887                             dump("COMPL: " + cmd + "\n");
888                             var context = dactyl.runExCompletion(cmd);
889                             if (context)
890                                 runTest("Completion tests failed: " + cmd.quote() + " " + test,
891                                         test, context);
892                         });
893                     });
894                 });
895                 break;
896             }
897         })(val);
898
899         if (params.cleanup)
900             _runCommands(command, "cleanup" + (i || ""), params.cleanup);
901     });
902 })(val);
903
904 // vim: sw=4 ts=8 et: