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 15:39:10 +0100
changeset 103444 a275a3c32e714c1bc5e8e886e60b15804f4dd5c5
parent 103443 efd1509146d56880a49c04dbac6c937fbb089fab
child 103445 c80d0e010be310c2770e6668a89ee5cce1e97f3d
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',