Index: modules/simpletest/simpletest.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.css,v
retrieving revision 1.3
diff -u -r1.3 simpletest.css
--- modules/simpletest/simpletest.css	18 Sep 2008 14:57:42 -0000	1.3
+++ modules/simpletest/simpletest.css	28 Sep 2008 04:26:18 -0000
@@ -5,7 +5,7 @@
 }
 /* Test Table */
 #simpletest-form-table th.select-all {
-  width: 50px;
+  width: 30px;
 }
 th.simpletest_test {
   width: 160px;
@@ -21,39 +21,38 @@
   color: #494949;
 }
 
-div.simpletest-pass {
-  color: #33a333;
+.simpletest-pass {
+  color: #316d31;
 }
 
-div.simpletest-fail {
-  color: #a30000;
+.simpletest-fail {
+  color: #981010;
 }
 
-tr.simpletest-pass.odd {
-  background: #b6ffb6;
+.simpletest-exception {
+  color: #bda400;
 }
 
-tr.simpletest-pass.even {
-  background: #9bff9b;
+li.simpletest-important {
+  font-weight: bold;
 }
 
-tr.simpletest-fail.odd {
-  background: #ffc9c9;
+ul.simpletest-results li div.item-list ul {
+  padding-left: 20px;
 }
 
-tr.simpletest-fail.even {
-  background: #ffacac;
-}
-
-tr.simpletest-exception.odd {
-  background: #f4ea71;
+ul.simpletest-results li span.simpletest-overview {
+  cursor: pointer;
 }
 
-tr.simpletest-exception.even {
-  background: #f5e742;
+ul.simpletest-results li span.simpletest-overview:hover {
+  text-decoration: underline;
 }
 
 div.simpletest-image {
   display: inline;
+}
+
+div.simpletest-image, label.simpletest-group-label {
   cursor: pointer;
 }
Index: modules/simpletest/simpletest.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v
retrieving revision 1.9
diff -u -r1.9 simpletest.test
--- modules/simpletest/simpletest.test	15 Sep 2008 20:48:09 -0000	1.9
+++ modules/simpletest/simpletest.test	28 Sep 2008 04:26:18 -0000
@@ -82,7 +82,7 @@
         $this->drupalGet('admin/build/testing');
 
         $edit = array();
-        $edit['SimpleTestTestCase'] = TRUE;
+        $edit[get_class($this)] = TRUE;
         $this->drupalPost(NULL, $edit, t('Run tests'));
 
         // Parse results and confirm that they are correct.
@@ -129,20 +129,23 @@
    * Confirm that the stub test produced the desired results.
    */
   function confirmStubTestResults() {
-    $this->assertAssertion($this->pass, 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
-    $this->assertAssertion($this->fail, 'Other', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $info = $this->getInfo();
+    $name = $info['name'];
+
+    $this->assertAssertion($this->pass, $name, 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion($this->fail, $name, 'fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
 
-    $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
-    $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
 
     // Check that a warning is catched by simpletest.
-    $this->assertAssertion('Division by zero', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion('Division by zero', 'Warning', 'exception', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
 
     // Check that the backtracing code works for specific assert function.
-    $this->assertAssertion('This is nothing.', 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion('This is nothing.', $name, 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
 
     // Check that errors that occur inside PHP internal functions are correctly reported.
-    $this->assertAssertion('The second argument should be either an array or an object', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
+    $this->assertAssertion('The second argument should be either an array or an object', 'Warning', 'exception', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
 
     $this->test_ids[] = $test_id = $this->getTestIdFromResults();
     $this->assertTrue($test_id, t('Found test ID in results.'));
@@ -165,18 +168,18 @@
    * in the test results.
    *
    * @param string $message Assertion message.
-   * @param string $type Assertion type.
+   * @param string $group Assertion group.
    * @param string $status Assertion status.
    * @param string $file File where the assertion originated.
    * @param string $functuion Function where the assertion originated.
    * @return Assertion result.
    */
-  function assertAssertion($message, $type, $status, $file, $function) {
+  function assertAssertion($message, $group, $status, $file, $function) {
     $message = trim(strip_tags($message));
     $found = FALSE;
     foreach ($this->results['assertions'] as $assertion) {
       if ((strpos($assertion['message'], $message) !== FALSE) &&
-          $assertion['type'] == $type &&
+          $assertion['group'] == $group &&
           $assertion['status'] == $status &&
           $assertion['file'] == $file &&
           $assertion['function'] == $function) {
@@ -184,7 +187,7 @@
         break;
       }
     }
-    return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function)));
+    return $this->assertTrue($found, t('Found assertion {"@message", "@group", "@status", "@file", "@function"}.', array('@message' => $message, '@group' => $group, '@status' => $status, "@file" => $file, "@function" => $function)));
   }
 
   /**
@@ -194,22 +197,31 @@
     $results = array();
 
     if ($this->parse()) {
-      if ($fieldset = $this->getResultFieldSet()) {
+      if ($container = $this->getResultContainer()) {
         // Code assumes this is the only test in group.
-        $results['summary'] = $this->asText($fieldset->div);
-        $results['name'] = $this->asText($fieldset->fieldset->legend);
+        preg_match('/^(.*?): (.*?)$/', $this->asText($container->span), $matches);
+        $results['name'] = $matches[1];
+        $results['summary'] = $matches[2];
 
         $results['assertions'] = array();
-        $tbody = $fieldset->fieldset->table->tbody;
-        foreach ($tbody->tr as $row) {
+        $list = $container->div->ul->li;
+        foreach ($list as $item) {
           $assertion = array();
-          $assertion['message'] = $this->asText($row->td[0]);
-          $assertion['type'] = $this->asText($row->td[1]);
-          $assertion['file'] = $this->asText($row->td[2]);
-          $assertion['line'] = $this->asText($row->td[3]);
-          $assertion['function'] = $this->asText($row->td[4]);
-          $ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png');
-          $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
+
+          // Get status from class name.
+          preg_match('/simpletest-(\w+)/', $item['class'], $matches);
+          $assertion['status'] = $matches[1];
+
+          // Get message text.
+          $assertion['message'] = $this->asText($item->span);
+
+          // Parse details, including: group, file, function, and line.
+          foreach ($item->div->ul->li as $detail) {
+            if (preg_match('/^(\w+): (.*?)$/', $this->asText($detail), $matches)) {
+              $assertion[strtolower($matches[1])] = $matches[2];
+            }
+          }
+
           $results['assertions'][] = $assertion;
         }
       }
@@ -218,17 +230,13 @@
   }
 
   /**
-   * Get the fieldset containing the results for group this test is in.
+   * Get the UL containing the results for the group this test is in.
    *
-   * @return fieldset containing the results for group this test is in.
+   * @return UL containing the results.
    */
-  function getResultFieldSet() {
-    $fieldsets = $this->xpath('//fieldset');
-    $info = $this->getInfo();
-    foreach ($fieldsets as $fieldset) {
-      if ($fieldset->legend == $info['group']) {
-        return $fieldset;
-      }
+  function getResultContainer() {
+    if ($result = $this->xpath('//ul[@class="simpletest-results"]')) {
+      return $result[0]->li->div->ul->li;
     }
     return FALSE;
   }
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.45
diff -u -r1.45 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	20 Sep 2008 20:22:24 -0000	1.45
+++ modules/simpletest/drupal_web_test_case.php	28 Sep 2008 04:26:18 -0000
@@ -76,13 +76,17 @@
       'test_class' => get_class($this),
       'status' => $status,
       'message' => $message,
-      'message_group' => $group,
+      'group' => $group,
       'function' => $caller['function'],
       'line' => $caller['line'],
       'file' => $caller['file'],
     );
 
     // Store assertion for display after the test has completed.
+    // Change the 'group' attribute to 'message_group' to avoid reserved word
+    // conflict when inserted into database.
+    $assertion['message_group'] = $assertion['group'];
+    unset($assertion['group']);
     db_insert('simpletest')->fields($assertion)->execute();
 
     // Return to testing prefix.
Index: modules/simpletest/simpletest.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v
retrieving revision 1.15
diff -u -r1.15 simpletest.module
--- modules/simpletest/simpletest.module	20 Sep 2008 20:22:24 -0000	1.15
+++ modules/simpletest/simpletest.module	28 Sep 2008 04:26:18 -0000
@@ -26,6 +26,14 @@
     'page arguments' => array('simpletest_test_form'),
     'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.',
     'access arguments' => array('administer unit tests'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+  $items['admin/build/testing/results'] = array(
+    'title' => 'Results',
+    'page callback' => 'simpletest_display_results',
+    'description' => 'View the test results.',
+    'access arguments' => array('administer unit tests'),
+    'type' => MENU_CALLBACK,
   );
   return $items;
 }
@@ -50,11 +58,14 @@
     'simpletest_result_summary' => array(
       'arguments' => array('form' => NULL)
     ),
+    'simpletest_results' => array(
+      'arguments' => array('results' => NULL, 'assertions' => NULL)
+    ),
   );
 }
 
 /**
- * Menu callback for both running tests and listing possible tests
+ * Menu callback -- List all tests that can be run.
  */
 function simpletest_test_form() {
   global $db_prefix, $db_prefix_original;
@@ -64,95 +75,6 @@
   // List out all tests in groups for selection.
   $uncategorized_tests = simpletest_get_all_tests();
   $tests = simpletest_categorize_tests($uncategorized_tests);
-  if (isset($_SESSION['test_id'])) {
-    // Select all results using the active test ID used to group them.
-    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
-
-    $summary = array(
-      '#theme' => 'simpletest_result_summary',
-      '#pass' => 0,
-      '#fail' => 0,
-      '#exception' => 0,
-      '#weight' => -10,
-    );
-    $form['summary'] = $summary;
-    $form['results'] = array();
-    $group_summary = array();
-    $map = array(
-      'pass' => theme('image', 'misc/watchdog-ok.png'),
-      'fail' => theme('image', 'misc/watchdog-error.png'),
-      'exception' => theme('image', 'misc/watchdog-warning.png'),
-    );
-    $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
-    while ($result = db_fetch_object($results)) {
-      $class = $result->test_class;
-      $info = $uncategorized_tests[$class]->getInfo();
-      $group = $info['group'];
-      if (!isset($group_summary[$group])) {
-        $group_summary[$group] = $summary;
-      }
-      $element = &$form['results'][$group][$class];
-      if (!isset($element)) {
-        $element['summary'] = $summary;
-      }
-      $status = $result->status;
-      // This reporter can only handle pass, fail and exception.
-      if (isset($map[$status])) {
-        $element['#title'] = $info['name'];
-        $status_index = '#'. $status;
-        $form['summary'][$status_index]++;
-        $group_summary[$group][$status_index]++;
-        $element['summary'][$status_index]++;
-        $element['result_table']['#rows'][] = array(
-          'data' => array(
-            $result->message,
-            $result->message_group,
-            basename($result->file),
-            $result->line,
-            $result->function,
-            $map[$status],
-          ),
-          'class' => "simpletest-$status",
-        );
-      }
-      unset($element);
-    }
-
-    // Clear test results.
-    if (variable_get('simpletest_clear_results', TRUE)) {
-      db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']);
-      db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']);
-    }
-    unset($_SESSION['test_id']);
-
-    $all_ok = TRUE;
-    foreach ($form['results'] as $group => &$elements) {
-      $group_ok = TRUE;
-      foreach ($elements as $class => &$element) {
-        $info = $uncategorized_tests[$class]->getInfo();
-        $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0;
-        $element += array(
-          '#type' => 'fieldset',
-          '#collapsible' => TRUE,
-          '#collapsed' => $ok,
-          '#description' => $info['description'],
-        );
-        $element['result_table']['#markup'] = theme('table', $header, $element['result_table']['#rows']);
-        $element['summary']['#ok'] = $ok;
-        $group_ok = $group_ok && $ok;
-      }
-      $elements += array(
-        '#type' => 'fieldset',
-        '#title' => $group,
-        '#collapsible' => TRUE,
-        '#collapsed' => $group_ok,
-        'summary' => $group_summary[$group],
-      );
-      $elements['summary']['#ok'] = $group_ok;
-      $all_ok = $group_ok && $all_ok;
-    }
-    $form['summary']['#ok'] = $all_ok;
-  }
   $form['tests'] = array(
     '#type' => 'fieldset',
     '#title' => t('Tests'),
@@ -160,7 +82,7 @@
   );
   $form['tests']['table'] = array(
     '#theme' => 'simpletest_test_table'
-    );
+  );
   foreach ($tests as $group_name => $test_group) {
     foreach ($test_group as $test) {
       $test_info = $test->getInfo();
@@ -275,6 +197,101 @@
   }
 }
 
+/**
+ * Display the results of the last run set of tests.
+ */
+function simpletest_display_results() {
+  if (!isset($_SESSION['test_id'])) {
+    if (arg(4) == 're-run') {
+      // Create form so that submit handler is called.
+      drupal_get_form('simpletest_rerun_form', array('pass' => array(), 'fail' => array()));
+      return;
+    }
+    else {
+      drupal_set_message(t('No results to display.'));
+      drupal_goto('admin/build/testing');
+    }
+  }
+
+  // Ensure that all classes are loaded before we create instances to get test information.
+  simpletest_get_all_tests();
+
+  // Add JavasSript and CSS neccessary to use the dynamic results display.
+  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module');
+  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module');
+
+  $result = db_query('SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id', $_SESSION['test_id']);
+
+  $results = array();
+  $assertions = array();
+  $classes = array();
+  while ($assertion = db_fetch_object($result)) {
+    $class = $assertion->test_class;
+
+    // Get the summary results, only once per test class.
+    if (empty($results[$class])) {
+      // Instantiate class and get class information.
+      $test = new $class();
+      $info = $test->getInfo();
+
+      // Init summary results counters.
+      $results[$class] = array('#fail' => 0, '#pass' => 0, '#exception' => 0);
+
+      // Store class info for use when themeing.
+      foreach ($info as $key => $value) {
+        $results[$class]['#' . $key] = $value;
+      }
+    }
+
+    // Update summary counters.
+    $results[$class]['#' . $assertion->status]++;
+
+    // Store assertion in nested array after converting 'message_group' to
+    // 'group' to avoid database field conflict with reserved word.
+    $assertion = (array) $assertion;
+    $assertion['group'] = $assertion['message_group'];
+    unset($assertion['message_group']);
+
+    $assertions[$class][] = $assertion;
+
+    // Update the state of the results for class, if any non-passes then test
+    // results are not "ok" and should be expanded by default.
+    $results[$class]['#ok'] = !($results[$class]['#fail'] + $results[$class]['#exception']);
+
+    // Store test class for use in re-run form.
+    if (!array_key_exists($assertion['test_class'], $classes)) {
+      $classes[$assertion['test_class']] = $results[$class]['#ok'];
+    }
+    else {
+      // If any fails then overall fail.
+      $classes[$assertion['test_class']] = $classes[$assertion['test_class']] && $results[$class]['#ok'];
+    }
+  }
+
+  // Separate lists in pass and fail.
+  $test_classes = array('pass' => array(), 'fail' => array());
+  foreach ($classes as $class => $status) {
+    if ($status) {
+      $test_classes['pass'][] = $class;
+    }
+    else {
+      $test_classes['fail'][] = $class;
+    }
+  }
+
+  // Clear test results.
+  if (variable_get('simpletest_clear_results', TRUE)) {
+    db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']);
+    db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']);
+  }
+  unset($_SESSION['test_id']);
+
+  return '<p>' . t('The assertions listed in <strong>bold text</strong> are those made by the tests themselves, rather than the implicit framework tests.') . '</p>' .
+         drupal_get_form('simpletest_filter_form') .
+         theme('simpletest_results', $results, $assertions) .
+         drupal_get_form('simpletest_rerun_form', $test_classes);
+}
+
 function theme_simpletest_result_summary($form, $text = NULL) {
   return '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">' . _simpletest_format_summary_line($form) . '</div>';
 }
@@ -348,6 +365,7 @@
       $test->run();
     }
     $_SESSION['test_id'] = $test_id;
+    drupal_goto('admin/build/testing/results');
   }
 }
 
@@ -412,6 +430,7 @@
   else {
     drupal_set_message(t('The tests did not successfully finish.'), 'error');
   }
+  drupal_goto('admin/build/testing/results');
 }
 
 /**
@@ -478,6 +497,237 @@
 }
 
 /**
+ * Theme simpletest results.
+ *
+ * @param $results
+ *   A nested array of simpletest results.
+ * @param $assertions
+ *   An array of assertions for each test.
+ * @return
+ *   A nested list of simpletest items.
+ */
+function theme_simpletest_results($results, $assertions) {
+  $items = array();
+  $group_data = array();
+  $totals = array(
+    '#fail' => 0,
+    '#pass' => 0,
+    '#exception' => 0,
+  );
+  foreach (element_children($results) as $class) {
+    $totals['#fail'] += $results[$class]['#fail'];
+    $totals['#pass'] += $results[$class]['#pass'];
+    $totals['#exception'] += $results[$class]['#exception'];
+    $item = array();
+    foreach ($results[$class] as $key => $value) {
+      if ($key == '#ok') {
+        $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] && $value : $value;
+      }
+      else {
+        $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] + $value : $value;
+      }
+    }
+    $item['class'] = 'simpletest-container ' . ($results[$class]['#fail'] ? 'simpletest-fail expanded' : ($results[$class]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed'));
+    $item['data'] = '<span class="simpletest-overview">' . t('@name: @summary', array('@name' => $results[$class]['#name'], '@summary' => _simpletest_format_summary_line($results[$class]))) . '</span>';
+    $assertion_items = array();
+    foreach ($assertions[$class] as $assertion) {
+      // The idea of importancy rests upon the fact that the testing framework
+      // makes many assertions in its normal process of making GET and POST
+      // requests, as well as many other normal procedures. We will treat
+      // assertions actually initiated by the test itself as far more likely to
+      // be important, and thus highlight it in bold text.
+      $important = FALSE;
+      if ($assertion['group'] == 'Other') {
+        $assertion['group'] = $results[$class]['#name'];
+        $important = TRUE;
+      }
+
+      $css_class = simpletest_get_assertion_css_class($assertion, $important);
+      $assertion_items[] = array(
+        'data' => t('[@status] <span class="simpletest-overview">!message</span>',
+                    array('@status' => simpletest_get_status_message($assertion),
+                          '!message' => $assertion['message'])),
+        'class' => 'collapsed ' . $css_class,
+        'children' => array(
+          array(
+            'data' => t('Group: @group', array('@group' => $assertion['group'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('File: @file', array('@file' => basename($assertion['file']))),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('Function: @function', array('@function' => $assertion['function'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('Line: @line', array('@line' => $assertion['line'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+        ),
+      );
+    }
+    $item['children'] = $assertion_items;
+    $items[$results[$class]['#group']]['children'][] = $item;
+    $items[$results[$class]['#group']]['data'] = '<span class="simpletest-overview">' . t('@name: @summary', array('@name' => $results[$class]['#group'], '@summary' => _simpletest_format_summary_line($group_data[$results[$class]['#group']]))) . '</span>';
+    $items[$results[$class]['#group']]['class'] = 'simpletest-container ' . ($group_data[$results[$class]['#group']]['#fail'] ? 'simpletest-fail expanded' : ($group_data[$results[$class]['#group']]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed'));
+  }
+  $output = '';
+  $output .= '<p class="' . ($totals['#fail'] ? 'simpletest-fail' : ($totals['#exception'] ? 'simpletest-exception' : 'simpletest-pass')) . '">';
+  $output .= t('Overall results: @summary', array('@summary' => _simpletest_format_summary_line($totals)));
+  $output .= '</p>';
+  $output .= theme('item_list', $items, NULL, 'ul', array('class' => 'simpletest-results'));
+  return $output;
+}
+
+/**
+ * Create a filter form used on the results page.
+ */
+function simpletest_filter_form() {
+  $form = array();
+  $form['outcome'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Filter by outcome'),
+    '#options' => array(),
+    '#attributes' => array('class' => 'container-inline'),
+    '#default_value' => array(),
+  );
+  $categories = array('pass' => t('Passes'), 'fail' => t('Fails'), 'exception' => t('Exceptions'));
+  foreach ($categories as $outcome => $readable) {
+    $form['outcome'][$outcome] = array(
+      '#type' => 'checkbox',
+      '#title' => $readable,
+      '#attributes' => array('class' => 'simpletest-filter'),
+      '#default_value' => TRUE,
+    );
+  }
+  return $form;
+}
+
+/**
+ * Create a form containing the last run test classes so that they can bee run
+ * again.
+ */
+function simpletest_rerun_form($form_state, array $classes) {
+  $form = array();
+  $form['#action'] = url('admin/build/testing/results/re-run');
+  $form['classes_pass'] = array(
+    '#type' => 'hidden',
+    '#default_value' => implode(',', $classes['pass'])
+  );
+  $form['classes_fail'] = array(
+    '#type' => 'hidden',
+    '#default_value' => implode(',', $classes['fail'])
+  );
+  $form['actions'] = array(
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['actions']['filter'] = array(
+    '#type' => 'select',
+    '#title' => t('Filter'),
+    '#options' => array(
+      'all' => t('All'),
+      'pass' => t('Pass'),
+      'fail' => t('Fail'),
+    ),
+  );
+  // If any tests fail select that as the default.
+  if (!empty($classes['fail'])) {
+    $form['actions']['filter']['#default_value'] = 'fail';
+  }
+
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Re-run tests'),
+  );
+  $form['actions']['cancel'] = array(
+    '#markup' => l(t('Return to testing overview page'), 'admin/build/testing'),
+  );
+  return $form;
+}
+
+/**
+ * Re-run selected tests.
+ */
+function simpletest_rerun_form_submit($form, &$form_state) {
+  // Simulate the checkboxes on the test select form by setting each of the
+  // test classes as a value in the form state.
+  $classes = array();
+  switch ($form_state['values']['filter']) {
+    case 'all':
+      $classes = array_merge(explode(',', $form_state['values']['classes_pass']), explode(',', $form_state['values']['classes_fail']));
+      break;
+    case 'pass':
+      $classes = explode(',', $form_state['values']['classes_pass']);
+      break;
+    case 'fail':
+      $classes = explode(',', $form_state['values']['classes_fail']);
+      break;
+  }
+
+  $found_tests = FALSE;
+  foreach ($classes as $class) {
+    if ($class) {
+      $form_state['values'][$class] = 1;
+      $found_tests = TRUE;
+    }
+  }
+
+  if (!$found_tests) {
+    drupal_set_message(t('No test to re-run.'), 'error');
+    drupal_goto('admin/build/testing');
+  }
+
+  // Use the existing submit handler to run the tests.
+  simpletest_test_form_submit($form, $form_state);
+}
+
+/**
+ * Get the appropriate css class based on the assertions status and importance.
+ *
+ * @param $assertion Assertion item aray.
+ * @param $important Importance of assertion.
+ * @return CSS class.
+ */
+function simpletest_get_assertion_css_class($assertion, $important) {
+  switch ($assertion['status']) {
+    case 'pass':
+      $css_class = 'simpletest-pass';
+      break;
+    case 'fail':
+      $css_class = 'simpletest-fail';
+      break;
+    case 'exception':
+      $css_class = 'simpletest-exception';
+      break;
+  }
+  if ($important) {
+    $css_class .= ' simpletest-important';
+  }
+  return $css_class;
+}
+
+/**
+ * Get the status message text
+ *
+ * @param $assertion Assertion item aray.
+ * @return Status message text.
+ */
+function simpletest_get_status_message($assertion) {
+  switch ($assertion['status']) {
+    case 'pass':
+      return t('PASS');
+    case 'fail':
+      return t('FAIL');
+    case 'exception':
+      return t('EXCEPTION');
+  }
+  return t('UNKNOWN');
+}
+
+/**
  * Remove all temporary database tables and directories.
  */
 function simpletest_clean_environment() {
Index: modules/simpletest/simpletest.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.js,v
retrieving revision 1.3
diff -u -r1.3 simpletest.js
--- modules/simpletest/simpletest.js	14 Sep 2008 22:34:25 -0000	1.3
+++ modules/simpletest/simpletest.js	28 Sep 2008 04:26:18 -0000
@@ -29,6 +29,10 @@
       $(this).html(Drupal.settings.simpleTest.images[(direction? 0 : 1)]);
     }
   });
+  $('label.simpletest-group-label').click(function() {
+    $(this).siblings('div.simpletest-image').click();
+    return false;
+  });
 }
 
 /**
@@ -68,4 +72,53 @@
     }
     $(this).append(checkbox);
   });
+};
+
+/**
+ * Filter the results based on their outcome.
+ */
+Drupal.behaviors.simpleTestFilterForm = function() {
+  var updateResults = function(type, checked) {
+    $('li.simpletest-' + type + ':not(.simpletest-container)')[checked ? 'show' : 'hide']();
+    $('li.simpletest-container:not(:has(li.simpletest-container))').each(function() {
+      var children = $(this).find('li:visible:not(.simpletest-container)').is('.simpletest-pass,.simpletest-fail,.simpletest-exception');
+      $(this)[children ? 'show' : 'hide']();
+    });
+  }
+  $('#edit-outcome-pass').change(function() {
+    updateResults('pass', $(this).is(':checked'));
+  });
+  $('#edit-outcome-fail').change(function() {
+    updateResults('fail', $(this).is(':checked'));
+  });
+  $('#edit-outcome-exception').change(function() {
+    updateResults('exception', $(this).is(':checked'));
+  });
+  updateResults('pass', $('#edit-outcome-pass').is(':checked'));
+  updateResults('fail', $('#edit-outcome-fail').is(':checked'));
+  updateResults('exception', $('#edit-outcome-exception').is(':checked'));
+};
+
+/**
+ * The results should collapse when you click on them, of course.
+ */
+Drupal.behaviors.simpleTestResultsCollapse = function() {
+  $('li > span.simpletest-overview').click(function() {
+    $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded');
+  });
+  $('li.collapsed > span.simpletest-overview').each(function() {
+    $(this).siblings().children('ul').hide().addClass('simpletest-hidden');
+  });
+};
+
+/**
+ * Batch update function - re-attaches javascript behaviors to the new elements
+ * now injected into the HTML.
+ */
+Drupal.batchUpdate.simpleTestResultsCollapse = function() {
+  $('li > span.simpletest-overview').click(function() {
+    $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded');
+  }).each(function() {
+    $(this).siblings().children('ul').hide().addClass('simpletest-hidden').parent().parent().removeClass('expanded').addClass('collapsed');
+  });
 };
\ No newline at end of file
