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 103439 7bf846af58d35674c1b02f8dbfa260f6012a78d5
parent 103438 6b00d1edb2e717b0d658dc3408fae6f38add9b02
child 103440 35e5ee61e193771a906a39c7d21e66651cf03ae0
push id13991
push userryanvm@gmail.com
push dateSun, 26 Aug 2012 02:29:03 +0000
treeherdermozilla-inbound@c4f20a024113 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharth
bugs775031
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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',