CB-7459 Allow auto tests to be run for specific plugin(s)

* On the "Auto Tests" page for plugins, generate a list of checkboxes to select
  each of the detected plugins.  These are initially checked based on "enabled"
  status of each plugin.

* Subsequent runs (i.e. using the existing <Again> button) will reflect any user
  changes to the selected tests

* User selections are persisted to localStorage, to remember the settings for
  subsequent runs of the app.  The enabling of test modules is encapsulated in
  the framework, so both user and console/programmatic selection are retained the
  same way via localStorage

* When the page is initially loaded, keep the existing behaviour, where all
  enabled plugin tests are automatically run

* UI enhancements to show/hide list of tests, add Select/Unselect All buttons,
  and use shorter test names
diff --git a/www/assets/main.css b/www/assets/main.css
index 455b1de..438ce68 100644
--- a/www/assets/main.css
+++ b/www/assets/main.css
@@ -100,3 +100,24 @@
   overflow: auto;
   -webkit-overflow-scrolling: touch;
 }
+
+#test-enablers-container {
+  margin: 10px 0px;
+}
+
+#test-expander {
+  margin: 5px;
+  text-decoration: underline;
+}
+
+#test-list {
+  display: none;
+}
+
+#test-list.expanded {
+  display: inherit;
+}
+
+#test-list label {
+  display: block;
+}
diff --git a/www/main.js b/www/main.js
index 79eaad5..a2412b0 100644
--- a/www/main.js
+++ b/www/main.js
@@ -21,6 +21,8 @@
 
 'use strict';
 
+var autoFirstTime = true;
+
 /******************************************************************************/
 
 function getMode(callback) {
@@ -33,7 +35,7 @@
     'main': runMain,
     'auto': runAutoTests,
     'manual': runManualTests
-  }
+  };
   if (!handlers.hasOwnProperty(mode)) {
     console.error("Unsupported mode: " + mode);
     console.error("Defaulting to 'main'");
@@ -117,14 +119,14 @@
       medic.log.apply(medic, arguments);
       appendToOnscreenLog(type, arguments);
       setLogVisibility(true);
-    }
+    };
   }
 
   window.console = {
     log: createCustomLogger('log'),
     warn: createCustomLogger('warn'),
     error: createCustomLogger('error'),
-  }
+  };
 };
 
 exports.unwrapConsole = function() {
@@ -149,21 +151,177 @@
 }
 
 /******************************************************************************/
+
+function setupAutoTestsEnablers(cdvtests) {
+  
+  var enablerList = createEnablerList();
+
+  // Iterate over all the registered test modules
+  Object.keys(cdvtests.tests).forEach(function(api) {
+    var testModule = cdvtests.tests[api];
+    
+    if (!testModule.hasOwnProperty('defineAutoTests'))
+      return;
+    
+    // For "standard" plugins remove the common/repetitive bits of
+    // the api key, for use as the title.  For third-party plugins, the full
+    // api will be used as the title
+    var title = api.replace(/org\.apache\.cordova\./i, '').replace(/\.tests.tests/i, '');
+
+    createEnablerCheckbox(api, title, testModule.getEnabled(), enablerList.id, toggleTestHandler);
+  });
+}
+
 /******************************************************************************/
+
+function createEnablerList() {
+  var buttons = document.getElementById('buttons');
+
+  var enablerContainer = document.createElement('div');
+  enablerContainer.id = 'test-enablers-container';
+  
+  var expander = document.createElement('span');
+  expander.id = 'test-expander';
+  expander.innerText = 'Show/hide tests to be run';
+  expander.onclick = toggleEnablerVisibility;
+
+  var enablerList = document.createElement('div');
+  enablerList.id = "test-list";
+  
+  var checkButtonBar = document.createElement('ul');
+  checkButtonBar.classList.add('topcoat-button-bar');
+  
+  for (var i = 0; i < 2; i++)
+  {
+    var barItem = document.createElement('li');
+    barItem.classList.add('topcoat-button-bar__item');
+    
+    var link = document.createElement('a');
+    var selected = (i === 0);
+    link.classList.add('topcoat-button-bar__button');
+    link.innerText = selected ? 'Check all' : 'Uncheck all';
+    link.href = null;
+    link.onclick = (function(select) {
+      return function(e) {
+        e.preventDefault();
+        toggleSelected(enablerList.id, select);
+        return false;
+      };
+    })(selected);
+
+    barItem.appendChild(link);
+    checkButtonBar.appendChild(barItem);
+  }
+
+  enablerList.appendChild(checkButtonBar);
+  
+  enablerContainer.appendChild(expander);
+  enablerContainer.appendChild(enablerList);
+  
+  buttons.appendChild(enablerContainer);
+  
+  return enablerList;
+}
+
+/******************************************************************************/
+
+function toggleSelected(containerId, newCheckedValue) {
+  var container = document.getElementById(containerId);
+  
+  var cbs = container.getElementsByTagName('input');
+  
+  for (var i = 0; i < cbs.length; i++) {
+    if(cbs[i].type === 'checkbox') {
+      cbs[i].checked = newCheckedValue;
+      toggleTestEnabled(cbs[i]);
+    }
+  }    
+}
+
+/******************************************************************************/
+
+function toggleEnablerVisibility() {
+  var enablerList = document.getElementById('test-list');
+  if (enablerList.classList.contains('expanded')) {
+    enablerList.classList.remove('expanded');
+  } else {
+    enablerList.classList.add('expanded');
+  }
+}
+
+/******************************************************************************/
+
+function createEnablerCheckbox(api, title, isEnabled, appendTo, callback) {
+  var container = document.getElementById(appendTo);
+
+  var label = document.createElement('label');
+  label.classList.add('topcoat-checkbox');
+  
+  var checkbox = document.createElement('input');
+  checkbox.type = "checkbox";
+  checkbox.value = api;
+  checkbox.checked = isEnabled;
+  label.htmlFor = checkbox.id = 'enable_' + api;
+
+  checkbox.onchange = function(e) {
+    e.preventDefault();
+    callback(e);
+  };
+
+  var div = document.createElement('div');
+  div.classList.add('topcoat-checkbox__checkmark');
+  
+  var text = document.createElement('span');
+  text.innerText = title;
+  
+  label.appendChild(checkbox);
+  label.appendChild(div);
+  label.appendChild(text);
+
+  container.appendChild(label);
+}
+
+/******************************************************************************/
+
+function toggleTestHandler(event) {
+  var checkbox = event.target;
+  
+  toggleTestEnabled(checkbox);
+}
+
+/******************************************************************************/
+
+function toggleTestEnabled(checkbox) {
+  var cdvtests = cordova.require('org.apache.cordova.test-framework.cdvtests');
+  cdvtests.tests[checkbox.value].setEnabled(checkbox.checked);
+}
+
 /******************************************************************************/
 
 function runAutoTests() {
   setTitle('Auto Tests');
 
-  createActionButton('Again', setMode.bind(null, 'auto'));
+  createActionButton('Run', setMode.bind(null, 'auto'));
   createActionButton('Reset App', location.reload.bind(location));
   createActionButton('Back', setMode.bind(null, 'main'));
 
   var cdvtests = cordova.require('org.apache.cordova.test-framework.cdvtests');
+  cdvtests.init();
+  setupAutoTestsEnablers(cdvtests);
+  
   cdvtests.defineAutoTests();
 
   // Run the tests!
   var jasmineEnv = window.jasmine.getEnv();
+  
+  if (autoFirstTime) {
+    autoFirstTime = false;
+    // Uncomment to skip running of tests on initial load
+    //  - If you're testing a specific plugin, you probably want to uncomment,
+    //    so you don't have to wait for all the tests to run every time
+    //return;
+  }
+  
   jasmineEnv.execute();
 }
 
@@ -181,7 +339,7 @@
     setTitle(title || 'Manual Tests');
     createActionButton('Reset App', location.reload.bind(location));
     createActionButton('Back', setMode.bind(null, 'manual'));
-  }
+  };
   var cdvtests = cordova.require('org.apache.cordova.test-framework.cdvtests');
   cdvtests.defineManualTests(contentEl, beforeEach, createActionButton);
 }
diff --git a/www/tests.js b/www/tests.js
index 1ee1a5e..424e351 100644
--- a/www/tests.js
+++ b/www/tests.js
@@ -23,8 +23,28 @@
 
 exports.tests = Object.create(null);
 
+function TestModule(api) {
+  var name = api;
+  var enabled = true;
+
+  var enabledPref = localStorage.getItem('cordova-tests-enabled-' + name);
+  if (enabledPref)
+  {
+    enabled = (enabledPref == true.toString());
+  }
+  
+  this.getEnabled = function () {
+    return enabled;
+  };
+  
+  this.setEnabled = function (isEnabled) {
+    enabled = isEnabled;
+    localStorage.setItem('cordova-tests-enabled-' + name, enabled);
+  };
+}
+
 function getTestsObject(api) {
-  return exports.tests[api] = exports.tests[api] || { enabled: true };
+  return exports.tests[api] = exports.tests[api] || new TestModule(api);
 }
 
 function requireAllTestModules() {
@@ -83,7 +103,7 @@
   attachJasmineInterfaceToGlobal();
 
   Object.keys(exports.tests).forEach(function(key) {
-    if (!exports.tests[key].enabled)
+    if (!exports.tests[key].getEnabled())
       return;
     if (!exports.tests[key].hasOwnProperty('defineAutoTests'))
       return;
@@ -96,7 +116,7 @@
   detachJasmineInterfaceFromGlobal();
 
   Object.keys(exports.tests).forEach(function(key) {
-    if (!exports.tests[key].enabled)
+    if (!exports.tests[key].getEnabled())
       return;
     if (!exports.tests[key].hasOwnProperty('defineManualTests'))
       return;
@@ -106,3 +126,7 @@
     });
   });
 };
+
+exports.init = function() {
+  requireAllTestModules();
+}