diff --git a/includes/common.inc b/includes/common.inc
index 581e1150c0856812f8fe77a85edca3b5b98450c5..dd4a2fd8799bc96e3cc17ee455cd2ac85b99dcf4 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -6459,7 +6459,7 @@ function drupal_common_theme() {
       'variables' => array('type' => MARK_NEW),
     ),
     'item_list' => array(
-      'variables' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => array()),
+      'variables' => array('items' => array(), 'title' => '', 'type' => 'ul', 'attributes' => array()),
     ),
     'more_help_link' => array(
       'variables' => array('url' => NULL),
diff --git a/includes/theme.inc b/includes/theme.inc
index 1f991ea6f19d579f3c47fb02ccb07c7ba2738e7c..b85a35cfb56262b31aa95cf4567f5b404dd88d07 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -1793,12 +1793,14 @@ function theme_mark($variables) {
  *
  * @param $variables
  *   An associative array containing:
- *   - items: An array of items to be displayed in the list. If an item is a
- *     string, then it is used as is. If an item is an array, then the "data"
- *     element of the array is used as the contents of the list item. If an item
- *     is an array with a "children" element, those children are displayed in a
- *     nested list. All other elements are treated as attributes of the list
- *     item element.
+ *   - items: A list of items to render. String values are rendered as is. Each
+ *     item can also be an associative array containing:
+ *     - data: The string content of the list item.
+ *     - children: A list of nested child items to render that behave
+ *       identically to 'items', but any non-numeric string keys are treated as
+ *       HTML attributes for the child list that wraps 'children'.
+ *     Any other key/value pairs are used as HTML attributes for the list item
+ *     in 'data'.
  *   - title: The title of the list.
  *   - type: The type of list to return (e.g. "ul", "ol").
  *   - attributes: The attributes applied to the list element.
@@ -1807,51 +1809,69 @@ function theme_item_list($variables) {
   $items = $variables['items'];
   $title = $variables['title'];
   $type = $variables['type'];
-  $attributes = $variables['attributes'];
+  $list_attributes = $variables['attributes'];
 
-  $output = '<div class="item-list">';
-  if (isset($title)) {
-    $output .= '<h3>' . $title . '</h3>';
-  }
+  $output = '';
+  if ($items) {
+    $output .= '<' . $type . drupal_attributes($list_attributes) . '>';
 
-  if (!empty($items)) {
-    $output .= "<$type" . drupal_attributes($attributes) . '>';
     $num_items = count($items);
-    foreach ($items as $i => $item) {
+    $i = 0;
+    foreach ($items as $key => $item) {
+      $i++;
       $attributes = array();
-      $children = array();
-      $data = '';
+
       if (is_array($item)) {
-        foreach ($item as $key => $value) {
-          if ($key == 'data') {
-            $data = $value;
-          }
-          elseif ($key == 'children') {
-            $children = $value;
-          }
-          else {
-            $attributes[$key] = $value;
+        $value = '';
+        if (isset($item['data'])) {
+          $value .= $item['data'];
+        }
+        $attributes = array_diff_key($item, array('data' => 0, 'children' => 0));
+
+        // Append nested child list, if any.
+        if (isset($item['children'])) {
+          // HTML attributes for the outer list are defined in the 'attributes'
+          // theme variable, but not inherited by children. For nested lists,
+          // all non-numeric keys in 'children' are used as list attributes.
+          $child_list_attributes = array();
+          foreach ($item['children'] as $child_key => $child_item) {
+            if (is_string($child_key)) {
+              $child_list_attributes[$child_key] = $child_item;
+              unset($item['children'][$child_key]);
+            }
           }
+          $value .= theme('item_list', array(
+            'items' => $item['children'],
+            'type' => $type,
+            'attributes' => $child_list_attributes,
+          ));
         }
       }
       else {
-        $data = $item;
-      }
-      if (count($children) > 0) {
-        // Render nested list.
-        $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
+        $value = $item;
       }
-      if ($i == 0) {
+
+      $attributes['class'][] = ($i % 2 ? 'odd' : 'even');
+      if ($i == 1) {
         $attributes['class'][] = 'first';
       }
-      if ($i == $num_items - 1) {
+      if ($i == $num_items) {
         $attributes['class'][] = 'last';
       }
-      $output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
+
+      $output .= '<li' . drupal_attributes($attributes) . '>' . $value . '</li>';
     }
     $output .= "</$type>";
   }
-  $output .= '</div>';
+
+  // Only output the list container and title, if there are any list items.
+  if ($output !== '') {
+    if ($title !== '') {
+      $title = '<h3>' . $title . '</h3>';
+    }
+    $output = '<div class="item-list">' . $title . $output . '</div>';
+  }
+
   return $output;
 }
 
diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test
index 53557e361a0381113a1a21b0f61ec3ee548b2276..d064359f253a2fc268a0329c921008a308057c24 100644
--- a/modules/simpletest/tests/theme.test
+++ b/modules/simpletest/tests/theme.test
@@ -164,30 +164,101 @@ class ThemeTableUnitTest extends DrupalWebTestCase {
 }
 
 /**
- * Unit tests for theme_item_list().
+ * Tests for common theme functions.
  */
-class ThemeItemListUnitTest extends DrupalWebTestCase {
+class ThemeFunctionsTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
-      'name' => 'Theme item list',
-      'description' => 'Test the theme_item_list() function.',
+      'name' => 'Theme functions',
+      'description' => 'Tests common theme functions.',
       'group' => 'Theme',
     );
   }
 
   /**
-   * Test nested list rendering.
+   * Tests theme_item_list().
    */
-  function testNestedList() {
-    $items = array('a', array('data' => 'b', 'children' => array('c', 'd')), 'e');
-    $expected = '<div class="item-list"><ul><li class="first">a</li>
-<li>b<div class="item-list"><ul><li class="first">c</li>
-<li class="last">d</li>
-</ul></div></li>
-<li class="last">e</li>
-</ul></div>';
-    $output = theme('item_list', array('items' => $items));
-    $this->assertIdentical($expected, $output, 'Nested list is rendered correctly.');
+  function testItemList() {
+    // Verify that empty variables produce no output.
+    $variables = array();
+    $expected = '';
+    $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates no output.');
+
+    $variables = array();
+    $variables['title'] = 'Some title';
+    $expected = '';
+    $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback with title generates no output.');
+
+    // Verify nested item lists.
+    $variables = array();
+    $variables['title'] = 'Some title';
+    $variables['attributes'] = array(
+      'id' => 'parentlist',
+    );
+    $variables['items'] = array(
+      'a',
+      array(
+        'data' => 'b',
+        'children' => array(
+          'c',
+          // Nested children may use additional attributes.
+          array(
+            'data' => 'd',
+            'class' => array('dee'),
+          ),
+          // Any string key is treated as child list attribute.
+          'id' => 'childlist',
+        ),
+        // Any other keys are treated as item attributes.
+        'id' => 'bee',
+      ),
+      array(
+        'data' => 'e',
+        'id' => 'E',
+      ),
+    );
+    $inner = '<div class="item-list"><ul id="childlist">';
+    $inner .= '<li class="odd first">c</li>';
+    $inner .= '<li class="dee even last">d</li>';
+    $inner .= '</ul></div>';
+
+    $expected = '<div class="item-list">';
+    $expected .= '<h3>Some title</h3>';
+    $expected .= '<ul id="parentlist">';
+    $expected .= '<li class="odd first">a</li>';
+    $expected .= '<li id="bee" class="even">b' . $inner . '</li>';
+    $expected .= '<li id="E" class="odd last">e</li>';
+    $expected .= '</ul></div>';
+
+    $this->assertThemeOutput('item_list', $variables, $expected);
+  }
+
+  /**
+   * Asserts themed output.
+   *
+   * @param $callback
+   *   The name of the theme function to invoke; e.g. 'links' for theme_links().
+   * @param $variables
+   *   An array of variables to pass to the theme function.
+   * @param $expected
+   *   The expected themed output string.
+   * @param $message
+   *   (optional) An assertion message.
+   */
+  protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '') {
+    $output = theme($callback, $variables);
+    $this->verbose('Variables:' . '<pre>' .  check_plain(var_export($variables, TRUE)) . '</pre>'
+      . '<hr />' . 'Result:' . '<pre>' .  check_plain(var_export($output, TRUE)) . '</pre>'
+      . '<hr />' . 'Expected:' . '<pre>' .  check_plain(var_export($expected, TRUE)) . '</pre>'
+      . '<hr />' . $output
+    );
+    if (!$message) {
+      $message = '%callback rendered correctly.';
+    }
+    $message = t($message, array('%callback' => 'theme_' . $callback . '()'));
+    $this->assertIdentical($output, $expected, $message);
   }
 }