diff --git a/core/modules/comment/comment.test b/core/modules/comment/comment.test
index 74f735d..03d3579 100644
--- a/core/modules/comment/comment.test
+++ b/core/modules/comment/comment.test
@@ -436,7 +436,6 @@ class CommentInterfaceTest extends CommentHelperCase {
     $this->drupalGet('node');
     $this->assertNoLink(t('@count comments', array('@count' => 0)));
     $this->assertNoLink(t('@count new comments', array('@count' => 0)));
-    $this->assertLink(t('Read more'));
     $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper'));
     $this->assertTrue(count($count) == 1, t('One child found'));
 
@@ -468,7 +467,6 @@ class CommentInterfaceTest extends CommentHelperCase {
     // Test if "new comment" link is correctly removed.
     $this->drupalGet('node');
     $this->assertLink(t('1 comment'));
-    $this->assertLink(t('Read more'));
     $this->assertNoLink(t('1 new comment'));
     $this->assertNoLink(t('@count new comments', array('@count' => 0)));
     $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper'));
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index d1e4f73..2843f8e 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -1053,6 +1053,39 @@ function field_extract_bundle($entity_type, $bundle) {
 }
 
 /**
+ * Determines whether any fields should trigger a 'Read More' link.
+ *
+ * This will parse through all renderable fields in the renederable array
+ * and check for #read_more property. If #read_more is TRUE, then we will
+ * return TRUE to signify that the caller should include a 'Read More' link.
+ *
+ * @param $elements
+ *   An array of renderable fields.
+ *
+ * @return
+ *   TRUE if at least one element has #readmore set to TRUE, otherwise FALSE.
+ */
+function field_has_read_more($elements) {
+  // Return early 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 or any of its children.
+  if (!empty($elements['#read_more']) && $elements['#read_more']) {
+    return TRUE;
+  }
+  foreach (element_children($elements) as $key) {
+    if (field_has_read_more($elements[$key])) {
+      return TRUE;
+    }
+  }
+
+  // Otherwise, 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/core/modules/field/modules/text/text.module b/core/modules/field/modules/text/text.module
index d73814f..033f4c7 100644
--- a/core/modules/field/modules/text/text.module
+++ b/core/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;
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 5ee9062..88921c3 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1416,7 +1416,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 the "Read more" link if the view_mode is 'teaser' and at least
+  // one field has the '#read_more' property 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/core/modules/node/node.test b/core/modules/node/node.test
index 02001c7..14a77ec 100644
--- a/core/modules/node/node.test
+++ b/core/modules/node/node.test
@@ -1100,8 +1100,8 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase {
     foreach (array($this->publicTid, $this->privateTid) as $tid_is_private => $tid) {
       $this->drupalGet("taxonomy/term/$tid");
       $this->nids_visible = array();
-      foreach ($this->xpath("//a[text()='Read more']") as $link) {
-        $this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Read more points to a node');
+      foreach ($this->xpath("//div[contains(@id,'node')]/h2/a") as $link) {
+        $this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Title link points to a node');
         $this->nids_visible[$matches[1]] = TRUE;
       }
       foreach ($this->nodesByUser as $uid => $data) {
@@ -2300,3 +2300,130 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase {
     }
   }
 }
+
+/**
+ * Tests the "Read more" link for teasers.
+ */
+class NodeTeaserReadMoreTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Teaser read more',
+      'description' => 'Tests 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);
+  }
+
+  /**
+   * Tests the "Read more" link when the node teaser and body are the same.
+   */
+  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, 'Node title appears on the default listing.');
+    $this->assertText($node1->body[LANGUAGE_NONE][0]['value'], 'Node body appears on the default listing.');
+    $this->assertNoFieldByXPath("//a[text()='Read more']", NULL, 'Read more link does not appear.');
+  }
+
+  /**
+   * Tests the "Read more" link when all text is shown and a tag is addded.
+   */
+  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, 'Node title appears on the default listing.');
+    $this->assertText($node1->body['und'][0]['value'], 'Node body appears on the default listing.');
+    $this->assertText($tag, 'Tag appears on the default listing.');
+    $this->assertNoFieldByXPath("//a[text()='Read more']", NULL, 'Read more link does not appear.');
+  }
+
+  /**
+   * Tests the "Read more" link on a node with a summary.
+   */
+  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, 'Node title appears on the default listing.');
+    $this->assertText($summary, 'The summary text appears in the default listing.');
+    $this->assertNoText($body, 'The body text does not appear in the default listing.');
+    $this->assertFieldByXPath("//a[text()='Read more']", NULL, 'Read more link appears in the default listing.');
+  }
+
+  /**
+   * Tests the "Read more" link on a node with a trimmed body.
+   */
+  function testTeaserTrimmed() {
+    $node1 = $this->drupalCreateNode(array(
+      'type' => 'article',
+      'promote' => 1,
+      'body' => array(
+        LANGUAGE_NONE => array(
+          '0' => array(
+            'value' => 'teaser<!--break-->body',
+            'format' => 'full_html',
+          ),
+        ),
+      ),
+    ));
+
+    $this->drupalGet('node');
+    $this->assertText($node1->title, 'Node title appears on the default listing.');
+    $this->assertText('teaser', 'The teaser text appears in the default listing.');
+    $this->assertNoText('body', 'The body text does not appear in the default listing.');
+    // Confirm that the read more link for the promoted node exists.
+    $this->assertFieldByXPath("//a[text()='Read more']", NULL, 'The read more link appears in the default listing.');
+  }
+}
