diff --git a/core/includes/common.inc b/core/includes/common.inc
index c6a29b7..5095384 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -6544,7 +6544,7 @@ function drupal_common_theme() {
       'variables' => array('text' => NULL, 'path' => NULL, 'options' => array()),
     ),
     'links' => array(
-      'variables' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()),
+      'variables' => array('links' => array(), 'attributes' => array('class' => array('links')), 'heading' => array()),
     ),
     'image' => array(
       // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 902907a..1497c62 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1561,73 +1561,80 @@ function theme_link($variables) {
  *     http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
  */
 function theme_links($variables) {
+  global $language_url;
+
   $links = $variables['links'];
   $attributes = $variables['attributes'];
   $heading = $variables['heading'];
-  global $language_url;
   $output = '';
 
-  if (count($links) > 0) {
-    $output = '';
-
-    // Treat the heading first if it is present to prepend it to the
-    // list of links.
-    if (!empty($heading)) {
+  if ($links) {
+    // Prepend the heading to the list, if any.
+    if ($heading) {
+      // Convert a string heading into an array, using a H2 tag by default.
       if (is_string($heading)) {
-        // Prepare the array that will be used when the passed heading
-        // is a string.
-        $heading = array(
-          'text' => $heading,
-          // Set the default level of the heading.
-          'level' => 'h2',
-        );
+        $heading = array('text' => $heading);
       }
-      $output .= '<' . $heading['level'];
-      if (!empty($heading['class'])) {
-        $output .= drupal_attributes(array('class' => $heading['class']));
+      // Merge in default array properties into $heading.
+      $heading += array(
+        'level' => 'h2',
+        'attributes' => array(),
+      );
+      // @todo Remove backwards compatibility for $heading['class'].
+      if (isset($heading['class'])) {
+        $heading['attributes']['class'] = $heading['class'];
       }
-      $output .= '>' . check_plain($heading['text']) . '</' . $heading['level'] . '>';
+
+      $output .= '<' . $heading['level'] . drupal_attributes($heading['attributes']) . '>';
+      $output .= check_plain($heading['text']);
+      $output .= '</' . $heading['level'] . '>';
     }
 
     $output .= '<ul' . drupal_attributes($attributes) . '>';
 
     $num_links = count($links);
-    $i = 1;
-
+    $i = 0;
     foreach ($links as $key => $link) {
-      $class = array($key);
+      $i++;
 
-      // Add first, last and active classes to the list of links to help out themers.
+      $class = array();
+      // Use the array key as class name.
+      $class[] = drupal_html_class($key);
+      // Add odd/even, first, and last classes.
+      $class[] = ($i % 2 ? 'odd' : 'even');
       if ($i == 1) {
         $class[] = 'first';
       }
       if ($i == $num_links) {
         $class[] = 'last';
       }
-      if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
-          && (empty($link['language']) || $link['language']->language == $language_url->language)) {
-        $class[] = 'active';
-      }
-      $output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
+      $item = '';
 
+      // Handle links.
       if (isset($link['href'])) {
+        $is_current_path = ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()));
+        $is_current_language = (empty($link['language']) || $link['language']->language == $language_url->language);
+        if ($is_current_path && $is_current_language) {
+          $class[] = 'active';
+        }
         // Pass in $link as $options, they share the same keys.
-        $output .= l($link['title'], $link['href'], $link);
+        $item = l($link['title'], $link['href'], $link);
       }
-      elseif (!empty($link['title'])) {
-        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes.
-        if (empty($link['html'])) {
-          $link['title'] = check_plain($link['title']);
-        }
-        $span_attributes = '';
-        if (isset($link['attributes'])) {
-          $span_attributes = drupal_attributes($link['attributes']);
-        }
-        $output .= '<span' . $span_attributes . '>' . $link['title'] . '</span>';
+      // Handle title-only text items.
+      else {
+        // Merge in default array properties into $link.
+        $link += array(
+          'html' => FALSE,
+          'attributes' => array(),
+        );
+        $item .= '<span' . drupal_attributes($link['attributes']) . '>';
+        $item .= ($link['html'] ? $link['title'] : check_plain($link['title']));
+        $item .= '</span>';
       }
 
-      $i++;
-      $output .= "</li>\n";
+      $output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
+      $output .= $item;
+      $output .= '</li>';
     }
 
     $output .= '</ul>';
diff --git a/core/modules/simpletest/tests/theme.test b/core/modules/simpletest/tests/theme.test
index 7968cf7..380f7a0 100644
--- a/core/modules/simpletest/tests/theme.test
+++ b/core/modules/simpletest/tests/theme.test
@@ -245,6 +245,69 @@ class ThemeFunctionsTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Tests theme_links().
+   */
+  function testLinks() {
+    // Verify that empty variables produce no output.
+    $variables = array();
+    $expected = '';
+    $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback generates no output.');
+
+    $variables = array();
+    $variables['heading'] = 'Some title';
+    $expected = '';
+    $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
+
+    // Set the current path to the front page path.
+    // Required to verify the "active" class in expected links below, and
+    // because the current path is different when running tests manually via
+    // simpletest.module ('batch') and via the testing framework ('').
+    $_GET['q'] = variable_get('site_frontpage', 'node');
+
+    // Verify that a list of links is properly rendered.
+    $variables = array();
+    $variables['attributes'] = array('id' => 'somelinks');
+    $variables['links'] = array(
+      'a link' => array(
+        'title' => 'A <link>',
+        'href' => 'a/link',
+      ),
+      'plain text' => array(
+        'title' => 'Plain "text"',
+      ),
+      'front page' => array(
+        'title' => 'Front page',
+        'href' => '<front>',
+      ),
+    );
+
+    $expected_links = '';
+    $expected_links .= '<ul id="somelinks">';
+    $expected_links .= '<li class="a-link odd first"><a href="' . url('a/link') . '">' . check_plain('A <link>') . '</a></li>';
+    $expected_links .= '<li class="plain-text even"><span>' . check_plain('Plain "text"') . '</span></li>';
+    $expected_links .= '<li class="front-page odd last active"><a href="' . url('<front>') . '" class="active">' . check_plain('Front page') . '</a></li>';
+    $expected_links .= '</ul>';
+
+    // Verify that passing a string as heading works.
+    $variables['heading'] = 'Links heading';
+    $expected_heading = '<h2>Links heading</h2>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+
+    // Verify that passing an array as heading works (core support).
+    $variables['heading'] = array('text' => 'Links heading', 'level' => 'h3', 'class' => 'heading');
+    $expected_heading = '<h3 class="heading">Links heading</h3>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+
+    // Verify that passing attributes for the heading works.
+    $variables['heading'] = array('text' => 'Links heading', 'level' => 'h3', 'attributes' => array('id' => 'heading'));
+    $expected_heading = '<h3 id="heading">Links heading</h3>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+  }
+
+  /**
    * Asserts themed output.
    *
    * @param $callback
@@ -269,19 +332,6 @@ class ThemeFunctionsTestCase extends DrupalWebTestCase {
     $message = t($message, array('%callback' => 'theme_' . $callback . '()'));
     $this->assertIdentical($output, $expected, $message);
   }
-}
-
-/**
- * Unit tests for theme_links().
- */
-class ThemeLinksTest extends DrupalWebTestCase {
-  public static function getInfo() {
-    return array(
-      'name' => 'Links',
-      'description' => 'Test the theme_links() function and rendering groups of links.',
-      'group' => 'Theme',
-    );
-  }
 
   /**
    * Test the use of drupal_pre_render_links() on a nested array of links.
