Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.809
diff -u -r1.809 common.inc
--- includes/common.inc	15 Oct 2008 16:05:51 -0000	1.809
+++ includes/common.inc	16 Oct 2008 15:26:13 -0000
@@ -2046,57 +2046,100 @@
  *   settings are required by some modules to function properly. The settings
  *   will be accessible at Drupal.settings.
  *
+ * Examples:
+ * @code
+ *   drupal_add_js('misc/collapse.js');
+ *   drupal_add_js('misc/collapse.js', 'module');
+ *   drupal_add_js('$(document).ready(function(){alert("Hello!");});',
+ *     array('type' => 'inline', 'scope' => 'footer')
+ *   );
+ * @endcode
+ *
  * @param $data
- *   (optional) If given, the value depends on the $type parameter:
+ *   (optional) If given, the value depends on the $options parameter:
  *   - 'core', 'module' or 'theme': Path to the file relative to base_path().
  *   - 'inline': The JavaScript code that should be placed in the given scope.
  *   - 'setting': An array with configuration options as associative array. The
  *       array is directly placed in Drupal.settings. You might want to wrap your
  *       actual configuration settings in another variable to prevent the pollution
  *       of the Drupal.settings namespace.
- * @param $type
- *   (optional) The type of JavaScript that should be added to the page. Allowed
- *   values are 'core', 'module', 'theme', 'inline' and 'setting'. You
- *   can, however, specify any value. It is treated as a reference to a JavaScript
- *   file. Defaults to 'module'.
- * @param $scope
- *   (optional) The location in which you want to place the script. Possible
- *   values are 'header' and 'footer' by default. If your theme implements
- *   different locations, however, you can also use these.
- * @param $defer
- *   (optional) If set to TRUE, the defer attribute is set on the <script> tag.
- *   Defaults to FALSE. This parameter is not used with $type == 'setting'.
- * @param $cache
- *   (optional) If set to FALSE, the JavaScript file is loaded anew on every page
- *   call, that means, it is not cached. Defaults to TRUE. Used only when $type
- *   references a JavaScript file.
- * @param $preprocess
- *   (optional) Should this JS file be aggregated if this
- *   feature has been turned on under the performance section?
+ * @param $options
+ *   (optional) A string defining the type of JavaScript that is being added
+ *   in the $data parameter ('core', 'module', 'theme', 'setting', 'inline'),
+ *   or an array which can have any or all of the following keys (these are
+ *   not valid with type => 'setting'):
+ *   - type
+ *       The type of JavaScript that should be added to the page. Allowed
+ *       values are 'core', 'module', 'theme', 'inline' and 'setting'. Defaults
+ *       to 'module'.
+ *   - scope
+ *       The location in which you want to place the script. Possible
+ *       values are 'header' and 'footer'. If your theme implements different
+ *       locations, however, you can also use these. Defaults to 'header'.
+ *   - defer
+ *       If set to TRUE, the defer attribute is set on the <script> tag.
+ *       Defaults to FALSE. This parameter is not used with 'type' => 'setting'.
+ *   - cache
+ *       If set to FALSE, the JavaScript file is loaded anew on every page
+ *       call, that means, it is not cached. Used only when type references
+ *       a JavaScript file. Defaults to TRUE.
+ *   - preprocess
+ *       Aggregate the JavaScript if the JavaScript optimization setting has
+ *       been toggled in admin/settings/performance. Defaults to TRUE.
+ * @param $reset
+ *   (optional) Resets the currently loaded JavaScript.
  * @return
- *   If the first parameter is NULL, the JavaScript array that has been built so
- *   far for $scope is returned. If the first three parameters are NULL,
- *   an array with all scopes is returned.
+ *   The contructed array of JavaScript files.
+ * @see drupal_get_js()
  */
-function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) {
+function drupal_add_js($data = NULL, $options = NULL, $reset = FALSE) {
   static $javascript = array();
 
-  if (isset($data)) {
+  // Construct the options, taking the defaults into consideration.
+  if (isset($options)) {
+    if (!is_array($options)) {
+      $options = array('type' => $options);
+    }
+  }
+  else {
+    $options = array();
+  }
+  $options += array(
+    'type' => 'module',
+    // Default to a header scope only if we're adding some data.
+    'scope' => isset($data) ? 'header' : NULL,
+    'cache' => TRUE, 
+    'defer' => FALSE,
+    'preprocess' => TRUE
+  );
+  // Preprocess can only be set if caching is enabled.
+  $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE;
+  $type = $options['type'];
+  $scope = $options['scope'];
+  unset($options['type'], $options['scope']);
+
+  // Request made to reset the JavaScript added so far.
+  if ($reset) {
+    $javascript = array();
+  }
 
+  if (isset($data)) {
     // Add jquery.js and drupal.js, as well as the basePath setting, the
     // first time a Javascript file is added.
     if (empty($javascript)) {
-      $javascript['header'] = array(
-        'core' => array(
-          'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
-          'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
-        ),
-        'module' => array(),
-        'theme' => array(),
-        'setting' => array(
-          array('basePath' => base_path()),
-        ),
-        'inline' => array(),
+      $javascript = array(
+        'header' => array(
+          'core' => array(
+            'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
+            'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
+          ),
+          'module' => array(),
+          'theme' => array(),
+          'setting' => array(
+            array('basePath' => base_path()),
+          ),
+          'inline' => array(),
+        )
       );
     }
 
@@ -2113,22 +2156,15 @@
         $javascript[$scope][$type][] = $data;
         break;
       case 'inline':
-        $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer);
+        $javascript[$scope][$type][] = array('code' => $data, 'defer' => $options['defer']);
         break;
       default:
-        // If cache is FALSE, don't preprocess the JS file.
-        $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess));
+        $javascript[$scope][$type][$data] = $options;
     }
   }
 
   if (isset($scope)) {
-
-    if (isset($javascript[$scope])) {
-      return $javascript[$scope];
-    }
-    else {
-      return array();
-    }
+    return isset($javascript[$scope]) ? $javascript[$scope] : array();
   }
   else {
     return $javascript;
@@ -2151,6 +2187,7 @@
  *   JavaScript array for the given scope.
  * @return
  *   All JavaScript code segments and includes for the scope as HTML tags.
+ * @see drupal_add_js()
  */
 function drupal_get_js($scope = 'header', $javascript = NULL) {
   if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
@@ -2158,7 +2195,7 @@
   }
 
   if (!isset($javascript)) {
-    $javascript = drupal_add_js(NULL, NULL, $scope);
+    $javascript = drupal_add_js(NULL, array('scope' => $scope));
   }
 
   if (empty($javascript)) {
Index: includes/batch.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/batch.inc,v
retrieving revision 1.25
diff -u -r1.25 batch.inc
--- includes/batch.inc	14 Oct 2008 11:01:08 -0000	1.25
+++ includes/batch.inc	16 Oct 2008 15:26:12 -0000
@@ -84,7 +84,7 @@
   // error messages. Only safe strings should be passed in to batch_set().
   $current_set = _batch_current_set();
   drupal_set_title($current_set['title'], PASS_THROUGH);
-  drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
+  drupal_add_js('misc/progress.js', array('type' => 'core', 'cache' => FALSE));
 
   $url = url($batch['url'], array('query' => array('id' => $batch['id'])));
   $js_setting = array(
@@ -95,7 +95,7 @@
     ),
   );
   drupal_add_js($js_setting, 'setting');
-  drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
+  drupal_add_js('misc/batch.js', array('type' => 'core', 'cache' => FALSE));
 
   $output = '<div id="progress"></div>';
   return $output;
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.229
diff -u -r1.229 locale.module
--- modules/locale/locale.module	12 Oct 2008 04:30:06 -0000	1.229
+++ modules/locale/locale.module	16 Oct 2008 15:26:13 -0000
@@ -518,10 +518,8 @@
   $dir = file_create_path(variable_get('locale_js_directory', 'languages'));
   $parsed = variable_get('javascript_parsed', array());
 
-  // The first three parameters are NULL in order to get an array with all
-  // scopes. This is necessary to prevent recreation of JS translation files
-  // when new files are added for example in the footer.
-  $javascript = drupal_add_js(NULL, NULL, NULL);
+  // Get an array of all the JavaScript added so far.
+  $javascript = drupal_add_js();
   $files = $new_files = FALSE;
 
   foreach ($javascript as $scope) {
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.8
diff -u -r1.8 common.test
--- modules/simpletest/tests/common.test	15 Oct 2008 16:05:51 -0000	1.8
+++ modules/simpletest/tests/common.test	16 Oct 2008 15:26:13 -0000
@@ -253,6 +253,109 @@
 }
 
 /**
+ * Tests for the JavaScript system.
+ */
+class JavaScriptTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('JavaScript'),
+      'description' => t("Tests the JavaScript system."),
+      'group' => t('System')
+    );
+  }
+  
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    // Reset drupal_add_js() before each test.
+    drupal_add_js(NULL, NULL, TRUE);
+  }
+  
+  /**
+   * Test default JavaScript is empty.
+   */
+  function testDefault() {
+    $this->assertEqual(array(), drupal_add_js(), t('Default JavaScript is empty.'));
+  }
+  
+  /**
+   * Test adding a JavaScript file.
+   */
+  function testAddFile() {
+    drupal_add_js('misc/collapse.js');
+    $javascript = drupal_add_js();
+    $this->assertTrue(array_key_exists('misc/jquery.js', $javascript['header']['core']), t('jQuery is added when a file is added.'));
+    $this->assertTrue(array_key_exists('misc/drupal.js', $javascript['header']['core']), t('Drupal.js is added when file is added.'));
+    $this->assertTrue(array_key_exists('misc/collapse.js', $javascript['header']['module']), t('JavaScript files are correctly added.'));
+    $this->assertEqual(base_path(), $javascript['header']['setting'][0]['basePath'], t('Base path JavaScript setting is correctly set.'));
+  }
+  
+  /**
+   * Test adding settings.
+   */
+  function testAddSetting() {
+    drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting');
+    $javascript = drupal_add_js();
+    $this->assertEqual(280342800, $javascript['header']['setting'][1]['dries'], t('JavaScript setting is set correctly.'));
+    $this->assertEqual('rocks', $javascript['header']['setting'][1]['drupal'], t('The other JavaScript setting is set correctly.'));
+  }
+  
+  /**
+   * Test drupal_get_js() for JavaScript settings.
+   */
+  function testHeaderSetting() {
+    drupal_add_js(array('testSetting' => 'testValue'), 'setting');
+    $javascript = drupal_get_js('header');
+    $this->assertTrue(strpos($javascript, 'basePath') > 0, t('Rendered JavaScript header returns basePath setting.'));
+    $this->assertTrue(strpos($javascript, 'testSetting') > 0, t('Rendered JavaScript header returns custom setting.'));
+    $this->assertTrue(strpos($javascript, 'misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.'));
+  }
+  
+  /**
+   * Test to see if resetting the JavaScript empties the cache.
+   */
+  function testReset() {
+    drupal_add_js('misc/collapse.js');
+    drupal_add_js(NULL, NULL, TRUE);    
+    $this->assertEqual(array(), drupal_add_js(), t('Resetting the JavaScript correctly empties the cache.'));
+  }
+  
+  /**
+   * Test adding inline scripts.
+   */
+  function testAddInline() {
+    $inline = '$(document).ready(function(){});';
+    drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer'));
+    $javascript = drupal_add_js();
+    $this->assertTrue(array_key_exists('misc/jquery.js', $javascript['header']['core']), t('jQuery is added when inline scripts are added.'));
+    $this->assertEqual($inline, $javascript['footer']['inline'][0]['code'], t('Inline JavaScript is correctly added to the footer.'));
+  }
+  
+  /**
+   * Test drupal_get_js() with a footer scope.
+   */
+  function testFooterHTML() {
+    $inline = '$(document).ready(function(){});';
+    drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer'));
+    $javascript = drupal_get_js('footer');
+    $this->assertTrue(strpos($javascript, $inline) > 0, t('Rendered JavaScript footer returns the inline code.'));
+  }
+  
+  /**
+   * Test drupal_add_js() sets preproccess to false when cache is set to false.
+   */
+  function testNoCache() {
+    drupal_add_js('misc/collapse.js', array('cache' => FALSE));
+    $javascript = drupal_add_js();
+    $this->assertTrue(!$javascript['header']['module']['misc/collapse.js']['preprocess'], t('Setting cache to FALSE sets proprocess to FALSE when adding JavaScript.'));
+  }
+}
+
+/**
  * Tests Drupal error and exception handlers.
  */
 class DrupalErrorHandlerUnitTest extends DrupalWebTestCase {
