head_playlistcommands.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2011 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
26 
27 // Current test being run
28 var gTestPrefix = "PlaylistCommandsBuilder";
29 
30 // globals used to indicate successful function calls
31 var gTriggered = false;
32 var gRefreshed = false;
34 
35 /* an increasing counter used to generate ids, labels, and tooltips that
36  * are unique among all subobjects added in these tests */
37 var gIDCounter = 0;
38 
39 // Dummy DOM node and document used as returns
40 var dummyNode = {
41  QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMNode])
42 };
44  QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMDocument])
45 };
46 
47 // PlaylistCommands subobject types.
48 // These constants are mirrored from sbPlaylistCommandsBuilder.js
49 var FLAG = {
50  EXISTING: "existing",
51  SUBMENU: "submenu",
52  ACTION: "action",
53  SEPARATOR: "separator",
54  FLAG: "flag",
55  VALUE: "value",
56  CHOICEMENU: "choice",
57  CHOICEMENUITEM: "choiceitem",
58  CUSTOM: "custom",
59  COMMAND: "subobject"
60 }
61 
62 // const used by addChoiceMenuItems
63 var NUM_CHOICEMENUITEMS = 10;
64 
65 // ============================================================================
66 // TEST FUNCTIONS
67 // ============================================================================
68 
69 /* This log is central to the implementation of this test.
70  * This test primarily deals with one command object and the subobjects
71  * that are added to it. Each subobjects entry in the log keeps track
72  * of enough information about that subobject to verify its state
73  * at any time through assertLog. Objects like child sbIPlaylistCommands,
74  * submenus, and choicemenus maintain information about their children in
75  * sub-logs that are functionally similar to this, the outermost log. */
76 var gRootLog = new Array();
77 
78 /* The command log in use at the moment. Changed only through setLog(),
79  * this will usually be gRootLog unless we are examining a subobject with
80  * children, in which case the current command log, represented by
81  * gActiveCommandLog, will be changed to the command log relevant to that
82  * subobject.
83  * Every test that can operate in or on a submenu must call setLog(submenuId)
84  * at the start of the test to ensure that the gActiveCommandLog
85  * is set appropriately. */
87 
88 /* TestAppendInsertRemove
89  * Test appending, inserting, and removing all types of sbIPlaylistCommands
90  * subobjects to a menu within aCommand.
91  * aMenuID indicates which menu within aCommand to direct the test to.
92  * To specify the root menu aMenuID is passed as null, otherwise aMenuID
93  * represents a submenu of aCommand that the test will be performed in.
94  *
95  * First this tests appending, removing, and inserting 'action'
96  * subobjects with previously added subobjects present.
97  * Second, the same tests are run after clearing all child objects from the
98  * command.
99  * Lastly, we run testAppendAndInsertAllTypes which tests appending and
100  * inserting other command subobject types.
101  *
102  * It is expected that the applicable command log is empty when this test begins
103  *
104  *@param aCommand The sbIPlaylistCommands object that will have subobjects
105  * appended to, inserted in, and removed from.
106  *@param aMenuID The submenu of aCommand which the test will be performed in.
107  * null indicates the root menu of aCommand should be used
108  *@param aTestLength The number of subobjects to be appended. Half this number
109  * will then be removed. Then subobjects will be inserted
110  * until aTestLength number of subobjects are present again.
111  *@param aNumSubCommands The number of subobjects within aCommand under the
112  * menu described by aMenuID when this test is begun.
113  */
114 function testAppendInsertRemove(aCommand, aMenuID, aTestLength, aNumSubCommands)
115 {
116  /* The correct log to verify subobjects depends on the menu that we are
117  * currently in. If we are not in a submenu, aMenuID is null, then we
118  * use gRootLog. Otherwise, we use the sublog that is attached to the
119  * log object for the submenu specified by aMenuID */
120  setLog(aMenuID);
121 
122  // the active command log is expected to be empty when this test begins
123  assertEqual(gActiveCommandLog.length, 0);
124 
125  // sanity checks
126  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
127  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
128 
129  // make sure that we were told the correct number of existing commands
130  assertNumCommands(aCommand, aMenuID, aNumSubCommands);
131 
132  /* Put an entry in the log for every existing subobject. We don't know
133  * anything about them, so just log where they are. */
134  for (var i = 0; i < aNumSubCommands; i++)
135  {
136  var cmdID = aCommand.getCommandId(aMenuID,
137  i,
138  "test" /* host string */);
139  gActiveCommandLog[i] = {CHECK_FLAG: FLAG.EXISTING,
140  CHECK_ID: cmdID};
141  }
142 
143  /* Test appending, removing, and inserting actions in a command with
144  * already-present subobjects */
145  // First, append aTestLength number of actions
146  _log("appendAction with existing commands", aMenuID);
147  testAppendActions(aCommand, aMenuID, aTestLength, aNumSubCommands);
148 
149  // Second, remove 1/2 * aTestLength number of actions.
150  // 1/2 was chosen arbitrarily
151  _log("removeAction with existing commands", aMenuID);
152  testRemoveSubobjects(aCommand, aMenuID, 0.5 * aTestLength, aNumSubCommands);
153 
154  // Then, insert 1/2 * aTestLength number of actions
155  // 1/2 was chosen arbitrarily, but needs to match the number removed
156  _log("insertAction with existing commands", aMenuID);
157  testInsertActions(aCommand, aMenuID, 0.5 * aTestLength, aNumSubCommands);
158 
159  // ensure that we have aTestLength more commands than we started with
160  assertNumCommands(aCommand, aMenuID, aTestLength + aNumSubCommands);
161 
162  // clear aCommand of subobjects and empty the active command log
163  aCommand.removeAllCommands(aMenuID);
164  assertNumCommands(aCommand, aMenuID, 0);
165  gActiveCommandLog.length = 0;
166 
167  // Test appending, removing, and inserting actions in an empty command.
168  // First, append aTestLength number of actions
169  _log("appendAction without existing commands", aMenuID);
170  testAppendActions(aCommand, aMenuID, aTestLength, 0);
171 
172  // Second, remove 1/2 * aTestLength number of actions
173  // 1/2 was chosen arbitrarily
174  _log("removeAction without existing commands", aMenuID);
175  testRemoveSubobjects(aCommand, aMenuID, 0.5 * aTestLength, 0);
176 
177  // Then, insert 1/2 * aTestLength number of actions
178  // 1/2 was chosen arbitrarily but needs to match the number removed
179  _log("insertAction without existing commands", aMenuID);
180  testInsertActions(aCommand, aMenuID, 0.5 * aTestLength, 0);
181 
182  // ensure that we have aTestLength commands
183  assertNumCommands(aCommand, aMenuID, aTestLength);
184 
185  // clear aCommand of subobjects and empty the active command log
186  aCommand.removeAllCommands(aMenuID);
187  gActiveCommandLog.length = 0;
188  assertNumCommands(aCommand, aMenuID, 0);
189 
190  // test appending and inserting all sub object types
191  _log("append other types of command elements", aMenuID);
192  testAppendAndInsertAllTypes(aCommand, aMenuID, aTestLength, 0);
193 
194  // Remove 1/2 * aTestLength number of subobjects
195  // 1/2 was chosen arbitrarily
196  _log("remove various types of command elements", aMenuID);
197  testRemoveSubobjects(aCommand, aMenuID, 0.5 * aTestLength, 0);
198 
199  // clear aCommand of subobjects and empty the active command log
200  aCommand.removeAllCommands(aMenuID);
201  gActiveCommandLog.length = 0;
202  assertNumCommands(aCommand, aMenuID, 0);
203 }
204 
205 /* TestAppendActions
206  * Test to append aTestLength number of actions and ensure that they have the
207  * proper information and ordering.
208  *
209  * There are no expectations for gActiveCommandLog when this test begins.
210  * When this test is over, aCommand will have aTestLength number of new
211  * actions and gActiveCommandLog will have a record for each.
212  *
213  *@param aCommand The sbIPlaylistCommands object that subobjects will be
214  * appended to
215  *@param aMenuID The submenu of aCommand which the test will be performed in.
216  * null indicates the root menu of aCommand should be used
217  *@param aTestLength The number of subobjects to be appended.
218  *@param aNumSubCommands The number of subobjects within aCommand under the
219  * menu described by aMenuID when this test's parent
220  * test began.
221  */
222 function testAppendActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
223 {
224  /* The correct log to verify subobjects depends on the menu that we are
225  * currently in. If we are not in a submenu, aMenuID is null, then we
226  * use gRootLog. Otherwise, we use the sublog that is attached to the
227  * log object for the submenu specified by aMenuID */
228  setLog(aMenuID);
229 
230  // sanity checks
231  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
232  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
233 
234  // make sure all the present commands are accounted for
235  assertNumCommands(aCommand, aMenuID, aNumSubCommands);
236 
237  // create a list of append action instructions
238  var instructions = new Array();
239  for (var i = 0; i < aTestLength; i++)
240  {
241  /* for this test we only append actions, so all of our instructions are the
242  * same. We want an action type object, added using append */
243  instructions.push({ type: FLAG.ACTION,
244  action: "append%s"});
245  }
246 
247  // execute those instructions
248  ExecuteInstructions(aCommand, instructions, aMenuID);
249  assertEqual(aTestLength + aNumSubCommands,
250  gActiveCommandLog.length,
251  "The length of the log should be "+ (aTestLength +
252  aNumSubCommands) + ", not " + gActiveCommandLog.length);
253 
254  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
255  assertLog(aCommand);
256 }
257 
258 /* TestRemoveSubobjects
259  * Test to remove aTestLength number of subobjects. aTestLength number of
260  * command subobjects must be present when this test is called, usually
261  * that means calling this immediately after testAppendActions.
262  *
263  * gActiveCommandLog is expected to have at least aTestLength number of
264  * entries more than aNumSubCommands when this test begins.
265  * When this test is over, aCommand will have aTestLength fewer subobjects and
266  * the corresponding records will be removed from gActiveCommandLog.
267  *
268  * The first aNumSubCommands sub objects of aCommand, will not be affected
269  * by this test. Removal begins with the last of the subobjects and proceeds
270  * to the front, removing every other subobject. When aNumSubCommands is
271  * reached, we start over at the end rather than remove one of those
272  * pre-existing commands.
273  *
274  *@param aCommand The sbIPlaylistCommands object that subobjects will be
275  * removed from
276  *@param aMenuID The submenu of aCommand which the test will be performed in.
277  * null indicates the root menu of aCommand should be used
278  *@param aTestLength The number of subobjects to be removed.
279  *@param aNumSubCommands The number of subobjects within aCommand under the
280  * menu described by aMenuID when this test's parent
281  * test began. These objects will not be removed.
282  */
283 function testRemoveSubobjects(aCommand, aMenuID, aTestLength, aNumSubCommands)
284 {
285  /* The correct log to verify subobjects depends on the menu that we are
286  * currently in. If we are not in a submenu, aMenuID is null, then we
287  * use gRootLog. Otherwise, we use the sublog that is attached to the
288  * log object for the submenu specified by aMenuID */
289  setLog(aMenuID);
290 
291  // sanity checks
292  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
293  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
294 
295  // check that there are enough commands to remove.
296  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
297  assertTrue((gActiveCommandLog.length >= aTestLength + aNumSubCommands),
298  "testRemoveSubobjects expected more subobjects to be present." +
299  "Have " + gActiveCommandLog.length + " but need at least " +
300  (aTestLength + aNumSubCommands));
301 
302  /* We will remove every other subobject starting from the end. If
303  * we make a full pass of the array, removing every other one, and
304  * still need to remove more, then we start over at the end.
305  * Also, we don't remove from the first numSubCommands representing
306  * existing subobjects from before this test's parent test was run */
307  var index = gActiveCommandLog.length - 1;
308  for (var i = 0; i < aTestLength; i++)
309  {
310  var checkObject = gActiveCommandLog[index];
311 
312  // remove the command object and its log record
313  aCommand.removeCommand(aMenuID, checkObject.CHECK_ID);
314  gActiveCommandLog.splice(index, 1);
315 
316  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
317 
318  // remove every other command subobject
319  index = index - 2;
320 
321  // protect the existing commands by starting over at the end
322  if (index < aNumSubCommands)
323  {
324  index = gActiveCommandLog.length - 1;
325  }
326  }
327 
328  assertLog(aCommand);
329 }
330 
331 /* TestInsertActions
332  * Test to insert aTestLength number of actions in aCommand under the menu
333  * described by aMenuID (if aMenuID is null we consider the root menu of
334  * aCommand).
335  *
336  * This function creates an instruction list that oscillates between
337  * inserting before and after. When those instructions are executed,
338  * the insert location is determined, and those insert locations are meant to
339  * jump around within aCommand.
340  *
341  * This test relies on aCommand having at least one command already present
342  * under the submenu aMenuID. This is necessary so that we know there
343  * is always something to insert commands after or before.
344  *
345  * When this test finishes, aCommand will have aTestLength more subobjects
346  *
347  *@param aCommand The sbIPlaylistCommands object that subobjects will be
348  * inserted into
349  *@param aMenuID The submenu of aCommand which the test will be performed in.
350  * null indicates the root menu of aCommand should be used
351  *@param aTestLength The number of subobjects to be inserted.
352  *@param aNumSubCommands The number of subobjects within aCommand under the
353  * menu described by aMenuID when this test's parent
354  * test began.
355  */
356 function testInsertActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
357 {
358  /* The correct log to verify subobjects depends on the menu that we are
359  * currently in. If we are not in a submenu, aMenuID is null, then we
360  * use gRootLog. Otherwise, we use the sublog that is attached to the
361  * log object for the submenu specified by aMenuID */
362  setLog(aMenuID);
363 
364  // sanity checks
365  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
366  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
367  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
368  assertTrue((gActiveCommandLog.length > 0),
369  "testInsertActions requires at least 1 command before running");
370 
371  var numAdded = 0;
372  var index = 0;
373 
374  var instructions = new Array();
375  for (var i = 0; i < aTestLength; i++)
376  {
377  /* Create a list of instructions that alternate between inserting actions
378  * before and after. The insert location is handled by a separate counter */
379  if (i % 2 == 0)
380  {
381  instructions.push({ type: FLAG.ACTION,
382  action: "insert%sBefore"});
383  }
384  else {
385  instructions.push({ type: FLAG.ACTION,
386  action: "insert%sAfter"});
387  }
388  }
389 
390  ExecuteInstructions(aCommand, instructions, aMenuID);
391 
392  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
393  assertLog(aCommand);
394 }
395 
396 /* TestAppendAndInsertAllTypes
397  * Test appending, inserting, and removing all types of sbIPlaylistCommands
398  * subobjects. This test creates a list of aTestLength number of instructions,
399  * pairs of method operation types (ie "append", "insertBefore", or
400  * "insertAfter") and subObject types, that are executed at the end of this
401  * function.
402  *
403  * The command log for the submenu described by aMenuID is expected to be
404  * empty when this test begins, and aCommand should have only aNumSubCommands
405  * number of subobjects.
406  *
407  * The instructions, though guaranteed to represent all relevant
408  * subobject types, select randomly from combinations of the three operation
409  * types.
410  *
411  *@param aCommand The sbIPlaylistCommands object that subobjects will be
412  * appended and inserted into
413  *@param aMenuID The submenu of aCommand which the test will be performed in.
414  * null indicates the root menu of aCommand should be used
415  *@param aTestLength The number of subobjects to insert and append.
416  *@param aNumSubCommands The number of subobjects within aCommand under the
417  * menu described by aMenuID when this test begins.
418  */
419 function testAppendAndInsertAllTypes(aCommand,
420  aMenuID,
421  aTestLength,
422  aNumSubCommands)
423 {
424  setLog(aMenuID);
425 
426  // sanity checks
427  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
428  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
429  assertTrue((gActiveCommandLog.length == 0),
430  "The relevant log for the menu '" + aMenuID + "' is expected to " +
431  "be empty when testAppendAndInsertAllTypes begins. It has a " +
432  "length of " + gActiveCommandLog.length);
433 
434  /* Put an entry in the log for every existing subobject. We don't know
435  * anything about them, so just log what and where they are. */
436  for (var i = 0; i < aNumSubCommands; i++)
437  {
438  var cmdID = aCommand.getCommandId(aMenuID,
439  i,
440  "test" /* host string */);
441  gActiveCommandLog[i] = {CHECK_FLAG: FLAG.EXISTING,
442  CHECK_ID: cmdID};
443  }
444 
445  /* this test expects aNumSubCommands subobjects to be present when this test
446  * begins, so that means gActiveCommandLog.length should match the number
447  * of subobjects now */
448  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
449 
450  /* We generate a list of instructions that indicate what type of subobject
451  * to add and how to add it. */
452  var instructions = new Array();
453 
454  /* The possible combinations of all the ways to add a subobject to aCommand.
455  * One of these arrays is selected randomly for a subobject type and an
456  * instruction is then created for each of the actions in the array paired
457  * with that type. For example, if the 4th element is selected for the
458  * "separator" subobject type, then two instructions will be created: one
459  * to append a separator and the other to insert a separator with
460  * 'insertBefore' */
461  var actions = [["append%s"],
462  ["insert%sBefore"],
463  ["insert%sAfter"],
464  ["append%s", "insert%sBefore"],
465  ["append%s", "insert%sAfter"],
466  ["insert%sBefore", "insert%sAfter"],
467  ["append%s", "insert%sBefore", "insert%sAfter"]
468  ];
469 
470  /* We make the first action an appendAction so that we know there will be at
471  * least one subobject present before the randomly picked instructions are
472  * executed. We do this in case the first random instruction is an insert,
473  * in which case it needs another object to be present to insert before
474  * or after */
475  instructions.push({type: FLAG.ACTION,
476  action: actions[0][0]});
477 
478  // make sure we add aTestLength number
479  while (instructions.length < aTestLength)
480  {
481  // for each subobject type
482  for (var flag in FLAG)
483  {
484  // we don't concern ourselves with some of the flags that don't apply here
485  if (FLAG[flag] == FLAG.EXISTING ||
486  FLAG[flag] == FLAG.CHOICEMENUITEM ||
487  FLAG[flag] == FLAG.SUBMENU)
488  {
489  continue;
490  }
491 
492  /* generate a random num between 0 and (actions.length - 1) to pick
493  * an instruction combination */
494  var num = Math.floor(Math.random() * actions.length);
495 
496  // get the instruction combination, an array of actions, that we selected
497  var currActions = actions[num];
498 
499  /* push an instruction for each of the actions in the combination we
500  * selected */
501  for each (var a in currActions)
502  {
503  instructions.push({ type: FLAG[flag],
504  action: a});
505 
506  // if we have aTestLength number of instructions, we are done
507  if (instructions.length >= aTestLength)
508  break;
509  }
510 
511  // if we have aTestLength number of instructions, we are done
512  if (instructions.length >= aTestLength)
513  break;
514  }
515  }
516 
517  /* perform the list of instructions that we just created */
518  ExecuteInstructions(aCommand, instructions, aMenuID);
519  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
520  assertLog(aCommand);
521 }
522 
523 /* ExecuteInstructions
524  * This functions takes a list of instructions, pairs of operation
525  * types (ie "append", "insertBefore", or "insertAfter") and subObject types,
526  * and executes the appropriate operation.
527  *
528  * There must be at least one subobject present in aCommand under the menu
529  * described by aMenuID if the first instruction is an insert operation,
530  * otherwise there will be nothing to insert before or after. This should be
531  * handled by the caller.
532  *
533  * Valid instructions are of the form { type: t, action: a} where t is one of
534  * the FLAGs and a is one of 'append&s', 'insert%sAfter', or 'insert%sBefore'
535  *
536  * For each of the instructions, the functionName is derived from the operation
537  * and subObject types. Then, the params for the execute call are pulled
538  * together in the params array.
539  * Finally, the function described by functionName is executed with the
540  * params array.
541  *
542  *@param aCommand The sbIPlaylistCommands object that subobjects will be
543  * appended and inserted into
544  *@param aInstructions The array of instruction objects that indicate which
545  * operations to perform for which subobject types.
546  * Each instruction must be of the form {type: t, action: a}
547  *@param aMenuID The submenu of aCommand which the test will be performed in.
548  * null indicates the root menu of aCommand should be used
549  */
550 function ExecuteInstructions(aCommand, aInstructions, aMenuID)
551 {
552  setLog(aMenuID);
553 
554  // sanity checks
555  assertTrue(aCommand instanceof Ci.sbIPlaylistCommands,
556  "aCommand is not a sbIPlaylistCommands");
557 
558  // index indicating where to perform the next insert operation
559  var insertIndex = 0;
560 
561  /* this bool makes sure that the first choice menu we create gets
562  * choice items as subobjects to be sure we test the choice items */
563  var firstChoiceMenu = true;
564 
565  for each (var instruction in aInstructions)
566  {
567  assertTrue("type" in instruction,
568  "The instruction being executed doesn't have a subobject type");
569  assertTrue("action" in instruction,
570  "The instruction being executed doesn't have an action type");
571 
572  /* the instruction.action is an incomplete functionName with %s in place of
573  * the type */
574  var functionName = instruction.action;
575  var type = instruction.type;
576 
577  /* fill out the method name which currently has %s instead of the subobject
578  * type to be added */
579  functionName = completeFunctionName(type, functionName);
580 
581  /* create an array for the params that will be sent to the function
582  * described by functionName */
583  var params = [aMenuID];
584 
585  /* Create a log object that keeps track of information about the object
586  * being added so that we can ensure all operations were succesful. We will
587  * add more to this object as we learn more about the subobject being added.
588  */
589  var checkObject = {CHECK_FLAG: type,
590  CHECK_SUBMENUID: aMenuID};
591 
592  // figure out where to put the log object in the command log.
593  // first figure out if this is an insert or append operation
594  if (/^insert/.test(functionName))
595  {
596  // this is an insert so we'll need to splice into the log
597  var insertId = gActiveCommandLog[insertIndex].CHECK_ID;
598 
599  params.push(insertId);
600  if (/Before$/.test(functionName))
601  {
602  // this is an insertBefore operation
603  gActiveCommandLog.splice(insertIndex, 0, checkObject);
604  }
605  else if (/After$/.test(functionName)) {
606  // this is an insertAfter operation
607  gActiveCommandLog.splice(insertIndex + 1, 0, checkObject);
608  }
609  else {
610  doFail("Instruction had invalid action in ExecuteInstructions. Tried " +
611  "to execute '" + functionName + "'");
612  }
613 
614  // jump the insertIndex around with +3 chosen arbitrarily
615  insertIndex = (insertIndex + 3) % gActiveCommandLog.length;
616  }
617  else if (functionName.search(/append/) != -1 ) {
618  // this is an append so we can just push onto the back of the log
619  gActiveCommandLog.push(checkObject);
620  }
621  else {
622  fail("Received an instruction with an unrecognized function name");
623  }
624 
625  /* Get an id, label, and tooltip for the new subobject. In all cases we
626  * need the id, so push that now, the label and tooltip we may not need. */
627  var [currId, currLabel, currTooltip] = getCommandData(type);
628  params.push(currId);
629  checkObject.CHECK_ID = currId;
630 
631  // separators, custom objects, and subcommands don't have a label or tooltip
632  if (type != FLAG.SEPARATOR &&
633  type != FLAG.CUSTOM &&
634  type != FLAG.COMMAND)
635  {
636  params.push(currLabel, currTooltip);
637  checkObject.CHECK_LABEL = currLabel;
638  checkObject.CHECK_TOOLTIP = currTooltip;
639  }
640 
641  // the remaining params are unique enough to warrant individual handling
642  switch(type)
643  {
644  case FLAG.ACTION:
645  // action objects need a trigger callback
646  params.push(makeTriggerCallback(currId));
647  break;
648  case FLAG.FLAG:
649  // Flag objects need a trigger and value callback.
650  params.push(makeTriggerCallback(currId));
651 
652  // Value callback for flag types returns a bool.
653  checkObject.CHECK_VALUECALLBACK = ((gIDCounter % 2) == 0);
654  params.push(makeValueCallback(currId, checkObject.CHECK_VALUECALLBACK));
655  break;
656  case FLAG.VALUE:
657  // value objects need setting and getting value callbacks
658  params.push(makeValueSetter(currId));
659 
660  // Value callback for value types returns a string.
661  checkObject.CHECK_VALUECALLBACK = FLAG.VALUE + gIDCounter;
662  params.push(makeValueCallback(currId, checkObject.CHECK_VALUECALLBACK));
663  break;
664  case FLAG.CHOICEMENU:
665  // jhawk this should probably check getCommandChoiceItem, but I can't
666  // figure out how that works
667  checkObject.CHECK_VALUECALLBACK = FLAG.CHOICEMENU + gIDCounter;
668  params.push(makeValueCallback(currId, checkObject.CHECK_VALUECALLBACK));
669  break;
670  case FLAG.CUSTOM:
671  // custom objects need an instantiation and refresh callback each
672  params.push(makeInstantiationCallback(currId));
673  params.push(makeRefreshCallback(currId));
674  break;
675  case FLAG.COMMAND:
676  // we add some actions (0-4) to each subcommand
677  var newCommand = PlaylistCommandsBuilder(currId);
678  checkObject.CHECK_SUBACTIONS = new Array();
679 
680  // select a random number between 0 and 4 and add that many subactions
681  var rand = Math.floor(Math.random() * 5);
682  for (var p = 0; p < rand; p++)
683  {
684  let subId = "subaction" + p + "id-in-" + currId;
685  let subLabel = "subaction" + p + "label-in-" + currId;
686  let subTooltip = "subaction" + p + "tooltip-in-" + currId;
687  let callback = makeTriggerCallback(subId);
688  newCommand.appendAction(null,
689  subId,
690  subLabel,
691  subTooltip,
692  callback);
693  checkObject.CHECK_SUBACTIONS
694  .push({CHECK_FLAG: FLAG.ACTION,
695  CHECK_ID: subId,
696  CHECK_LABEL: subLabel,
697  CHECK_TOOLTIP: subTooltip,
698  CHECK_SUBMENUID: null});
699  }
700  params.push(newCommand);
701  break;
702  }
703 
704  // here we do the operation we've been constructing in this function
705  aCommand[functionName].apply(this, params);
706 
707  /* at this point if this instruction is for a choice menu, it has been added
708  * so it might be time to add choicemenuitems to it */
709  if (type == FLAG.CHOICEMENU && (gIDCounter % 2 == 0 || firstChoiceMenu))
710  {
711  addChoiceMenuItems(aCommand, currId, checkObject)
712  firstChoiceMenu = false;
713  }
714  }
715 }
716 
717 /* replace the missing portions of a functionName depending on the type of
718  * subobject and return the completed functionName */
719 function completeFunctionName(aType, aFunctionName)
720 {
721  switch(aType)
722  {
723  case FLAG.ACTION:
724  aFunctionName = aFunctionName.replace(/%s/, "Action");
725  break;
726  case FLAG.SEPARATOR:
727  aFunctionName = aFunctionName.replace(/%s/, "Separator");
728  break;
729  case FLAG.FLAG:
730  aFunctionName = aFunctionName.replace(/%s/, "Flag");
731  break;
732  case FLAG.VALUE:
733  aFunctionName = aFunctionName.replace(/%s/, "Value");
734  break;
735  case FLAG.CHOICEMENU:
736  aFunctionName = aFunctionName.replace(/%s/, "ChoiceMenu");
737  break;
738  case FLAG.CUSTOM:
739  aFunctionName = aFunctionName.replace(/%s/, "CustomItem");
740  break;
741  case FLAG.COMMAND:
742  aFunctionName = aFunctionName.replace(/%s/, "PlaylistCommands");
743  break;
744  default:
745  fail("unrecognized instruction type: " + type);
746  }
747  return aFunctionName;
748 }
749 
750 /* Adds NUM_CHOICEMENUITEMS number of choicemenuitems to the choicemenu under
751  * the param command with an id of choiceMenuId. Additions are made to the
752  * CHECK_CHOICEMENUITEMS array of the param checkObject to serve as a mini-log
753  * of the added choicemenuitems.
754  */
755 function addChoiceMenuItems(aCommand, aChoiceMenuId, aCheckObject)
756 {
757  aCheckObject.CHECK_CHOICEMENUITEMS = new Array();
758  for (var c = 0; c < NUM_CHOICEMENUITEMS; c++)
759  {
760  assertNumCommands(aCommand, aChoiceMenuId, c);
761  var currId = "choiceitem" + c;
762  var currLabel = "choiceitem" + c + "label";
763  var currTooltip = "choiceitem" + c + "tooltip";
764  var triggerCallback = makeTriggerCallback(currId);
765  aCommand.appendChoiceMenuItem(aChoiceMenuId,
766  currId,
767  currLabel,
768  currTooltip,
769  triggerCallback);
770  aCheckObject.CHECK_CHOICEMENUITEMS
771  .push({CHECK_FLAG: FLAG.CHOICEMENUITEM,
772  CHECK_ID: currId,
773  CHECK_LABEL: currLabel,
774  CHECK_TOOLTIP: currTooltip,
775  CHECK_SUBMENUID: aChoiceMenuId});
776  }
777  assertEqual(aCheckObject.CHECK_CHOICEMENUITEMS.length,
778  NUM_CHOICEMENUITEMS,
779  "The length of the choicemenuitem log should be "+
780  NUM_CHOICEMENUITEMS + ", not " +
781  aCheckObject.CHECK_CHOICEMENUITEMS.length);
782  assertNumCommands(aCommand, aChoiceMenuId, NUM_CHOICEMENUITEMS);
783 }
784 
785 /* TestCommandCallbacksAndShortcuts
786  * This tests the command enabled and visible callbacks that determine where/
787  * when/if a subobject appears, as well as the command shortcuts (keyboard
788  * shortcuts) and action triggers that occur when the user activates and action.
789  *
790  * It is expected that the relevant gActiveCommandLog is empty when this test
791  * is run
792  */
794  aMenuID,
795  aTestLength,
796  aNumSubCommands)
797 {
798  setLog(aMenuID);
799 
800  // the active command log is expected to be empty when this test begins
801  assertEqual(gActiveCommandLog.length, 0);
802 
803  // sanity checks
804  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
805  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
806 
807  // make sure that we were told the correct number of existing commands
808  assertNumCommands(aCommand, aMenuID, aNumSubCommands);
809 
810  /* Put an entry in the log for every existing subobject. We don't know
811  * anything about them, so just log where they are. */
812  for (var i = 0; i < aNumSubCommands; i++)
813  {
814  var cmdID = aCommand.getCommandId(aMenuID,
815  i,
816  "test" /* host string */);
817  gActiveCommandLog[i] = {CHECK_FLAG: FLAG.EXISTING,
818  CHECK_ID: cmdID};
819  }
820 
821  // first append some logged objects to the commands for use in the test
822  testAppendActions(aCommand, aMenuID, aTestLength, aNumSubCommands);
823 
824  _log("setCommandVisibleCallback and setCommandEnabledCallback",
825  aMenuID);
826  testCommandVisibleAndEnabled(aCommand, aMenuID, aTestLength, aNumSubCommands);
827 
828  _log("setCommandShortcut", aMenuID);
829  testCommandShortcut(aCommand, aMenuID, aTestLength, aNumSubCommands);
830 
831  _log("command action triggers", aMenuID);
832  testTriggerActions(aCommand, aMenuID, aTestLength, aNumSubCommands);
833 
834  _log("initiation and shutdown callbacks", aMenuID);
835  testInitShutdownCB(aCommand);
836 
837  aCommand.removeAllCommands(aMenuID);
838  gActiveCommandLog.length = 0;
839  assertNumCommands(aCommand, aMenuID, 0);
840 }
841 
842 /* TestInitShutdownCB
843  * This tests the initialization and shutdown callbacks of an
844  * sbIPlaylistCommands.
845  */
846 function testInitShutdownCB(aCommand) {
847 
848  // we'll use these variables to determine success
849  var isInitialized = false;
850  var isShutdown = false;
851 
852  // definte the callbacks that we'll need
853  function cmds_init(context, host, data) {
854  var implementorContext = {
855  testString: "test",
856  toString: function() {
857  return this.testString;
858  },
859  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsString])
860  };
861  context.implementorContext = implementorContext;
862  isInitialized = true;
863  }
864 
865  function cmds_shutdown(context, host, data) {
866  assertTrue((context),
867  "shutdown callback not provided context");
868  assertTrue((context.implementorContext),
869  "shutdown callback not provided implementor context");
870  var testString =
871  context.implementorContext.QueryInterface(Ci.nsISupportsString);
872  assertEqual(testString.toString(),
873  "test",
874  "shutdown callback implementor context invalid");
875  context.implementorContext = null;
876  isShutdown = true;
877  }
878 
879  // set the callbacks that we want
880  aCommand.setInitCallback(cmds_init);
881  aCommand.setShutdownCallback(cmds_shutdown);
882 
883  // copy the command because we are going to shut it down in this test
884  var commandDup = aCommand.duplicate();
885 
886  // try the initialize
887  commandDup.initCommands("");
888  assertTrue(isInitialized,
889  "initCommands did not call init callback!");
890 
891  // try the shutdown
892  commandDup.shutdownCommands();
893  assertTrue(isShutdown,
894  "shutdownCommands did not call shutdown callback!");
895 }
896 
897 /* TestCommandVisibleAndEnabled
898  * This tests the command enabled and visible callbacks that determine where/
899  * when/if a subobject appears. Every other command is made visible, while
900  * every third is made enabled. If CHECK_VIS or CHECK_ENABLED is defined
901  * in a log object, the values are compared to the result from getCommandVisible
902  * and getCommandEnabled respectively.
903  */
905  aMenuID,
906  aTestLength,
907  aNumSubCommands)
908 {
909  setLog(aMenuID);
910  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
911  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
912 
913  for (var i = aNumSubCommands; i < aTestLength + aNumSubCommands; i++)
914  {
915  var checkObject = gActiveCommandLog[i];
916 
917  checkObject.CHECK_VIS = ((i % 2) == 0);
918  var visCallback = makeValueCallback(checkObject.CHECK_ID,
919  checkObject.CHECK_VIS);
920  aCommand.setCommandVisibleCallback(aMenuID, checkObject.CHECK_ID, visCallback);
921 
922  checkObject.CHECK_ENABLED = ((i % 3) == 0);
923  var enabledCallback = makeValueCallback(checkObject.CHECK_ID,
924  checkObject.CHECK_ENABLED);
925  aCommand.setCommandEnabledCallback(aMenuID, checkObject.CHECK_ID, enabledCallback);
926  }
927  assertLog(aCommand);
928 }
929 
930 /* TestCommandShortcut
931  * This tests the the command shortcuts (keyboard shortcuts). Every subobject
932  * is given a shortcut based on its index in the gActiveCommandLog, with the local
933  * boolean alternating between true and false. If CHECK_SHORTCUT is
934  * defined in log object, the command shortcuts will be checked.
935  */
936 function testCommandShortcut(aCommand, aMenuID, aTestLength, aNumSubCommands)
937 {
938  setLog(aMenuID);
939 
940  // sanity checks
941  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
942  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
943 
944  for (var i = aNumSubCommands; i < aTestLength + aNumSubCommands; i++)
945  {
946  var checkObject = gActiveCommandLog[i];
947  aCommand.setCommandShortcut(aMenuID,
948  checkObject.CHECK_ID,
949  "key" + i,
950  "keycode" + i,
951  "modifier" + i,
952  (i % 2) == 0);
953  checkObject.CHECK_SHORTCUT = ((i % 2) == 0);
954  }
955 
956  assertLog(aCommand);
957 }
958 
959 /* TestTriggerActions
960  * Tests that every action trigger is functional by calling onCommand on the
961  * subobject and ensuring that the gTriggered boolean changes from
962  * false to true (the only operation performed by the test trigger callback)
963  */
964 function testTriggerActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
965 {
966  setLog(aMenuID);
967 
968  // sanity checks
969  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
970  assertTrue((aNumSubCommands >= 0), "aNumSubCommands can't be negative");
971 
972  for (var i = aNumSubCommands; i < aTestLength + aNumSubCommands; i++)
973  {
974  assertTriggerCallback(aCommand, gActiveCommandLog[i], i);
975  }
976 }
977 
978 /* TestSubmenus
979  * Test submenus by adding aTestLength number of submenus. We cycle through
980  * appending, insertingAfter, and insertingBefore. Finally, we run
981  * testAppendAndInsertAllTypes within the first submenu added.
982  */
983 function testSubmenus(aCommand, aMenuID, aTestLength, aNumSubCommands)
984 {
985  setLog(aMenuID);
986 
987  // sanity checks
988  assertTrue((aTestLength > 0), "A test length > 0 must be specified");
989  assertTrue((aNumSubCommands >= 0),
990  "aNumSubCommands can't be negative, got: " + aNumSubCommands);
991 
992  _log("adding submenus", aMenuID);
993 
994  /* Put an entry in the log for every existing subobject. We don't know
995  * anything about them, so just log where they are. */
996  for (var i = 0; i < aNumSubCommands; i++)
997  {
998  var cmdID = aCommand.getCommandId(aMenuID,
999  i,
1000  "test" /* host string */);
1001  gActiveCommandLog[i] = {CHECK_FLAG: FLAG.EXISTING,
1002  CHECK_ID: cmdID};
1003  }
1004 
1005  // marker to keep track of a location for inserting before and after
1006  let insertCounter = 0;
1007 
1008  // add aTestLength number of submenus
1009  for (var i = 0; i < aTestLength; i++)
1010  {
1011  var currId = FLAG.SUBMENU + i + "id";
1012  var currLabel = FLAG.SUBMENU + i + "label";
1013  var currTooltip = FLAG.SUBMENU + i + "tooltip";
1014 
1015  // an object for the log that keeps track of this submenu's information
1016  var checkObject = {CHECK_FLAG: FLAG.SUBMENU,
1017  CHECK_ID: currId,
1018  CHECK_LABEL: currLabel,
1019  CHECK_TOOLTIP: currTooltip,
1020  CHECK_SUBMENUID: aMenuID};
1021 
1022  // we cycle through append, insertAfter, and insertBefore
1023  switch (i % 3)
1024  {
1025  case 0:
1026  // add submenu to the end, and add the log object to the end of the log
1027  aCommand.appendSubmenu(aMenuID, currId, currLabel, currTooltip);
1028  gActiveCommandLog.push(checkObject);
1029  break;
1030  case 1:
1031  // add submenu as insertAfter, and splice the log object into the log
1032  var insertId = gActiveCommandLog[insertCounter].CHECK_ID;
1033  aCommand.insertSubmenuAfter(aMenuID,
1034  insertId,
1035  currId,
1036  currLabel,
1037  currTooltip)
1038  gActiveCommandLog.splice(insertCounter + 1, 0 , checkObject);
1039 
1040  // jump the insertIndex around with +3 chosen arbitrarily
1041  insertCounter = (insertCounter + 3) % gActiveCommandLog.length;
1042  break;
1043  case 2:
1044  // add submenu as insertBefore, and splice the log object into the log
1045  var insertId = gActiveCommandLog[insertCounter].CHECK_ID;
1046  aCommand.insertSubmenuBefore(aMenuID,
1047  insertId,
1048  currId,
1049  currLabel,
1050  currTooltip)
1051  gActiveCommandLog.splice(insertCounter, 0 , checkObject);
1052 
1053  // jump the insertIndex around with +4 chosen arbitrarily
1054  insertCounter = (insertCounter + 4) % gActiveCommandLog.length;
1055  break;
1056  default:
1057  break;
1058  }
1059  }
1060 
1061  // confirm that the command object's contents match the log
1062  assertNumCommands(aCommand, aMenuID, gActiveCommandLog.length);
1063  assertLog(aCommand);
1064 
1065  // run testAppendAndInsertAllTypes within the first submenu
1066  var submenuId = gActiveCommandLog[aNumSubCommands].CHECK_ID;
1067  _log("adding subobjects of all types to submenu", submenuId);
1068  testAppendAndInsertAllTypes(aCommand, submenuId, aTestLength, 0);
1069 
1070  // clear the command of subobjects and empty the log
1071  aCommand.removeAllCommands(aMenuID);
1072  gActiveCommandLog.length = 0;
1073  assertNumCommands(aCommand, aMenuID, 0);
1074 }
1075 
1076 // ============================================================================
1077 // COMMAND CALLBACKS
1078 // ============================================================================
1079 
1080 // makes a test trigger callback that sets gTriggered to true
1081 function makeTriggerCallback(aActionID)
1082 {
1083  return function TriggerCallback(context, submenu, commandid, host)
1084  {
1085  gTriggered = true;
1086  assertEqual(commandid,
1087  aActionID,
1088  "CommandId passed to action callback should be " + aActionID +
1089  ", not " + commandid + "!");
1090  };
1091 }
1092 
1093 // makes a test value callback that returns the param callbackval
1094 function makeValueCallback(aActionID, aCallbackval)
1095 {
1096  return function ValueCallback(context, submenu, commandid, host)
1097  {
1098  assertEqual(commandid,
1099  aActionID,
1100  "CommandId should be " + aActionID + "in visible callback, not " +
1101  commandid + "!");
1102  return aCallbackval;
1103  };
1104 }
1105 
1106 // makes a value setter function that sets gValueChecker
1107 function makeValueSetter(aActionID)
1108 {
1109  return function ValueSetter(context, submenu, commandid, host, val)
1110  {
1111  gValueChecker = val;
1112  assertEqual(commandid,
1113  aActionID,
1114  "CommandId should be " + aActionID + "in visible callback, not " +
1115  commandid + "!");
1116  };
1117 }
1118 
1119 // makes an instantiation callback that is used by custom commands
1120 function makeInstantiationCallback(aActionID)
1121 {
1122  return function InstantiationCallback(context, submenu, commandid, host, doc)
1123  {
1124  assertTrue((doc instanceof Ci.nsIDOMDocument),
1125  "instantiateCustomCommand did not forward the document");
1126  assertEqual(commandid,
1127  aActionID,
1128  "CommandId should be " + aActionID + " in instantiation callback" +
1129  ", not " + commandid + "!");
1130  return dummyNode;
1131  };
1132 }
1133 
1134 // makes a refresh callback that is used by custom commands
1135 function makeRefreshCallback(aActionID)
1136 {
1137  return function RefreshCallback(context, submenu, commandid, host, element)
1138  {
1139  assertEqual(commandid,
1140  aActionID,
1141  "CommandId should be " + aActionID + " in instantiation callback" +
1142  ", not " + commandid + "!");
1143  assertTrue((element instanceof Ci.nsIDOMNode),
1144  "refreshCustomCommand did not trigger a refresh or failed to " +
1145  "forward a DOM node");
1146  gRefreshed = true;
1147  };
1148 }
1149 
1150 // ============================================================================
1151 // ASSERTS
1152 // ============================================================================
1153 
1154 /* This function compares the currently active gActiveCommandLog with the contents
1155  * of aCommandObject to ensure that all subobjects are complete and were
1156  * added correctly.
1157  */
1158 function assertLog(aCommandObject)
1159 {
1160  // we handle the gActiveCommandLog in reverse order because we can
1161  for (var j = gActiveCommandLog.length-1; j >= 0; j--)
1162  {
1163  var checkObject = gActiveCommandLog[j];
1164  // make sure a submenuid is specified. null refers to the root
1165  if (typeof(checkObject.CHECK_SUBMENUID) == "undefined")
1166  checkObject.CHECK_SUBMENUID = null;
1167 
1168  /* make sure a host string is specifed. This is largely unimportant for our
1169  * operations */
1170  if (typeof(checkObject.CHECK_HOST) == "undefined")
1171  checkObject.CHECK_HOST = "";
1172 
1173  /* all subobjects must have an id, and we need to confirm that the
1174  * type of subobject is what we think it is */
1175  assertType(aCommandObject, checkObject, j);
1176  assertId(aCommandObject, checkObject, j);
1177 
1178  /* only separators, custom objects, and subcommands don't have labels
1179  * and tooltips */
1180  if (checkObject.CHECK_FLAG != FLAG.SEPARATOR &&
1181  checkObject.CHECK_FLAG != FLAG.CUSTOM &&
1182  checkObject.CHECK_FLAG != FLAG.COMMAND)
1183  {
1184  assertLabel(aCommandObject, checkObject, j);
1185  assertTooltip(aCommandObject, checkObject, j);
1186  }
1187 
1188  /* perform more specialized checking depending on the type of object
1189  * being checked */
1190  switch(checkObject.CHECK_FLAG)
1191  {
1192  case FLAG.ACTION:
1193  /* for actions we check the ui callbacks (like the action trigger) and
1194  * the keyboard command shortcuts */
1195  assertUICallbacks(aCommandObject, checkObject, j);
1196  assertShortcuts(aCommandObject, checkObject, j);
1197  break;
1198  case FLAG.FLAG:
1199  // for flags we check the get value returned by the value callback
1200  assertValueCallback(aCommandObject, checkObject, j);
1201  break;
1202  case FLAG.VALUE:
1203  // for values we check the value getter and setters
1204  assertValueCallback(aCommandObject, checkObject, j);
1205  assertValueSetter(aCommandObject, checkObject, j);
1206  break;
1207  case FLAG.CHOICEMENU:
1208  // jhawk we probably also need a getCommandChoiceItem check,
1209  // but I can't figure out how that works
1210 
1211  // if a choice menu has choicemenuitems, we check those too
1212  if ("CHECK_CHOICEMENUITEMS" in checkObject)
1213  {
1214  assertChoiceMenuItems(aCommandObject, checkObject, j);
1215  }
1216  break;
1217  case FLAG.CUSTOM:
1218  // for custom objects we check the instantiate and refresh callbacks
1219  assertInstantiate(aCommandObject, checkObject, j);
1220  assertRefresh(aCommandObject, checkObject, j);
1221  break;
1222  case FLAG.COMMAND:
1223  // for subcommands we check that all subactions are sane as well
1224  assertSubActions(aCommandObject, checkObject, j);
1225  break;
1226  case FLAG.SEPARATOR:
1227  // separators only really have an id and type so this is checked already
1228  case FLAG.SUBMENU:
1229  // submenus only have id, type, label, and tooltip so this is checkalready
1230  case FLAG.EXISTING:
1231  // we don't know enough about existing objects to check them properly
1232  default:
1233  break;
1234  }
1235  }
1236 }
1237 
1238 //-----------------------------------------------------------------------------
1239 /* The following functions check that the X attribute in the
1240  * param log object, aCheckObject, matches the X attribute of the subobject
1241  * of aCommandObject at index.
1242  * X is specified in the function name of the form assertX
1243  */
1244 function assertType(aCommandObject, aCheckObject, aIndex)
1245 {
1246  // we can't check properties of an existing command, because we don't
1247  // know what they should be.
1248  if (aCheckObject.CHECK_FLAG == FLAG.EXISTING)
1249  {
1250  return;
1251  }
1252  var type = aCommandObject.getCommandType(aCheckObject.CHECK_SUBMENUID,
1253  aIndex,
1254  aCheckObject.CHECK_HOST);
1255  assertEqual(type,
1256  aCheckObject.CHECK_FLAG,
1257  "Type should be " + aCheckObject.CHECK_FLAG + ", not " +
1258  type);
1259 }
1260 function assertId(aCommandObject, aCheckObject, aIndex)
1261 {
1262  // we can't check properties of an existing command, because we don't
1263  // know what they should be.
1264  if (aCheckObject.CHECK_FLAG == FLAG.EXISTING)
1265  {
1266  return;
1267  }
1268  var id = aCommandObject.getCommandId(aCheckObject.CHECK_SUBMENUID,
1269  aIndex,
1270  aCheckObject.CHECK_HOST);
1271  assertEqual(id,
1272  aCheckObject.CHECK_ID,
1273  "Id should be " + aCheckObject.CHECK_ID + ", not " + id);
1274 }
1275 function assertLabel(aCommandObject, aCheckObject, aIndex)
1276 {
1277  // we can't check properties of an existing command, because we don't
1278  // know what they should be.
1279  if (aCheckObject.CHECK_FLAG == FLAG.EXISTING)
1280  {
1281  return;
1282  }
1283  var label = aCommandObject.getCommandText(aCheckObject.CHECK_SUBMENUID,
1284  aIndex,
1285  aCheckObject.CHECK_HOST);
1286  assertEqual(label,
1287  aCheckObject.CHECK_LABEL,
1288  "Label should be " + aCheckObject.CHECK_LABEL + ", not " +
1289  label);
1290 
1291 }
1292 function assertTooltip(aCommandObject, aCheckObject, aIndex)
1293 {
1294  // we can't check properties of an existing command, because we don't
1295  // know what they should be.
1296  if (aCheckObject.CHECK_FLAG == FLAG.EXISTING)
1297  {
1298  return;
1299  }
1300  var tooltip = aCommandObject.getCommandToolTipText(aCheckObject.CHECK_SUBMENUID,
1301  aIndex,
1302  aCheckObject.CHECK_HOST);
1303  assertEqual(tooltip,
1304  aCheckObject.CHECK_TOOLTIP,
1305  "Tooltip should be " + aCheckObject.CHECK_TOOLTIP + ", not " +
1306  tooltip);
1307 }
1308 
1309 //-----------------------------------------------------------------------------
1310 
1311 /* Checks that the enabled and visible callbacks for the subobject of
1312  * aCommandObject at aIndex return the correct values as specified by
1313  * aCheckObject */
1314 function assertUICallbacks(aCommandObject, aCheckObject, aIndex)
1315 {
1316  if ("CHECK_VIS" in aCheckObject)
1317  {
1318  var visible = aCommandObject.getCommandVisible(aCheckObject.CHECK_SUBMENUID,
1319  aIndex,
1320  aCheckObject.CHECK_HOST);
1321  assertEqual(visible,
1322  aCheckObject.CHECK_VIS,
1323  "getCommandVisible should have returned " + aCheckObject.CHECK_VIS +
1324  " instead of " + visible + "!");
1325  }
1326 
1327  if ("CHECK_ENABLED" in aCheckObject)
1328  {
1329  var enabled = aCommandObject.getCommandEnabled(aCheckObject.CHECK_SUBMENUID,
1330  aIndex,
1331  aCheckObject.CHECK_HOST);
1332  assertEqual(enabled,
1333  aCheckObject.CHECK_ENABLED,
1334  "getCommandEnabled should have returned " +
1335  aCheckObject.CHECK_ENABLED + " instead of " + enabled + "!");
1336  }
1337 }
1338 
1339 /* Checks that the keyboard shortcuts for the subobject of aCommandObject at
1340  * aIndex are correctly reported, matching the values in aCheckObject */
1341 function assertShortcuts(aCommandObject, aCheckObject, aIndex)
1342 {
1343  if ("CHECK_SHORTCUT" in aCheckObject)
1344  {
1345  var key = aCommandObject.getCommandShortcutKey(aCheckObject.CHECK_SUBMENUID,
1346  aIndex,
1347  aCheckObject.CHECK_HOST);
1348  var code = aCommandObject.getCommandShortcutKeycode
1349  (aCheckObject.CHECK_SUBMENUID,
1350  aIndex,
1351  aCheckObject.CHECK_HOST);
1352  var mod = aCommandObject.getCommandShortcutModifiers
1353  (aCheckObject.CHECK_SUBMENUID,
1354  aIndex,
1355  aCheckObject.CHECK_HOST);
1356  var local = aCommandObject.getCommandShortcutLocal
1357  (aCheckObject.CHECK_SUBMENUID,
1358  aIndex,
1359  aCheckObject.CHECK_HOST);
1360  assertEqual(key,
1361  "key" + aIndex,
1362  "getCommandShortcutKey should have returned " +
1363  ("key" + aIndex) + ", instead of " + key + "!");
1364  assertEqual(code,
1365  "keycode" + aIndex,
1366  "getCommandShortcutKeycode should have returned " +
1367  ("keycode" + aIndex) + ", instead of " + code + "!");
1368  assertEqual(mod,
1369  "modifier" + aIndex,
1370  "getCommandShortcutModifiers should have returned " +
1371  ("modifier" + aIndex) + ", instead of " + mod + "!");
1372  assertEqual(local,
1373  aCheckObject.CHECK_SHORTCUT,
1374  "getCommandShortcutLocal should have returned " +
1375  aCheckObject.CHECK_SHORTCUT + ", instead of " + local+ "!");
1376  }
1377 }
1378 
1379 /* Checks that the get value callback for the subobject of aCommandObject at
1380  * aIndex returns the correct value, matching the value saved in aCheckObject.
1381  * The value callback was specified by makeValueCallback */
1382 function assertValueCallback(aCommandObject, aCheckObject, aIndex)
1383 {
1384  var getval = aCommandObject.getCommandValue(aCheckObject.CHECK_SUBMENUID,
1385  aIndex,
1386  aCheckObject.CHECK_HOST);
1387  /* Some callbacks are setup to return booleans while others return strings
1388  * This check can handle both if we stringify any boolean returned */
1389  getval = String(getval);
1390  aCheckObject.CHECK_VALUECALLBACK = String(aCheckObject.CHECK_VALUECALLBACK);
1391 
1392  assertEqual(getval,
1393  aCheckObject.CHECK_VALUECALLBACK,
1394  "getCommandValue should have returned " +
1395  aCheckObject.CHECK_VALUECALLBACK + ", instead of " + getval +
1396  "!");
1397 }
1398 
1399 /* Checks that the set value callback for the subobject of aCommandObject at
1400  * aIndex can alter gValueChecker properly.
1401  * The value setter was specified by makeValueSetter */
1402 function assertValueSetter(aCommandObject, aCheckObject, aIndex)
1403 {
1404  gValueChecker = null;
1405  aCommandObject.onCommand(aCheckObject.CHECK_SUBMENUID,
1406  aIndex,
1407  aCheckObject.CHECK_HOST,
1408  aCheckObject.CHECK_ID,
1409  "setvalue" + aCheckObject.CHECK_ID);
1410  assertEqual(gValueChecker,
1411  "setvalue" + aCheckObject.CHECK_ID,
1412  "onCommand failed to trigger a value setter!");
1413 }
1414 
1415 /* Checks the choicemenuitems of a choice menu subobject to ensure that
1416  * they match the recorded values in aCheckObject. */
1417 function assertChoiceMenuItems(aCommandObject, aCheckObject, aIndex)
1418 {
1419  // CHECK_CHOICEMENUITEMS is a mini-log that corresponds to the choicemenuitems
1420  var checkItemArray = aCheckObject.CHECK_CHOICEMENUITEMS;
1421 
1422  // set the submenu to be the choicemenu containing the choicemenuitems
1423  var choiceMenuId = checkItemArray[0].CHECK_SUBMENUID;
1424 
1425  // ensure there are the expected number of choicemenuitems
1426  assertNumCommands(aCommandObject,
1427  choiceMenuId,
1428  checkItemArray.length);
1429 
1430  // use the checkItemArray minilog to check the contents of the choicemenuitems
1431  for (var i = 0; i < checkItemArray.length; i++)
1432  {
1433  var currItemCheckObject = checkItemArray[i];
1434  assertType(aCommandObject, currItemCheckObject, i);
1435  assertId(aCommandObject, currItemCheckObject, i);
1436  assertLabel(aCommandObject, currItemCheckObject, i);
1437  assertTooltip(aCommandObject, currItemCheckObject, i);
1438  assertTriggerCallback(aCommandObject, currItemCheckObject, i);
1439  }
1440 }
1441 
1442 /* Checks the actions appended to a sub-sbIPlaylistCommands object, itself under
1443  * aCommandObject at aIndex. */
1444 function assertSubActions(aCommandObject, aCheckObject, aIndex)
1445 {
1446  var subcmd = aCommandObject.getCommandSubObject(aCheckObject.CHECK_SUBMENUID,
1447  aIndex,
1448  aCheckObject.CHECK_HOST);
1449  assertTrue((subcmd),
1450  "getCommandSubObject should not have returned null!");
1451 
1452  // CHECK_SUBACTIONS is a mini-log that corresponds to the subactions
1453  var checkItemArray = aCheckObject.CHECK_SUBACTIONS;
1454 
1455  // ensure there are the expected number of actions under the subcommand
1456  assertNumCommands(subcmd,
1457  null,
1458  checkItemArray.length);
1459 
1460  // use the checkItemArray minilog to check the contents of the subactions
1461  for (var i = 0; i < aCheckObject.CHECK_SUBACTIONS; i++)
1462  {
1463  assertType(subcmd, aCheckObject.CHECK_SUBACTIONS[i], i);
1464  assertId(subcmd, aCheckObject.CHECK_SUBACTIONS[i], i);
1465  assertLabel(subcmd, aCheckObject.CHECK_SUBACTIONS[i], i);
1466  assertTooltip(subcmd, aCheckObject.CHECK_SUBACTIONS[i], i);
1467  }
1468 }
1469 
1470 /* Checks that the action trigger callback for the subobject of aCommandObject
1471  * at aIndex can alter gTriggered properly.
1472  * The trigger callback was specified by makeTriggerCallback */
1473 function assertTriggerCallback(aCommandObject, aCheckObject, aIndex)
1474 {
1475  gTriggered = false;
1476  var host = (typeof(aCheckObject.CHECK_HOST) == "undefined") ?
1477  "" : aCheckObject.CHECK_HOST;
1478  var unusedVal = null;
1479  aCommandObject.onCommand(aCheckObject.CHECK_SUBMENUID,
1480  aIndex,
1481  host,
1482  aCheckObject.CHECK_ID,
1483  unusedVal);
1484  assertTrue(gTriggered, "onCommand failed to trigger an action!");
1485 }
1486 
1487 /* Checks that the instantiation callback for the subobject of aCommandObject
1488  * at aIndex returns the dummy node.
1489  * The instantiation callback was specified by makeInstantiationCallback */
1490 function assertInstantiate(aCommandObject, aCheckObject, aIndex)
1491 {
1492  var element = aCommandObject.instantiateCustomCommand
1493  (aCheckObject.CHECK_SUBMENUID,
1494  aIndex,
1495  aCheckObject.CHECK_HOST,
1496  aCheckObject.CHECK_ID,
1497  dummyDocument);
1498  element = element.QueryInterface(Ci.nsIDOMNode);
1499  assertTrue((element),
1500  "instantiateCustomCommand should have returned dummyNode!");
1501 }
1502 
1503 /* Checks that the refresh callback for the subobject of aCommandObject
1504  * at aIndex returns the can correctly modify gRefreshed.
1505  * The instantiation callback was specified by makeRefreshCallback */
1506 function assertRefresh(aCommandObject, aCheckObject, aIndex)
1507 {
1508  gRefreshed = false;
1509  aCommandObject.refreshCustomCommand(aCheckObject.CHECK_SUBMENUID,
1510  aIndex,
1511  aCheckObject.CHECK_HOST,
1512  aCheckObject.CHECK_ID,
1513  dummyNode);
1514  assertTrue(gRefreshed,
1515  "refreshCustomCommand did not trigger a refresh!");
1516 }
1517 
1518 /* Checks that the number of commands in cmds, under the submenu specified by
1519  * aMenuID, is equal to the param num */
1520 function assertNumCommands(aCmds, aMenuID, aNum)
1521 {
1522  // the host string, "test", is unimportant here
1523  var actual = aCmds.getNumCommands(aMenuID, "test");
1524  assertEqual(actual,
1525  aNum,
1526  "Number of commands should be " + aNum + ", not " + actual + "!");
1527 }
1528 
1529 // ============================================================================
1530 // UTILS
1531 // ============================================================================
1532 
1533 /* Utility method to display a log message that is informed of the submenu
1534  * that is currently relevant */
1535 function _log(aMsg, aMenuID)
1536 {
1537  log(gTestPrefix + " - " + aMsg + (aMenuID ? " (" + aMenuID + ")" : "") );
1538 }
1539 
1540 /* The correct log to use depends on the current submenu being investigated.
1541  * Usually, the gRootLog is the correct one (used whenever not within a
1542  * another commands submenu), but if we are in a submenu this function
1543  * finds the correct log and makes it the active one.
1544  */
1545 function setLog(aMenuID)
1546 {
1547  if (aMenuID == null)
1548  {
1549  gActiveCommandLog = gRootLog;
1550  }
1551  else {
1552  for (var i = 0; i < gActiveCommandLog.length; i++)
1553  {
1554  if (gActiveCommandLog[i].CHECK_ID == aMenuID)
1555  {
1556  if ((typeof(gActiveCommandLog[i].SUBMENU_LOG) == "undefined"))
1557  {
1558  gActiveCommandLog[i].SUBMENU_LOG = new Array();
1559  }
1560  gActiveCommandLog = gActiveCommandLog[i].SUBMENU_LOG;
1561  }
1562  }
1563  }
1564 }
1565 
1566 /* This is a utility function to uniquely generate an id, label, and tooltip for
1567  * a new command subobject. The attributes are returned in an array of the form
1568  * [id, label, tooltip]. */
1569 function getCommandData(type)
1570 {
1571  var id = type + gIDCounter + "id";
1572  var label = type + gIDCounter + "label";
1573  var tooltip = type + gIDCounter + "tooltip"
1574  gIDCounter++;
1575 
1576  return [id, label, tooltip];
1577 }
const PlaylistCommandsBuilder
function testCommandShortcut(aCommand, aMenuID, aTestLength, aNumSubCommands)
var dummyDocument
function testInsertActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
var gActiveCommandLog
function ExecuteInstructions(aCommand, aInstructions, aMenuID)
function fail(aMessage)
function makeInstantiationCallback(aActionID)
menuItem id
Definition: FeedWriter.js:971
function makeValueSetter(aActionID)
function completeFunctionName(aType, aFunctionName)
function doc() browser.contentDocument
function assertTooltip(aCommandObject, aCheckObject, aIndex)
function log(s)
function assertValueSetter(aCommandObject, aCheckObject, aIndex)
function testTriggerActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
function testAppendAndInsertAllTypes(aCommand, aMenuID, aTestLength, aNumSubCommands)
function assertId(aCommandObject, aCheckObject, aIndex)
var gTriggered
function assertSubActions(aCommandObject, aCheckObject, aIndex)
sbOSDControlService prototype QueryInterface
function assertTrue(aTest, aMessage)
function assertEqual(aExpected, aActual, aMessage)
const SEPARATOR
function assertShortcuts(aCommandObject, aCheckObject, aIndex)
function makeTriggerCallback(aActionID)
function assertTriggerCallback(aCommandObject, aCheckObject, aIndex)
function assertChoiceMenuItems(aCommandObject, aCheckObject, aIndex)
function getCommandData(type)
var gRootLog
this _contentSandbox label
Definition: FeedWriter.js:814
gTestRoot append("_tests")
function num(elem, prop)
function assertLabel(aCommandObject, aCheckObject, aIndex)
function assertUICallbacks(aCommandObject, aCheckObject, aIndex)
this _dialogInput val(dateText)
grep callback
function insert(dbq, size, name)
function testSubmenus(aCommand, aMenuID, aTestLength, aNumSubCommands)
function assertType(aCommandObject, aCheckObject, aIndex)
var gValueChecker
function assertNumCommands(aCmds, aMenuID, aNum)
var gIDCounter
function testAppendInsertRemove(aCommand, aMenuID, aTestLength, aNumSubCommands)
return null
Definition: FeedWriter.js:1143
function addChoiceMenuItems(aCommand, aChoiceMenuId, aCheckObject)
function assertValueCallback(aCommandObject, aCheckObject, aIndex)
var gTestPrefix
var dummyNode
var gRefreshed
function _log(aMsg, aMenuID)
function makeValueCallback(aActionID, aCallbackval)
function assertLog(aCommandObject)
function testCommandCallbacksAndShortcuts(aCommand, aMenuID, aTestLength, aNumSubCommands)
const Ci
function testRemoveSubobjects(aCommand, aMenuID, aTestLength, aNumSubCommands)
function doFail(text)
function makeRefreshCallback(aActionID)
function testAppendActions(aCommand, aMenuID, aTestLength, aNumSubCommands)
function testInitShutdownCB(aCommand)
function assertRefresh(aCommandObject, aCheckObject, aIndex)
function testCommandVisibleAndEnabled(aCommand, aMenuID, aTestLength, aNumSubCommands)
function setLog(aMenuID)
observe data
Definition: FeedWriter.js:1329
var actual
_getSelectedPageStyle s i
function assertInstantiate(aCommandObject, aCheckObject, aIndex)