diff --git modules/field/field.default.inc modules/field/field.default.inc
index 93b5bea..9108b6f 100644
--- modules/field/field.default.inc
+++ modules/field/field.default.inc
@@ -233,6 +233,16 @@ function field_default_view($entity_type, $entity, $field, $instance, $langcode,
       }
     }
   }
+  elseif ($view_mode == 'teaser' && !empty($items)) {
+    // Set the readmode flag if the field is not empty, and hidden in teaser
+    // but shown in full mode. In other words: if there is something more to be
+    // read on the full node. Field-specific readmore flagging must be done in
+    // the proper hook_field_formatter_view().
+    $display_full = field_get_display($instance, 'full', $entity);
+    if ($display_full['type'] !== 'hidden') {
+      $entity->readmore = TRUE;
+    }
+  }
 
   return $addition;
 }
diff --git modules/field/modules/text/text.module modules/field/modules/text/text.module
index 89c605c..e3b4114 100644
--- modules/field/modules/text/text.module
+++ modules/field/modules/text/text.module
@@ -263,7 +263,12 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la
       foreach ($items as $delta => $item) {
         $output = _text_sanitize($instance, $langcode, $item, 'value');
         if ($display['type'] == 'text_trimmed') {
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $output_trimmed = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          // Flag the readmore property if trimmed and full versions differ.
+          if ($output_trimmed != $output) {
+            $entity->readmore = TRUE;
+          }
+          $output = $output_trimmed;
         }
         $element[$delta] = array('#markup' => $output);
       }
@@ -273,10 +278,18 @@ function text_field_formatter_view($entity_type, $entity, $field, $instance, $la
       foreach ($items as $delta => $item) {
         if (!empty($item['summary'])) {
           $output = _text_sanitize($instance, $langcode, $item, 'summary');
+          // Summary is different than the rest of the text field by default so
+          // flag the readmore property.
+          $entity->readmore = TRUE;
         }
         else {
           $output = _text_sanitize($instance, $langcode, $item, 'value');
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          $output_trimmed = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
+          // Flag the readmore property if trimmed and full versions differ.
+          if ($output_trimmed != $output) {
+            $entity->readmore = TRUE;
+          }
+          $output = $output_trimmed;
         }
         $element[$delta] = array('#markup' => $output);
       }
diff --git modules/node/node.module modules/node/node.module
index c052f4b..ad52657 100644
--- modules/node/node.module
+++ modules/node/node.module
@@ -1347,6 +1347,8 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
 
   // Remove previously built content, if exists.
   $node->content = array();
+  // Reset the readmore flag.
+  $node->readmore = FALSE;
 
   // The 'view' hook can be implemented to overwrite the default function
   // to display nodes.
@@ -1370,7 +1372,7 @@ 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') {
+  if ($view_mode == 'teaser' && $node->readmore) {
     $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 modules/node/node.test modules/node/node.test
index 8a871c0..34515bb 100644
--- modules/node/node.test
+++ modules/node/node.test
@@ -2146,3 +2146,197 @@ 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.'));
+  }
+
+  /**
+   * Hide tags field in teaser and create a node without tags. The "Read more"
+   * link must not appear.
+   */
+  function testTeaserHiddenFieldEmpty() {
+    // Hide the tags in teaser.
+    $edit = array('fields[field_tags][type]' => 'hidden');
+    $this->drupalPost('admin/structure/types/manage/article/display/teaser', $edit, t('Save'));
+
+    // Post an article without 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['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.'));
+    // Confirm that promoted node does not appear with the read more link in the
+    // default node listing.
+    $this->assertNoText('Read more', t('"Read more" does not appear in the default listing.'));
+  }
+
+  /**
+   * Hide tags field in teaser and create a node with tags. The "Read more"
+   * link must be set.
+   */
+  function testTeaserHiddenFieldFilled() {
+    // Hide the tags in teaser.
+    $edit = array('fields[field_tags][type]' => 'hidden');
+    $this->drupalPost('admin/structure/types/manage/article/display/teaser', $edit, t('Save'));
+
+    // 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->assertNoText($tag, t('Tag does not appear on 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 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.'));
+  }
+
+}
