Bug 775031 - GCLI should display [options] rather than either hiding them or showing them all; r=harth
authorJoe Walker <jwalker@mozilla.com>
Fri, 24 Aug 2012 11:05:07 +0100
changeset 105419 7bf846af58d35674c1b02f8dbfa260f6012a78d5
parent 105418 6b00d1edb2e717b0d658dc3408fae6f38add9b02
child 105420 35e5ee61e193771a906a39c7d21e66651cf03ae0
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersharth
bugs775031
milestone17.0a1
Bug 775031 - GCLI should display [options] rather than either hiding them or showing them all; r=harth
browser/devtools/commandline/gcli.jsm
browser/devtools/commandline/test/browser_cmd_cookie.js
browser/devtools/commandline/test/browser_gcli_web.js
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -2334,16 +2334,17 @@ function Command(commandSpec) {
 
   if (this.params == null) {
     this.params = [];
   }
   if (!Array.isArray(this.params)) {
     throw new Error('command.params must be an array in ' + this.name);
   }
 
+  this.hasNamedParameters = false;
   this.description = 'description' in this ? this.description : undefined;
   this.description = lookup(this.description, 'canonDescNone');
   this.manual = 'manual' in this ? this.manual : undefined;
   this.manual = lookup(this.manual);
 
   // At this point this.params has nested param groups. We want to flatten it
   // out and replace the param object literals with Parameter objects
   var paramSpecs = this.params;
@@ -2368,22 +2369,30 @@ function Command(commandSpec) {
     if (!spec.group) {
       if (usingGroups) {
         console.error('Parameters can\'t come after param groups.' +
             ' Ignoring ' + this.name + '/' + spec.name);
       }
       else {
         var param = new Parameter(spec, this, null);
         this.params.push(param);
+
+        if (!param.isPositionalAllowed) {
+          this.hasNamedParameters = true;
+        }
       }
     }
     else {
       spec.params.forEach(function(ispec) {
         var param = new Parameter(ispec, this, spec.group);
         this.params.push(param);
+
+        if (!param.isPositionalAllowed) {
+          this.hasNamedParameters = true;
+        }
       }, this);
 
       usingGroups = true;
     }
   }, this);
 }
 
 canon.Command = Command;
@@ -9815,16 +9824,19 @@ Completer.prototype._getCompleterTemplat
   // We generate an array of emptyParameter markers for each positional
   // parameter to the current command.
   // Generally each emptyParameter marker begins with a space to separate it
   // from whatever came before, unless what comes before ends in a space.
   // Also if we've got a directTabText prediction or we're in a NamedParameter
   // then we don't want any text for that parameter at all.
   // The algorithm to add spaces needs to take this into account.
 
+  var command = this.requisition.commandAssignment.value;
+  var jsCommand = command && command.name === '{';
+
   var firstBlankParam = true;
   var emptyParameters = [];
   this.requisition.getAssignments().forEach(function(assignment) {
     if (!assignment.param.isPositionalAllowed) {
       return;
     }
     if (current.arg.type === 'NamedArgument') {
       return;
@@ -9851,18 +9863,33 @@ Completer.prototype._getCompleterTemplat
     if (!trailingSeparator || !firstBlankParam) {
       text = '\u00a0' + text; // i.e. &nbsp;
     }
 
     firstBlankParam = false;
     emptyParameters.push(text);
   }.bind(this));
 
-  var command = this.requisition.commandAssignment.value;
-  var jsCommand = command && command.name === '{';
+  var optionsRemaining = false;
+  if (command && command.hasNamedParameters) {
+    command.params.forEach(function(param) {
+      var arg = this.requisition.getAssignment(param.name).arg;
+      if (!param.isPositionalAllowed && !param.hidden
+              && arg.type === "BlankArgument") {
+        optionsRemaining = true;
+      }
+    }, this);
+  }
+
+  if (optionsRemaining) {
+    // Add an nbsp if we don't have one at the end of the input or if
+    // this isn't the first param we've mentioned
+    var prefix = (!trailingSeparator || !firstBlankParam) ?  '\u00a0' : '';
+    emptyParameters.push(prefix + '[options]');
+  }
 
   // Is the entered command a JS command with no closing '}'?
   // TWEAK: This code should be considered for promotion to Requisition
   var unclosedJs = jsCommand &&
       this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
 
   // The text for the 'jump to scratchpad' feature, or '' if it is disabled
   var link = this.scratchpad && jsCommand ? this.scratchpad.linkText : '';
--- a/browser/devtools/commandline/test/browser_cmd_cookie.js
+++ b/browser/devtools/commandline/test/browser_cmd_cookie.js
@@ -32,17 +32,17 @@ function testCookieCommands() {
     typed: "cookie remove",
     status: "ERROR",
     emptyParameters: [ " <key>" ]
   });
 
   DeveloperToolbarTest.checkInputStatus({
     typed: "cookie set",
     status: "ERROR",
-    emptyParameters: [ " <key>", " <value>" ],
+    emptyParameters: [ " <key>", " <value>", " [options]" ],
   });
 
   DeveloperToolbarTest.exec({
     typed: "cookie set fruit banana",
     args: {
       key: "fruit",
       value: "banana",
       path: "/",
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -189,17 +189,17 @@ define('gclitest/index', ['require', 'ex
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIncomplete', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
+define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIncomplete', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testMenu', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
 
   // We need to make sure GCLI is initialized before we begin testing it
   require('gcli/index');
 
   var examiner = require('test/examiner');
 
   // It's tempting to want to unify these strings and make addSuite() do the
   // call to require(), however that breaks the build system which looks for
@@ -210,16 +210,17 @@ define('gclitest/suite', ['require', 'ex
   examiner.addSuite('gclitest/testExec', require('gclitest/testExec'));
   examiner.addSuite('gclitest/testHelp', require('gclitest/testHelp'));
   examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
   examiner.addSuite('gclitest/testInputter', require('gclitest/testInputter'));
   examiner.addSuite('gclitest/testIncomplete', require('gclitest/testIncomplete'));
   examiner.addSuite('gclitest/testIntro', require('gclitest/testIntro'));
   examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
   examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard'));
+  examiner.addSuite('gclitest/testMenu', require('gclitest/testMenu'));
   examiner.addSuite('gclitest/testPref', require('gclitest/testPref'));
   examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire'));
   examiner.addSuite('gclitest/testResource', require('gclitest/testResource'));
   examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
   examiner.addSuite('gclitest/testSettings', require('gclitest/testSettings'));
   examiner.addSuite('gclitest/testSpell', require('gclitest/testSpell'));
   examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit'));
   examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize'));
@@ -1730,16 +1731,17 @@ exports.setup = function() {
   canon.addCommand(exports.tsnDeepDown);
   canon.addCommand(exports.tsnDeepDownNested);
   canon.addCommand(exports.tsnDeepDownNestedCmd);
   canon.addCommand(exports.tselarr);
   canon.addCommand(exports.tsm);
   canon.addCommand(exports.tsg);
   canon.addCommand(exports.tshidden);
   canon.addCommand(exports.tscook);
+  canon.addCommand(exports.tslong);
 };
 
 exports.shutdown = function() {
   canon.removeCommand(exports.tsv);
   canon.removeCommand(exports.tsr);
   canon.removeCommand(exports.tse);
   canon.removeCommand(exports.tsj);
   canon.removeCommand(exports.tsb);
@@ -1755,16 +1757,17 @@ exports.shutdown = function() {
   canon.removeCommand(exports.tsnDeepDown);
   canon.removeCommand(exports.tsnDeepDownNested);
   canon.removeCommand(exports.tsnDeepDownNestedCmd);
   canon.removeCommand(exports.tselarr);
   canon.removeCommand(exports.tsm);
   canon.removeCommand(exports.tsg);
   canon.removeCommand(exports.tshidden);
   canon.removeCommand(exports.tscook);
+  canon.removeCommand(exports.tslong);
 
   types.deregisterType(exports.optionType);
   types.deregisterType(exports.optionValue);
 };
 
 
 exports.option1 = { type: types.getType('string') };
 exports.option2 = { type: types.getType('number') };
@@ -2046,16 +2049,88 @@ exports.tscook = {
           description: 'tscookSecureDesc'
         }
       ]
     }
   ],
   exec: createExec('tscook')
 };
 
+exports.tslong = {
+  name: 'tslong',
+  description: 'long param tests to catch problems with the jsb command',
+  returnValue:'string',
+  params: [
+    {
+      name: 'url',
+      type: 'string',
+      description: 'tslongUrlDesc'
+    },
+    {
+      group: "tslongOptionsDesc",
+      params: [
+        {
+          name: 'indentSize',
+          type: 'number',
+          description: 'tslongIndentSizeDesc',
+          defaultValue: 2
+        },
+        {
+          name: 'indentChar',
+          type: {
+            name: 'selection',
+            lookup: [
+              { name: "space", value: " " },
+              { name: "tab", value: "\t" }
+            ]
+          },
+          description: 'tslongIndentCharDesc',
+          defaultValue: ' ',
+        },
+        {
+          name: 'preserveNewlines',
+          type: 'boolean',
+          description: 'tslongPreserveNewlinesDesc'
+        },
+        {
+          name: 'preserveMaxNewlines',
+          type: 'number',
+          description: 'tslongPreserveMaxNewlinesDesc',
+          defaultValue: -1
+        },
+        {
+          name: 'jslintHappy',
+          type: 'boolean',
+          description: 'tslongJslintHappyDesc'
+        },
+        {
+          name: 'braceStyle',
+          type: {
+            name: 'selection',
+            data: ['collapse', 'expand', 'end-expand', 'expand-strict']
+          },
+          description: 'tslongBraceStyleDesc',
+          defaultValue: "collapse"
+        },
+        {
+          name: 'noSpaceBeforeConditional',
+          type: 'boolean',
+          description: 'tslongNoSpaceBeforeConditionalDesc'
+        },
+        {
+          name: 'unescapeStrings',
+          type: 'boolean',
+          description: 'tslongUnescapeStringsDesc'
+        }
+      ]
+    }
+  ],
+  exec: createExec('tslong')
+};
+
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -2225,108 +2300,108 @@ exports.testActivate = function(options)
     arrowTabText: '',
     emptyParameters: []
   });
 
   helpers.setInput('tsg');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: [ ' <solo>' ]
+    emptyParameters: [ ' <solo>', ' [options]' ]
   });
 
   helpers.setInput('tsg ');
   helpers.check({
-    emptyParameters: [],
+    emptyParameters: [ ' [options]' ],
     arrowTabText: '',
     directTabText: 'aaa'
   });
 
   helpers.setInput('tsg a');
   helpers.check({
-    emptyParameters: [],
+    emptyParameters: [ ' [options]' ],
     arrowTabText: '',
     directTabText: 'aa'
   });
 
   helpers.setInput('tsg b');
   helpers.check({
-    emptyParameters: [],
+    emptyParameters: [ ' [options]' ],
     arrowTabText: '',
     directTabText: 'bb'
   });
 
   helpers.setInput('tsg d');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aa');
   helpers.check({
-    emptyParameters: [],
+    emptyParameters: [ ' [options]' ],
     arrowTabText: '',
     directTabText: 'a'
   });
 
   helpers.setInput('tsg aaa');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aaa ');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ '[options]' ]
   });
 
   helpers.setInput('tsg aaa d');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aaa dddddd');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aaa dddddd ');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ '[options]' ]
   });
 
   helpers.setInput('tsg aaa "d');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aaa "d d');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsg aaa "d d"');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
-    emptyParameters: []
+    emptyParameters: [ ' [options]' ]
   });
 
   helpers.setInput('tsn ex ');
   helpers.check({
     directTabText: '',
     arrowTabText: '',
     emptyParameters: []
   });
@@ -2911,17 +2986,17 @@ exports.testCompleted = function(options
   helpers.setInput('tsg -');
   helpers.check({
     input:  'tsg -',
     markup: 'VVVVI',
     cursor: 5,
     directTabText: '-txt1',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       solo: { value: undefined, status: 'INCOMPLETE' },
       txt1: { value: undefined, status: 'VALID' },
       bool: { value: undefined, status: 'VALID' },
       txt2: { value: undefined, status: 'VALID' },
       num: { value: undefined, status: 'VALID' }
     }
   });
@@ -2929,68 +3004,68 @@ exports.testCompleted = function(options
   helpers.pressTab();
   helpers.check({
     input:  'tsg --txt1 ',
     markup: 'VVVVIIIIIIV',
     cursor: 11,
     directTabText: '',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ], // Bug 770830: '<txt1>', ' <solo>'
+    emptyParameters: [ '[options]' ], // Bug 770830: '<txt1>', ' <solo>'
     args: {
       solo: { value: undefined, status: 'INCOMPLETE' },
       txt1: { value: undefined, status: 'INCOMPLETE' },
       bool: { value: undefined, status: 'VALID' },
       txt2: { value: undefined, status: 'VALID' },
       num: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tsg --txt1 fred');
   helpers.check({
     input:  'tsg --txt1 fred',
     markup: 'VVVVVVVVVVVVVVV',
     directTabText: '',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ], // Bug 770830: ' <solo>'
+    emptyParameters: [ ' [options]' ], // Bug 770830: ' <solo>'
     args: {
       solo: { value: undefined, status: 'INCOMPLETE' },
       txt1: { value: 'fred', status: 'VALID' },
       bool: { value: undefined, status: 'VALID' },
       txt2: { value: undefined, status: 'VALID' },
       num: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tscook key value --path path --');
   helpers.check({
     input:  'tscook key value --path path --',
     markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVII',
     directTabText: 'domain',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       key: { value: 'key', status: 'VALID' },
       value: { value: 'value', status: 'VALID' },
       path: { value: 'path', status: 'VALID' },
       domain: { value: undefined, status: 'VALID' },
       secure: { value: false, status: 'VALID' }
     }
   });
 
   helpers.setInput('tscook key value --path path --domain domain --');
   helpers.check({
     input:  'tscook key value --path path --domain domain --',
     markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVII',
     directTabText: 'secure',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       key: { value: 'key', status: 'VALID' },
       value: { value: 'value', status: 'VALID' },
       path: { value: 'path', status: 'VALID' },
       domain: { value: 'domain', status: 'VALID' },
       secure: { value: false, status: 'VALID' }
     }
   });
@@ -2999,17 +3074,17 @@ exports.testCompleted = function(options
 exports.testCase = function(options) {
   helpers.setInput('tsg AA');
   helpers.check({
     input:  'tsg AA',
     markup: 'VVVVII',
     directTabText: '',
     arrowTabText: 'aaa',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       solo: { value: undefined, text: 'AA', status: 'INCOMPLETE' },
       txt1: { value: undefined, status: 'VALID' },
       bool: { value: undefined, status: 'VALID' },
       txt2: { value: undefined, status: 'VALID' },
       num: { value: undefined, status: 'VALID' }
     }
   });
@@ -3057,113 +3132,128 @@ exports.testHidden = function(options) {
 
   helpers.setInput('tshidden');
   helpers.check({
     input:  'tshidden',
     markup: 'VVVVVVVV',
     directTabText: '',
     arrowTabText: '',
     status: 'VALID',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'VALID' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --vis');
   helpers.check({
     input:  'tshidden --vis',
     markup: 'VVVVVVVVVIIIII',
     directTabText: 'ible',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'VALID' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --invisiblestrin');
   helpers.check({
     input:  'tshidden --invisiblestrin',
     markup: 'VVVVVVVVVEEEEEEEEEEEEEEEE',
     directTabText: '',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'VALID' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --invisiblestring');
   helpers.check({
     input:  'tshidden --invisiblestring',
     markup: 'VVVVVVVVVIIIIIIIIIIIIIIIII',
     directTabText: '',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'INCOMPLETE' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --invisiblestring x');
   helpers.check({
     input:  'tshidden --invisiblestring x',
     markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV',
     directTabText: '',
     arrowTabText: '',
     status: 'VALID',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: 'x', status: 'VALID' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --invisibleboolea');
   helpers.check({
     input:  'tshidden --invisibleboolea',
     markup: 'VVVVVVVVVEEEEEEEEEEEEEEEEE',
     directTabText: '',
     arrowTabText: '',
     status: 'ERROR',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'VALID' },
       invisibleboolean: { value: undefined, status: 'VALID' }
     }
   });
 
   helpers.setInput('tshidden --invisibleboolean');
   helpers.check({
     input:  'tshidden --invisibleboolean',
     markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVV',
     directTabText: '',
     arrowTabText: '',
     status: 'VALID',
-    emptyParameters: [ ],
+    emptyParameters: [ ' [options]' ],
     args: {
       visible: { value: undefined, status: 'VALID' },
       invisiblestring: { value: undefined, status: 'VALID' },
       invisibleboolean: { value: true, status: 'VALID' }
     }
   });
+
+  helpers.setInput('tshidden --visible xxx');
+  helpers.check({
+    input:  'tshidden --visible xxx',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'VALID',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: 'xxx', status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
 };
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -3594,16 +3684,66 @@ exports.testIncrDecr = function() {
   check('tselarr 2', KEY_DOWNS_TO, 'tselarr 3');
   check('tselarr 3', KEY_DOWNS_TO, 'tselarr 1');
 
   check('tselarr 3', KEY_UPS_TO, 'tselarr 2');
 };
 
 });
 /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testMenu', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+var helpers = require('gclitest/helpers');
+var mockCommands = require('gclitest/mockCommands');
+
+
+exports.setup = function(options) {
+  mockCommands.setup();
+  helpers.setup(options);
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+  helpers.shutdown(options);
+};
+
+exports.testOptions = function(options) {
+  helpers.setInput('tslong');
+  helpers.check({
+    input:  'tslong',
+    markup: 'VVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ' <url>', ' [options]' ],
+    args: {
+      url: { value: undefined, status: 'INCOMPLETE' },
+      indentSize: { value: undefined, status: 'VALID' },
+      indentChar: { value: undefined, status: 'VALID' },
+      preserveNewlines: { value: undefined, status: 'VALID' },
+      preserveMaxNewlines: { value: undefined, status: 'VALID' },
+      jslintHappy: { value: undefined, status: 'VALID' },
+      braceStyle: { value: undefined, status: 'VALID' },
+      noSpaceBeforeConditional: { value: undefined, status: 'VALID' },
+      unescapeStrings: { value: undefined, status: 'VALID' }
+    }
+  });
+};
+
+
+});
+
+/*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
@@ -5711,16 +5851,17 @@ let testModuleNames = [
   'gclitest/testExec',
   'gclitest/testHelp',
   'gclitest/testHistory',
   'gclitest/testInputter',
   'gclitest/testIncomplete',
   'gclitest/testIntro',
   'gclitest/testJs',
   'gclitest/testKeyboard',
+  'gclitest/testMenu',
   'gclitest/testPref',
   'gclitest/mockSettings',
   'gclitest/testRequire',
   'gclitest/requirable',
   'gclitest/testResource',
   'gclitest/testScratchpad',
   'gclitest/testSettings',
   'gclitest/testSpell',