From f20e3abe0b109381592ec53f12690188dad5ad5c Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Wed, 11 May 2011 21:49:20 -0400
Subject: [PATCH] Issue #823380, #221257 by AlexisWilke, NancyDru, greg.harvey, pillarsdotnet, AES2, vito_swat, scito, joachim, intoxination: Better read_more handling.

---
 modules/field/field.module             |   35 ++++++
 modules/field/modules/text/text.module |  193 +++++++++++++++++---------------
 modules/field/modules/text/text.test   |  141 +++++++++---------------
 modules/node/node.module               |    4 +-
 modules/node/node.test                 |  138 +++++++++++++++++++++++
 5 files changed, 331 insertions(+), 180 deletions(-)

diff --git a/modules/field/field.module b/modules/field/field.module
index 9e03c8d911dc3dc6ef42eee599752bbc789399ed..367d1046bcf617d7df1004eb651f444bb6a9a0b0 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -1010,6 +1010,41 @@ function field_extract_bundle($entity_type, $bundle) {
 }
 
 /**
+ * Check if any fields should trigger a readmore.
+ *
+ * This will parse through all renderable fields in the renederable array
+ * and check for #readmore property. If #readmore is TRUE, then we will
+ * return TRUE to signify that the caller should include a readmore link.
+ *
+ * @param $elements
+ *   An array of renderable fields
+ * @return
+ *   True if a single element has #readmore set to TRUE, otherwise FALSE.
+ */
+function field_has_read_more($elements) {
+  // Early-return if the user does not have access.
+  if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
+    return FALSE;
+  }
+
+  // Return if #read_more is set on this element.
+  if (!empty($elements['#read_more']) && $elements['#read_more']) {
+    return TRUE;
+  }
+
+  // Iterate through children.
+  foreach (element_children($elements) as $key) {
+    if (field_has_read_more($elements[$key])) {
+      // A child element has #read_more set to TRUE.
+      return TRUE;
+    }
+  }
+
+  // Neither this element, nor any child elements had #read_more set.
+  return FALSE;
+}
+
+/**
  * Theme preprocess function for theme_field() and field.tpl.php.
  *
  * @see theme_field()
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index 89c605cf2c046eb48d448e20d27a63ef88a8583f..fc7c434a1fac0eec744a6719a86dc7d99c6ecf3f 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -262,23 +262,34 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la
     case 'text_trimmed':
       foreach ($items as $delta => $item) {
         $output = _text_sanitize($instance, $langcode, $item, 'value');
+        $readmore = FALSE;
         if ($display['type'] == 'text_trimmed') {
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $trimmed_output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          if ($trimmed_output != $output){
+            $readmore = TRUE;
+          }
+          $output = $trimmed_output;
         }
-        $element[$delta] = array('#markup' => $output);
+        $element[$delta] = array('#markup' => $output, '#read_more' => $readmore);
       }
       break;
 
     case 'text_summary_or_trimmed':
       foreach ($items as $delta => $item) {
+        $readmore = FALSE;
         if (!empty($item['summary'])) {
+          $readmore = TRUE;
           $output = _text_sanitize($instance, $langcode, $item, 'summary');
         }
         else {
           $output = _text_sanitize($instance, $langcode, $item, 'value');
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $trimmed_output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          if ($trimmed_output != $output){
+            $readmore = TRUE;
+          }
+          $output = $trimmed_output;
         }
-        $element[$delta] = array('#markup' => $output);
+        $element[$delta] = array('#markup' => $output, '#read_more' => $readmore);
       }
       break;
 
@@ -330,49 +341,38 @@ function _text_sanitize($instance, $langcode, $item, $column) {
  * @param $text
  *   The content for which a summary will be generated.
  * @param $format
- *   The format of the content.
- *   If the PHP filter is present and $text contains PHP code, we do not
- *   split it up to prevent parse errors.
- *   If the line break filter is present then we treat newlines embedded in
- *   $text as line breaks.
- *   If the htmlcorrector filter is present, it will be run on the generated
- *   summary (if different from the incoming $text).
+ *   The format of the content. The $text string will be passed through
+ *   check_markup() before generating a summary.
  * @param $size
- *   The desired character length of the summary. If omitted, the default
- *   value will be used. Ignored if the special delimiter is present
- *   in $text.
+ *   The desired character length of the summary, not counting HTML tags. If
+ *   omitted, the default value will be used. Ignored if the special delimiter
+ *   is present in $text.
  * @return
  *   The generated summary.
  */
 function text_summary($text, $format = NULL, $size = NULL) {
-
-  if (!isset($size)) {
-    // What used to be called 'teaser' is now called 'summary', but
-    // the variable 'teaser_length' is preserved for backwards compatibility.
-    $size = variable_get('teaser_length', 600);
-  }
-
   // Find where the delimiter is in the body
   $delimiter = strpos($text, '<!--break-->');
 
-  // If the size is zero, and there is no delimiter, the entire body is the summary.
-  if ($size == 0 && $delimiter === FALSE) {
-    return $text;
-  }
-
   // If a valid delimiter has been specified, use it to chop off the summary.
   if ($delimiter !== FALSE) {
-    return substr($text, 0, $delimiter);
+    // Since there is no drupal_strpos(), we must use substr() instead of
+    // drupal_substr() here, or we'll break on UTF-8 input.
+    return trim(check_markup(substr($text, 0, $delimiter), $format));
   }
 
-  // We check for the presence of the PHP evaluator filter in the current
-  // format. If the body contains PHP code, we do not split it up to prevent
-  // parse errors.
-  if (isset($format)) {
-    $filters = filter_list_format($format);
-    if (isset($filters['php_code']) && $filters['php_code']->status && strpos($text, '<?') !== FALSE) {
-      return $text;
-    }
+  // Start with the trimmed, formatted version of $text
+  $text = trim(check_markup($text, $format));
+
+  if (!isset($size)) {
+    // What used to be called 'teaser' is now called 'summary', but
+    // the variable 'teaser_length' is preserved for backwards compatibility.
+    $size = variable_get('teaser_length', 600);
+  }
+
+  // If the size is zero, the entire body is the summary.
+  if ($size == 0) {
+    return $text;
   }
 
   // If we have a short body, the entire body is the summary.
@@ -380,68 +380,79 @@ function text_summary($text, $format = NULL, $size = NULL) {
     return $text;
   }
 
-  // If the delimiter has not been specified, try to split at paragraph or
-  // sentence boundaries.
-
-  // The summary may not be longer than maximum length specified. Initial slice.
-  $summary = truncate_utf8($text, $size);
-
-  // Store the actual length of the UTF8 string -- which might not be the same
-  // as $size.
-  $max_rpos = strlen($summary);
-
-  // How much to cut off the end of the summary so that it doesn't end in the
-  // middle of a paragraph, sentence, or word.
-  // Initialize it to maximum in order to find the minimum.
-  $min_rpos = $max_rpos;
-
-  // Store the reverse of the summary. We use strpos on the reversed needle and
-  // haystack for speed and convenience.
-  $reversed = strrev($summary);
-
-  // Build an array of arrays of break points grouped by preference.
-  $break_points = array();
-
-  // A paragraph near the end of sliced summary is most preferable.
-  $break_points[] = array('</p>' => 0);
-
-  // If no complete paragraph then treat line breaks as paragraphs.
-  $line_breaks = array('<br />' => 6, '<br>' => 4);
-  // Newline only indicates a line break if line break converter
-  // filter is present.
-  if (isset($filters['filter_autop'])) {
-    $line_breaks["\n"] = 1;
-  }
-  $break_points[] = $line_breaks;
-
-  // If the first paragraph is too long, split at the end of a sentence.
-  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
-
-  // Iterate over the groups of break points until a break point is found.
-  foreach ($break_points as $points) {
-    // Look for each break point, starting at the end of the summary.
-    foreach ($points as $point => $offset) {
-      // The summary is already reversed, but the break point isn't.
-      $rpos = strpos($reversed, strrev($point));
-      if ($rpos !== FALSE) {
-        $min_rpos = min($rpos + $offset, $min_rpos);
-      }
-    }
-
-    // If a break point was found in this group, slice and stop searching.
-    if ($min_rpos !== $max_rpos) {
-      // Don't slice with length 0. Length must be <0 to slice from RHS.
-      $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos);
+  // Generate a DOM Document to hold the full body.
+  $body_doc = new DOMDocument();
+  $body_doc->loadHTML('<html><body>' . $text . '</body></html>');
+  $body_node = $body_doc->getElementsByTagName('body')->item(0);
+
+  // Generate a DOM Document to hold the summary.
+  $summary_doc = new DOMDocument();
+  $summary_doc->loadHTML('<html><body></body></html>');
+  $summary_node = $summary_doc->getElementsByTagName('body')->item(0);
+
+  // Recursively copy each child node from $body_node to $summary_node
+  // until $size limit is reached.
+  foreach ($body_node->childNodes as $node) {
+    if ($size <= 0) {
       break;
     }
+    _text_summarize($node, $size, $summary_node, $summary_doc);
   }
 
-  // If the htmlcorrector filter is present, apply it to the generated summary.
-  if (isset($filters['filter_htmlcorrector'])) {
-    $summary = _filter_htmlcorrector($summary);
+  // Export and return the summary document.
+  $output = '';
+  foreach ($summary_doc->getElementsByTagName('body')->item(0)->childNodes as $node) {
+    $output .= $summary_doc->saveXml($node);
   }
+  // If the original format was plaintext, the summary should be, too.
+  if ($text === strip_tags($text)) {
+    $output = strip_tags($output);
+  }
+  return trim($output);
+}
 
-  return $summary;
+function _text_summarize(&$body, &$size, &$summary, &$doc) {
+  if ($body->nodeType === XML_TEXT_NODE) {
+    $text_length = drupal_strlen($body->textContent);
+    if ($text_length <= $size) {
+      $size -= $text_length;
+      $summary->appendChild($doc->createTextNode($body->textContent));
+      return;
+    }
+    // Split by sentence.
+    $lines = preg_split(
+      '/(?<=[[:alnum:]][\.\?\!])(?=[^[:alnum:]])/',
+      $body->textContent,
+      NULL,
+      PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
+    );
+    $text = '';
+    foreach ($lines as $line) {
+      $line_length = drupal_strlen($line);
+      // Only add the sentence if it fits within the length limit.
+      if ($line_length > $size) {
+        break;
+      }
+      $text .= $line;
+      $size -= $line_length;
+    }
+    // Set size to zero to avoid adding subsequent text nodes.
+    $size = 0;
+    $summary->appendChild($doc->createTextNode($text));
+    return;
+  }
+  if ($body->hasChildNodes()) {
+    $node = $summary->appendChild($doc->createElement($body->tagName));
+    foreach ($body->childNodes as $child) {
+      if ($size > 0) {
+        _text_summarize($child, $size, $node, $doc);
+      }
+      else {
+        break;
+      }
+    }
+  }
+  return;
 }
 
 /**
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index b42fed7e09894d352b3ecdd27431234e6ae77f72..11b8ef34ac77173b0701d5534404a5690bf1e953 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -258,7 +258,8 @@ class TextSummaryTestCase extends DrupalWebTestCase {
    */
   function testFirstSentenceQuestion() {
     $text = 'A question? A sentence. Another sentence.';
-    $expected = 'A question? A sentence.';
+    // The default format includes the auto-paragraph filter.
+    $expected = '<p>A question? A sentence.</p>';
     $this->callTextSummary($text, $expected, NULL, 30);
   }
 
@@ -270,9 +271,9 @@ class TextSummaryTestCase extends DrupalWebTestCase {
             'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . // 108
             'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' . // 103
             'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; // 110
-    $expected = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' .
+    $expected = '<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' .
                 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' .
-                'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.';
+                'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>';
     // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word.
     $this->callTextSummary($text, $expected, NULL, 340);
   }
@@ -286,95 +287,52 @@ class TextSummaryTestCase extends DrupalWebTestCase {
 
     // The summaries we expect text_summary() to return when $size is the index
     // of each array item.
-    // Using no text format:
-    $expected = array(
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "<",
-      "<p",
-      "<p>",
-      "<p>\n",
-      "<p>\nH",
-      "<p>\nHi",
-      "<p>\nHi\n",
-      "<p>\nHi\n<",
-      "<p>\nHi\n</",
-      "<p>\nHi\n</p",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-    );
 
-    // And using a text format WITH the line-break and htmlcorrector filters.
-    $expected_lb = array(
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "",
-      "<p></p>",
-      "<p></p>",
-      "<p></p>",
-      "<p></p>",
-      "<p></p>",
-      "<p>\nHi</p>",
-      "<p>\nHi</p>",
-      "<p>\nHi</p>",
-      "<p>\nHi</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
-      "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+    // Using plain text format:
+    $expected = array (
+      0 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      1 => "<p></p>",
+      2 => "<p>Hi</p>",
+      3 => "<p>Hi</p>",
+      4 => "<p>Hi</p>\n<p></p>",
+      5 => "<p>Hi</p>\n<p></p>",
+      6 => "<p>Hi</p>\n<p></p>",
+      7 => "<p>Hi</p>\n<p></p>",
+      8 => "<p>Hi</p>\n<p>folks</p>",
+      9 => "<p>Hi</p>\n<p>folks</p>",
+      10 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      11 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      12 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      13 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      14 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      15 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      16 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      17 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      18 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      19 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      20 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      21 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      22 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      23 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      24 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      25 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      26 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      27 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      28 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      29 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      30 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      31 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      32 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      33 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      34 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      35 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      36 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
+      37 => "<p>Hi</p>\n<p>folks</p>\n<p>!</p>",
     );
 
     // Test text_summary() for different sizes.
     for ($i = 0; $i <= 37; $i++) {
-      $this->callTextSummary($text, $expected[$i],    NULL, $i);
-      $this->callTextSummary($text, $expected_lb[$i], 'plain_text', $i);
-      $this->callTextSummary($text, $expected_lb[$i], 'filtered_html', $i);
+      $this->callTextSummary($text, $expected[$i], 'filtered_html', $i);
     }
   }
 
@@ -383,7 +341,14 @@ class TextSummaryTestCase extends DrupalWebTestCase {
    */
   function callTextSummary($text, $expected, $format = NULL, $size = NULL) {
     $summary = text_summary($text, $format, $size);
-    $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected)));
+    $this->assertIdentical(
+      $summary, $expected, t('Generated summary "@summary" matches expected "@expected".',
+        array(
+          '@summary' => str_replace("\n", '\n', $summary),
+          '@expected' => str_replace("\n", '\n', $expected),
+        )
+      )
+    );
   }
 
   /**
diff --git a/modules/node/node.module b/modules/node/node.module
index a002e24c31fb773116bc36c101c1c21fce0cfb23..71279b4f45372737d1ba1e3de1bd7df6e09345c9 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1369,7 +1369,9 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
     '#pre_render' => array('drupal_pre_render_links'),
     '#attributes' => array('class' => array('links', 'inline')),
   );
-  if ($view_mode == 'teaser') {
+  // Only show read more in teaser view_mode and if a field has #read_more
+  // set to TRUE.
+  if ($view_mode == 'teaser' && field_has_read_more($node->content)) {
     $node_title_stripped = strip_tags($node->title);
     $links['node-readmore'] = array(
       'title' => t('Read more<span class="element-invisible"> about @title</span>', array('@title' => $node_title_stripped)),
diff --git a/modules/node/node.test b/modules/node/node.test
index 8a871c0c731fcf61fd5c52d0bb8e962c4e916f46..18856bdc7bdb9274646624e7f19cb3810d4c83bc 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -2146,3 +2146,141 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase {
     }
   }
 }
+
+/**
+ * Test the "Read more" link for teasers.
+ */
+class NodeTeaserReadMoreTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Teaser read more',
+      'description' => 'Test the "Read more" link for teasers.',
+      'group' => 'Node',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $web_user = $this->drupalCreateUser(array(
+      'create article content',
+      'create page content',
+      'administer filters',
+      filter_permission_name(filter_format_load('filtered_html')),
+      filter_permission_name(filter_format_load('full_html')),
+      'administer content types',
+      'access administration pages',
+      'bypass node access',
+      'administer taxonomy',
+      'administer nodes',
+    ));
+    $this->drupalLogin($web_user);
+  }
+
+  /**
+   * Create a node where teaser and full view are equal. The "Read more" link
+   * must not appear.
+   */
+  function testTeaserIsComplete() {
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => 'body_' . $this->randomName(32),
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($node1->body['und'][0]['value'], t('Node body appears on the default listing.'));
+    // Confirm that promoted node appears without the read more link in the
+    // default node listing.
+    $this->assertNoText('Read more', t('"Read more" does not appear in the default listing.'));
+  }
+
+  /**
+   * Create a node with a tag where teaser and full view are equal. The
+   * "Read more" link must not appear.
+   */
+  function testTeaserIsCompleteWithTag() {
+    // Post an article with a taxonomy term.
+    $langcode = LANGUAGE_NONE;
+    $tag = 'tag_' . $this->randomName(8);
+    $edit = array();
+    $edit['title'] = 'title_' . $this->randomName(8);
+    $edit["body[$langcode][0][value]"] = 'body_' . $this->randomName(32);
+    $edit["field_tags[$langcode]"] = $tag;
+    $edit['promote'] = 1;
+    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $node1 = $this->drupalGetNodeByTitle($edit['title']);
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($node1->body['und'][0]['value'], t('Node body appears on the default listing.'));
+    $this->assertText($tag, t('Tag appears on the default listing.'));
+    // Confirm that promoted node appears without the read more link in the
+    // default node listing.
+    $this->assertNoText('Read more', t('"Read more" does not appear in the default listing.'));
+  }
+
+  /**
+   * Create a node with a summary. The "Read more" link must be set.
+   */
+  function testTeaserSummary() {
+    $body = 'body_' . $this->randomName(32);
+    $summary = 'summary_' . $this->randomName(32);
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => $body,
+            'summary' => $summary,
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($summary, t('The summary text appears in the default listing.'));
+    $this->assertNoText($body, t('The body text does not appear in the default listing.'));
+    // Confirm that promoted node appears with the read more link in the
+    // default node listing.
+    $this->assertText('Read more', t('"Read more" appears in the default listing.'));
+  }
+
+  /**
+   * Create a node with trimmed body. The "Read more" link must be set.
+   */
+  function testTeaserTrimmed() {
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => 'teaser<!--break-->body',
+            // Set text format to Full HTML due to bug http://drupal.org/node/881006
+            'format' => 'full_html',
+          ),
+        ),
+      ),
+    ) );
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText('teaser', t('The teaser text appears in the default listing.'));
+    $this->assertNoText('body', t('The body text does not appear in the default listing.'));
+    // Confirm that promoted node appears with the read more link in the
+    // default node listing.
+    $this->assertText('Read more', t('"Read more" appears in the default listing.'));
+  }
+
+}
\ No newline at end of file
-- 
1.7.4.1

