diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
index 7ed6663..aba35e2 100644
--- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
@@ -23,6 +23,15 @@
   use ThirdPartySettingsTrait;
 
   /**
+   * The 'mode' for runtime EntityDisplay objects used to render entities with
+   * arbitrary display options rather than a configured view mode or form mode.
+   *
+   * @todo Prevent creation of a mode with this ID
+   *   https://www.drupal.org/node/2410727
+   */
+  const CUSTOM_MODE = '_custom';
+
+  /**
    * Unique ID for the config entity.
    *
    * @var string
@@ -55,7 +64,7 @@
    *
    * @var string
    */
-  public $mode;
+  public $mode = self::CUSTOM_MODE;
 
   /**
    * Whether this display is enabled or not. If the entity (form) display
@@ -112,7 +121,7 @@
    * {@inheritdoc}
    */
   public function __construct(array $values, $entity_type) {
-    if (!isset($values['targetEntityType']) || !isset($values['bundle']) || !isset($values['mode'])) {
+    if (!isset($values['targetEntityType']) || !isset($values['bundle'])) {
       throw new \InvalidArgumentException('Missing required properties for an EntityDisplay entity.');
     }
 
@@ -156,13 +165,13 @@ public function preSave(EntityStorageInterface $storage, $update = TRUE) {
    */
   public function calculateDependencies() {
     parent::calculateDependencies();
-    $target_entity_type = \Drupal::entityManager()->getDefinition($this->targetEntityType);
+    $target_entity_type = $this->entityManager()->getDefinition($this->targetEntityType);
 
     $bundle_entity_type_id = $target_entity_type->getBundleEntityType();
     if ($bundle_entity_type_id != 'bundle') {
       // If the target entity type uses entities to manage its bundles then
       // depend on the bundle entity.
-      if (!$bundle_entity = \Drupal::entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
+      if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
         throw new \LogicException(String::format('Missing bundle entity, entity type %type, entity id %bundle.', array('%type' => $bundle_entity_type_id, '%bundle' => $this->bundle)));
       }
       $this->addDependency('config', $bundle_entity->getConfigDependencyName());
@@ -181,7 +190,7 @@ public function calculateDependencies() {
     }
     // Depend on configured modes.
     if ($this->mode != 'default') {
-      $mode_entity = \Drupal::entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
+      $mode_entity = $this->entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
       $this->addDependency('config', $mode_entity->getConfigDependencyName());
     }
     return $this->dependencies;
@@ -213,39 +222,42 @@ public function toArray() {
    * - or that are not supposed to be configurable.
    */
   protected function init() {
-    // Fill in defaults for extra fields.
-    $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
-    $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
-    $extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array();
-    foreach ($extra_fields as $name => $definition) {
-      if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
-        // Extra fields are visible by default unless they explicitly say so.
-        if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
-          $this->content[$name] = array(
-            'weight' => $definition['weight']
-          );
-        }
-        else {
-          $this->hidden[$name] = TRUE;
+    // Only populate defaults for "official" view modes and form modes.
+    if ($this->mode !== static::CUSTOM_MODE) {
+      // Fill in defaults for extra fields.
+      $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
+      $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
+      $extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array();
+      foreach ($extra_fields as $name => $definition) {
+        if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
+          // Extra fields are visible by default unless they explicitly say so.
+          if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
+            $this->content[$name] = array(
+              'weight' => $definition['weight']
+            );
+          }
+          else {
+            $this->hidden[$name] = TRUE;
+          }
         }
       }
-    }
-
-    // Fill in defaults for fields.
-    $fields = $this->getFieldDefinitions();
-    foreach ($fields as $name => $definition) {
-      if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
-        $options = $definition->getDisplayOptions($this->displayContext);
 
-        if (!empty($options['type']) && $options['type'] == 'hidden') {
-          $this->hidden[$name] = TRUE;
+      // Fill in defaults for fields.
+      $fields = $this->getFieldDefinitions();
+      foreach ($fields as $name => $definition) {
+        if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
+          $options = $definition->getDisplayOptions($this->displayContext);
+
+          if (!empty($options['type']) && $options['type'] == 'hidden') {
+            $this->hidden[$name] = TRUE;
+          }
+          elseif ($options) {
+            $this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
+          }
+          // Note: (base) fields that do not specify display options are not
+          // tracked in the display at all, in order to avoid cluttering the
+          // configuration that gets saved back.
         }
-        elseif ($options) {
-          $this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
-        }
-        // Note: (base) fields that do not specify display options are not
-        // tracked in the display at all, in order to avoid cluttering the
-        // configuration that gets saved back.
       }
     }
   }
@@ -348,7 +360,12 @@ protected function getFieldDefinitions() {
 
     if (!isset($this->fieldDefinitions)) {
       $definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
-      $this->fieldDefinitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
+      // For "official" view modes and form modes, ignore fields whose
+      // definition states they should not be displayed.
+      if ($this->mode !== static::CUSTOM_MODE) {
+        $definitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
+      }
+      $this->fieldDefinitions = $definitions;
     }
 
     return $this->fieldDefinitions;
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index a6688f4..420e787 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -475,7 +475,6 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
         $this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
           'targetEntityType' => $entity_type_id,
           'bundle' => $bundle,
-          'mode' => '_custom',
           'status' => TRUE,
         ))->setComponent($field_name, $display_options);
       }
diff --git a/core/modules/field/config/schema/field.views.schema.yml b/core/modules/field/config/schema/field.views.schema.yml
index 56d852d..aaf0697 100644
--- a/core/modules/field/config/schema/field.views.schema.yml
+++ b/core/modules/field/config/schema/field.views.schema.yml
@@ -18,7 +18,7 @@ views.argument.field_list_string:
 
 views.field.field:
   type: views_field
-  label: 'Log event message'
+  label: 'Views entity field handler'
   mapping:
     click_sort_column:
       type: string
@@ -27,11 +27,8 @@ views.field.field:
       type: string
       label: 'Formatter'
     settings:
-      type: sequence
       label: 'Settings'
-      sequence:
-        - type: string
-          label: 'Setting'
+      type: field.formatter.settings.[%parent.type]
     group_column:
       type: string
       label: 'Group by column'
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 0f444a1..3b9309d 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -29,7 +29,7 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\entity_test\EntityTestViewsData"
  *   },
  *   base_table = "entity_test",
  *   persistent_cache = FALSE,
diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php b/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php
new file mode 100644
index 0000000..0c9cd83
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/EntityTestViewsData.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\entity_test\EntityTestViewsData.
+ */
+
+namespace Drupal\entity_test;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\views\EntityViewsData;
+
+/**
+ * Provides a view to override views data for test entity types.
+ */
+class EntityTestViewsData extends EntityViewsData {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewsData() {
+    $views_data = parent::getViewsData();
+
+    if ($this->entityType->id() != 'entity_test') {
+      return $views_data;
+    }
+
+    $views_data = NestedArray::mergeDeep($views_data, \Drupal::state()->get('entity_test.views_data', []));
+
+    return $views_data;
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/ViewsHandlerManager.php b/core/modules/views/src/Plugin/ViewsHandlerManager.php
index 9d3fda4..b6aa487 100644
--- a/core/modules/views/src/Plugin/ViewsHandlerManager.php
+++ b/core/modules/views/src/Plugin/ViewsHandlerManager.php
@@ -91,7 +91,7 @@ public function getHandler($item, $override = NULL) {
 
     if (isset($data[$field][$this->handlerType])) {
       $definition = $data[$field][$this->handlerType];
-      foreach (array('group', 'title', 'title short', 'help', 'real field', 'real table', 'entity field') as $key) {
+      foreach (array('group', 'title', 'title short', 'help', 'real field', 'real table', 'entity type', 'entity field') as $key) {
         if (!isset($definition[$key])) {
           // First check the field level.
           if (!empty($data[$field][$key])) {
@@ -99,7 +99,8 @@ public function getHandler($item, $override = NULL) {
           }
           // Then if that doesn't work, check the table level.
           elseif (!empty($data['table'][$key])) {
-            $definition[$key] = $data['table'][$key];
+            $definition_key = $key === 'entity type' ? 'entity_type' : $key;
+            $definition[$definition_key] = $data['table'][$key];
           }
         }
       }
diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php
index bc13127..05f6335 100644
--- a/core/modules/views/src/Plugin/views/field/Field.php
+++ b/core/modules/views/src/Plugin/views/field/Field.php
@@ -13,7 +13,9 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldConfigInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
 use Drupal\Core\Field\FormatterPluginManager;
 use Drupal\Core\Form\FormHelper;
 use Drupal\Core\Form\FormStateInterface;
@@ -22,6 +24,7 @@
 use Drupal\Core\Render\Element;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\field\FieldStorageConfigInterface;
 use Drupal\views\Plugin\CacheablePluginInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
@@ -36,6 +39,8 @@
  *
  * @ingroup views_field_handlers
  *
+ * @todo Rename the class https://www.drupal.org/node/2408667
+ *
  * @ViewsField("field")
  */
 class Field extends FieldPluginBase implements CacheablePluginInterface, MultiItemsFieldHandlerInterface {
@@ -122,6 +127,13 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
   protected $renderer;
 
   /**
+   * The field type plugin manager.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+   */
+  protected $fieldTypePluginManager;
+
+  /**
    * Constructs a \Drupal\field\Plugin\views\field\Field object.
    *
    * @param array $configuration
@@ -134,19 +146,27 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
    *   The field formatter plugin manager.
    * @param \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager
    *   The field formatter plugin manager.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
+   *   The field plugin type manager.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer.
-   *
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->entityManager = $entity_manager;
     $this->formatterPluginManager = $formatter_plugin_manager;
+    $this->fieldTypePluginManager = $field_type_plugin_manager;
     $this->languageManager = $language_manager;
     $this->renderer = $renderer;
+
+    // @todo Unify 'entity field'/'field_name' instead of converting back and
+    //   forth. https://www.drupal.org/node/2410779
+    if (isset($this->definition['entity field'])) {
+      $this->definition['field_name'] = $this->definition['entity field'];
+    }
   }
 
   /**
@@ -159,6 +179,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('entity.manager'),
       $container->get('plugin.manager.field.formatter'),
+      $container->get('plugin.manager.field.field_type'),
       $container->get('language_manager'),
       $container->get('renderer')
     );
@@ -172,7 +193,7 @@ public static function create(ContainerInterface $container, array $configuratio
    */
   protected function getFieldDefinition() {
     if (!$this->fieldDefinition) {
-      $field_storage_config = $this->getFieldStorageConfig();
+      $field_storage_config = $this->getFieldStorageDefinition();
       $this->fieldDefinition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config);
     }
     return $this->fieldDefinition;
@@ -228,48 +249,17 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
    * {@inheritdoc}
    */
   public function access(AccountInterface $account) {
-    $base_table = $this->get_base_table();
-    $access_control_handler = $this->entityManager->getAccessControlHandler($this->definition['entity_tables'][$base_table]);
+    $access_control_handler = $this->entityManager->getAccessControlHandler($this->getEntityType());
     return $access_control_handler->fieldAccess('view', $this->getFieldDefinition(), $account);
   }
 
   /**
-   * Set the base_table and base_table_alias.
-   *
-   * @return string
-   *   The base table which is used in the current view "context".
-   */
-  function get_base_table() {
-    if (!isset($this->base_table)) {
-      // This base_table is coming from the entity not the field.
-      $this->base_table = $this->view->storage->get('base_table');
-
-      // If the current field is under a relationship you can't be sure that the
-      // base table of the view is the base table of the current field.
-      // For example a field from a node author on a node view does have users as base table.
-      if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
-        $relationships = $this->view->display_handler->getOption('relationships');
-        if (!empty($relationships[$this->options['relationship']])) {
-          $options = $relationships[$this->options['relationship']];
-          $data = Views::viewsData()->get($options['table']);
-          $this->base_table = $data[$options['field']]['relationship']['base'];
-        }
-      }
-    }
-
-    return $this->base_table;
-  }
-
-  /**
    * Called to add the field to a query.
    *
    * By default, all needed data is taken from entities loaded by the query
    * plugin. Columns are added only if they are used in groupings.
    */
   public function query($use_groupby = FALSE) {
-    $this->get_base_table();
-
-    $entity_type = $this->definition['entity_tables'][$this->base_table];
     $fields = $this->additional_fields;
     // No need to add the entity type.
     $entity_type_key = array_search('entity_type', $fields);
@@ -286,16 +276,10 @@ public function query($use_groupby = FALSE) {
       }
       $options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : array();
 
-      $fields = array();
-      $rkey = $this->definition['is revision'] ? EntityStorageInterface::FIELD_LOAD_REVISION : EntityStorageInterface::FIELD_LOAD_CURRENT;
       // Go through the list and determine the actual column name from field api.
+      $fields = array();
       foreach ($options as $column) {
-        $name = $column;
-        if (isset($field_definition['storage_details']['sql'][$rkey][$this->table][$column])) {
-          $name = $field_definition['storage_details']['sql'][$rkey][$this->table][$column];
-        }
-
-        $fields[$column] = $name;
+        $fields[$column] = $this->getTableMapping()->getFieldColumnName($this->getFieldStorageDefinition(), $column);
       }
 
       $this->group_fields = $fields;
@@ -370,12 +354,8 @@ public function clickSort($order) {
     }
 
     $this->ensureMyTable();
-    $entity_type_id = $this->definition['entity_type'];
-    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
-    $field_storage = $field_storage_definitions[$this->definition['field_name']];
-    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
-    $table_mapping = $this->entityManager->getStorage($entity_type_id)->getTableMapping();
-    $column = $table_mapping->getFieldColumnName($field_storage, $this->options['click_sort_column']);
+    $field_storage_definition = $this->getFieldStorageDefinition();
+    $column = $this->getTableMapping()->getFieldColumnName($field_storage_definition, $this->options['click_sort_column']);
     if (!isset($this->aliases[$column])) {
       // Column is not in query; add a sort on it (without adding the column).
       $this->aliases[$column] = $this->tableAlias . '.' . $column;
@@ -383,13 +363,36 @@ public function clickSort($order) {
     $this->query->addOrderBy(NULL, NULL, $order, $this->aliases[$column]);
   }
 
+  /**
+   * Gets the field storage of the used field.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  protected function getFieldStorageDefinition() {
+    $entity_type_id = $this->definition['entity_type'];
+    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
+
+    $field_storage = NULL;
+    // @todo Unify 'entity field'/'field_name' instead of converting back and
+    //   forth. https://www.drupal.org/node/2410779
+    if (isset($this->definition['field_name'])) {
+      $field_storage = $field_storage_definitions[$this->definition['field_name']];
+    }
+    elseif (isset($this->definition['entity field'])) {
+      $field_storage = $field_storage_definitions[$this->definition['entity field']];
+    }
+    return $field_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function defineOptions() {
     $options = parent::defineOptions();
 
-    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->definition['entity_type']);
-    $field_storage = $field_storage_definitions[$this->definition['field_name']];
-    $field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType());
-    $column_names = array_keys($field_storage->getColumns());
+    $field_storage_definition = $this->getFieldStorageDefinition();
+    $field_type = $this->fieldTypePluginManager->getDefinition($field_storage_definition->getType());
+    $column_names = array_keys($field_storage_definition->getColumns());
     $default_column = '';
     // Try to determine a sensible default.
     if (count($column_names) == 1) {
@@ -403,11 +406,13 @@ protected function defineOptions() {
     $options['click_sort_column'] = array(
       'default' => $default_column,
     );
+
     $options['type'] = array(
-      'default' => $field_type['default_formatter'],
+      'default' => isset($field_type['default_formatter']) ? $field_type['default_formatter'] : '',
     );
+
     $options['settings'] = array(
-      'default' => array(),
+      'default' => isset($this->definition['default_formatter_settings']) ? $this->definition['default_formatter_settings'] : [],
     );
     $options['group_column'] = array(
       'default' => $default_column,
@@ -423,7 +428,7 @@ protected function defineOptions() {
     // If we know the exact number of allowed values, then that can be
     // the default. Otherwise, default to 'all'.
     $options['delta_limit'] = array(
-      'default' => ($field_storage->getCardinality() > 1) ? $field_storage->getCardinality() : 'all',
+      'default' => ($field_storage_definition->getCardinality() > 1) ? $field_storage_definition->getCardinality() : 'all',
     );
     $options['delta_offset'] = array(
       'default' => 0,
@@ -756,7 +761,7 @@ public function getItems(ResultRow $values) {
 
     $items = array();
     if ($this->options['field_api_classes']) {
-      return array(array('rendered' => drupal_render($render_array)));
+      return array(array('rendered' => $this->renderer->render($render_array)));
     }
 
     foreach (Element::children($render_array) as $count) {
@@ -955,9 +960,11 @@ public function calculateDependencies() {
     $dependencies = parent::calculateDependencies();
 
     // Add the module providing the configured field storage as a dependency.
-    $dependencies['config'][] = $this->getFieldStorageConfig()->getConfigDependencyName();
+    if (($field_storage_definition = $this->getFieldStorageDefinition()) && $field_storage_definition instanceof EntityInterface) {
+      $dependencies['config'][] = $field_storage_definition->getConfigDependencyName();
+    }
     // Add the module providing the formatter.
-    if ($this->options['type']) {
+    if (!empty($this->options['type'])) {
       $dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
     }
 
@@ -983,4 +990,25 @@ public function getCacheContexts() {
     return $contexts;
   }
 
+  /**
+   * Gets the table mapping for the entity type of the field.
+   *
+   * @return \Drupal\Core\Entity\Sql\DefaultTableMapping
+   *   The table mapping.
+   */
+  protected function getTableMapping() {
+    return $this->entityManager->getStorage($this->definition['entity_type'])->getTableMapping();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue(ResultRow $values, $field = NULL) {
+    if ($field === NULL) {
+      return $this->getEntity($values)->{$this->definition['field_name']}->value;
+    }
+
+    return $this->getEntity($values)->{$this->definition['field_name']}->$field;
+  }
+
 }
diff --git a/core/modules/views/src/Tests/Handler/FieldFieldTest.php b/core/modules/views/src/Tests/Handler/FieldFieldTest.php
new file mode 100644
index 0000000..75f3878
--- /dev/null
+++ b/core/modules/views/src/Tests/Handler/FieldFieldTest.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\views\Tests\Handler\FieldFieldTest.
+ */
+
+namespace Drupal\views\Tests\Handler;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\views\Plugin\views\field\Field;
+use Drupal\views\Tests\ViewUnitTestBase;
+use Drupal\views\Views;
+
+/**
+ * Provides some integration tests for the Field handler.
+ *
+ * @see \Drupal\views\Plugin\views\field\Field
+ * @group views
+ */
+class FieldFieldTest extends ViewUnitTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['field', 'entity_test', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $testViews = ['test_field_field_test'];
+
+  /**
+   * The stored test entities.
+   *
+   * @var \Drupal\entity_test\Entity\EntityTest[]
+   */
+  protected $entities;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('entity_test');
+
+    // Setup a field storage and field, but also change the views data for the
+    // entity_test entity type.
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => 'field_test',
+      'type' => 'integer',
+      'entity_type' => 'entity_test',
+    ]);
+    $field_storage->save();
+
+    $field = FieldConfig::create([
+      'field_name' => 'field_test',
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    ]);
+    $field->save();
+
+    $random_number = (string) 30856;
+    for ($i = 0; $i < 5; $i++) {
+      $this->entities[$i] = $entity = EntityTest::create([
+        'bundle' => 'entity_test',
+        'field_test' => $random_number[$i],
+      ]);
+      $entity->save();
+    }
+
+    \Drupal::state()->set('entity_test.views_data', [
+      'entity_test' => [
+        'id' => [
+          'field' => [
+            'id' => 'field',
+          ],
+        ],
+      ],
+    ]);
+
+    Views::viewsData()->clear();
+  }
+
+  /**
+   * Tests the result of a view with base fields and configurable fields.
+   */
+  public function testSimpleExecute() {
+    $executable = Views::getView('test_field_field_test');
+    $executable->execute();
+
+    $this->assertTrue($executable->field['id'] instanceof Field);
+    $this->assertTrue($executable->field['field_test'] instanceof Field);
+
+    $this->assertIdenticalResultset($executable, [
+      ['id' => 1, 'field_test' => 3],
+      ['id' => 2, 'field_test' => 0],
+      ['id' => 3, 'field_test' => 8],
+      ['id' => 4, 'field_test' => 5],
+      ['id' => 5, 'field_test' => 6],
+    ],
+      ['id' => 'id', 'field_test' => 'field_test']
+    );
+  }
+
+  /**
+   * Tests the output of a view with base fields and configurable fields.
+   */
+  public function testSimpleRender() {
+    $executable = Views::getView('test_field_field_test');
+    $executable->execute();
+
+    $this->assertEqual(1, $executable->getStyle()->getField(0, 'id'));
+    $this->assertEqual(3, $executable->getStyle()->getField(0, 'field_test'));
+    $this->assertEqual(2, $executable->getStyle()->getField(1, 'id'));
+    $this->assertEqual(0, $executable->getStyle()->getField(1, 'field_test'));
+    $this->assertEqual(3, $executable->getStyle()->getField(2, 'id'));
+    $this->assertEqual(8, $executable->getStyle()->getField(2, 'field_test'));
+    $this->assertEqual(4, $executable->getStyle()->getField(3, 'id'));
+    $this->assertEqual(5, $executable->getStyle()->getField(3, 'field_test'));
+    $this->assertEqual(5, $executable->getStyle()->getField(4, 'id'));
+    $this->assertEqual(6, $executable->getStyle()->getField(4, 'field_test'));
+  }
+
+}
diff --git a/core/modules/views/src/Tests/ViewResultAssertionTrait.php b/core/modules/views/src/Tests/ViewResultAssertionTrait.php
index 41692e8..4f730ca 100644
--- a/core/modules/views/src/Tests/ViewResultAssertionTrait.php
+++ b/core/modules/views/src/Tests/ViewResultAssertionTrait.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests;
 
+use Drupal\views\Plugin\views\field\Field;
 use Drupal\views\ViewExecutable;
 
 /**
@@ -90,8 +91,15 @@ protected function assertIdenticalResultsetHelper($view, $expected_result, $colu
     foreach ($view->result as $key => $value) {
       $row = array();
       foreach ($column_map as $view_column => $expected_column) {
+        if (property_exists($value, $view_column)) {
+          $row[$expected_column] = (string) $value->$view_column;
+        }
         // The comparison will be done on the string representation of the value.
-        $row[$expected_column] = (string) $value->$view_column;
+        // For entity fields we don't have the raw value. Let's try to fetch it
+        // using the entity itself.
+        elseif (empty($value->$view_column) && isset($view->field[$expected_column]) && ($field = $view->field[$expected_column]) && $field instanceof Field) {
+          $row[$expected_column] = $field->getEntity($value)->{$field->definition['field_name']}->value;
+        }
       }
       $result[$key] = $row;
     }
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml
new file mode 100644
index 0000000..c0005ca
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml
@@ -0,0 +1,38 @@
+langcode: und
+status: true
+dependencies: {  }
+id: test_field_field_test
+module: views
+description: ''
+tag: ''
+base_table: entity_test
+base_field: id
+core: '8'
+display:
+  default:
+    display_options:
+      access:
+        type: none
+      cache:
+        type: none
+      fields:
+        id:
+          id: id
+          table: entity_test
+          field: id
+          plugin_id: field
+          entity_type: entity_test
+          entity_field: id
+        field_test:
+          id: field_test
+          table: entity_test__field_test
+          field: field_test
+          plugin_id: field
+          entity_type: entity_test
+          entity_field: field_test
+      style:
+        type: html_list
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: 0
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_rows.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_rows.yml
index e978187..588bc71 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_rows.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_rows.yml
@@ -87,7 +87,7 @@ display:
           click_sort_column: value
           type: string
           settings:
-            link_to_entity: '0'
+            link_to_entity: false
           group_column: value
           group_columns: {  }
           group_rows: true
diff --git a/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php
new file mode 100644
index 0000000..12b7ca1
--- /dev/null
+++ b/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\views\Unit\Plugin\HandlerBaseTest.
+ */
+
+namespace Drupal\Tests\views\Unit\Plugin;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\views\Plugin\views\HandlerBase;
+
+/**
+ * @coversDefaultClass \Drupal\views\Plugin\views\HandlerBase
+ * @group Views
+ */
+class HandlerBaseTest extends UnitTestCase {
+
+  use HandlerTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->setupViewsData();
+    $this->setupExecutableAndView();
+    $this->setupDisplay();
+  }
+
+  /**
+   * @covers ::getEntityType
+   */
+  public function testGetEntityTypeForFieldOnBaseTable() {
+    $handler = new TestHandler([], 'test_handler', []);
+    $handler->init($this->executable, $this->display);
+
+    $this->view->expects($this->any())
+      ->method('get')
+      ->with('base_table')
+      ->willReturn('test_entity_type_table');
+    $this->viewsData->expects($this->any())
+      ->method('get')
+      ->with('test_entity_type_table')
+      ->willReturn([
+        'table' => ['entity type' => 'test_entity_type']
+      ]);
+    $handler->setViewsData($this->viewsData);
+
+    $this->assertEquals('test_entity_type', $handler->getEntityType());
+  }
+
+  /**
+   * @covers ::getEntityType
+   */
+  public function testGetEntityTypeForFieldWithRelationship() {
+    $handler = new TestHandler([], 'test_handler', []);
+
+    $options = ['relationship' => 'test_relationship'];
+    $handler->init($this->executable, $this->display, $options);
+
+    $this->display->expects($this->atLeastOnce())
+      ->method('getOption')
+      ->with('relationships')
+      ->willReturn(['test_relationship' => ['table' => 'test_entity_type_table', 'id' => 'test_relationship', 'field' => 'test_relationship']]);
+
+    $this->view->expects($this->any())
+      ->method('get')
+      ->with('base_table')
+      ->willReturn('test_entity_type_table');
+
+    $this->viewsData->expects($this->any())
+      ->method('get')
+      ->willReturnMap([
+        ['test_entity_type_table', [
+          'table' => ['entity type' => 'test_entity_type'],
+          'test_relationship' => [
+            'relationship' => [
+              'base' => 'test_other_entity_type_table',
+              'base field' => 'id',
+            ],
+          ],
+        ]],
+        ['test_other_entity_type_table', [
+          'table' => ['entity type' => 'test_other_entity_type'],
+        ]],
+      ]);
+    $handler->setViewsData($this->viewsData);
+
+    $this->assertEquals('test_other_entity_type', $handler->getEntityType());
+  }
+
+}
+
+/**
+ * Allow testing base handler implementation by extending the abstract class.
+ */
+class TestHandler extends HandlerBase {
+
+}
diff --git a/core/modules/views/tests/src/Unit/Plugin/HandlerTestTrait.php b/core/modules/views/tests/src/Unit/Plugin/HandlerTestTrait.php
new file mode 100644
index 0000000..231546c
--- /dev/null
+++ b/core/modules/views/tests/src/Unit/Plugin/HandlerTestTrait.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\views\Unit\Plugin\HandlerTestTrait.
+ */
+
+namespace Drupal\Tests\views\Unit\Plugin;
+
+/**
+ * Test trait to mock dependencies of a handler.
+ */
+trait HandlerTestTrait {
+
+  /**
+   * The mocked view entity.
+   *
+   * @var \Drupal\views\Entity\View|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $view;
+
+  /**
+   * The mocked view executable.
+   *
+   * @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $executable;
+
+  /**
+   * The mocked views data.
+   *
+   * @var \Drupal\views\ViewsData|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $viewsData;
+
+  /**
+   * The mocked display.
+   *
+   * @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $display;
+
+  /**
+   * Sets up a view executable and a view entity.
+   */
+  protected function setupExecutableAndView() {
+    $this->view = $this->getMockBuilder('Drupal\views\Entity\View')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->executable->storage = $this->view;
+  }
+
+  /**
+   * Sets up a mocked views data object.
+   */
+  protected function setupViewsData() {
+    $this->viewsData = $this->getMockBuilder('Drupal\views\ViewsData')
+      ->disableOriginalConstructor()
+      ->getMock();
+  }
+
+  /**
+   * Sets up a mocked display object.
+   */
+  protected function setupDisplay() {
+    $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
+      ->disableOriginalConstructor()
+      ->getMock();
+  }
+
+}
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
new file mode 100644
index 0000000..90ac97c
--- /dev/null
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
@@ -0,0 +1,568 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\views\Unit\Plugin\field\FieldTest.
+ */
+
+namespace Drupal\Tests\views\Unit\Plugin\field;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Tests\views\Unit\Plugin\HandlerTestTrait;
+use Drupal\views\Plugin\views\field\Field;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * @coversDefaultClass \Drupal\views\Plugin\views\field\Field
+ * @group views
+ */
+class FieldTest extends UnitTestCase {
+
+  use HandlerTestTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mocked formatter plugin manager.
+   *
+   * @var \Drupal\Core\Field\FormatterPluginManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $formatterPluginManager;
+
+  /**
+   * The mocked language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The mocked field type plugin manager.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $fieldTypePluginManager;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $renderer;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->formatterPluginManager = $this->getMockBuilder('Drupal\Core\Field\FormatterPluginManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->fieldTypePluginManager = $this->getMock('Drupal\Core\Field\FieldTypePluginManagerInterface');
+    $this->fieldTypePluginManager->expects($this->any())
+      ->method('getDefaultStorageSettings')
+      ->willReturn([]);
+    $this->fieldTypePluginManager->expects($this->any())
+      ->method('getDefaultFieldSettings')
+      ->willReturn([]);
+
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->renderer = $this->getMock('Drupal\Core\Render\RendererInterface');
+
+    $this->setupExecutableAndView();
+    $this->setupViewsData();
+    $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * @covers ::__construct
+   */
+  public function testConstruct() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      // Just provide 'entity field' as definition. This is how EntityViewsData
+      // provides it.
+      'entity field' => 'title',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+
+    $this->assertEquals('title', $handler->definition['field_name']);
+  }
+
+  /**
+   * @covers ::defineOptions()
+   */
+  public function testDefineOptionsWithNoOptions() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title'
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+
+    // Setup the entity manager to allow fetching the storage definitions.
+    $title_storage = $this->getBaseFieldStorage();
+
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $title_storage,
+      ]);
+
+    $options = [];
+    $handler->init($this->executable, $this->display, $options);
+
+    $this->assertEquals('value', $handler->options['group_column']);
+    $this->assertEquals('all', $handler->options['delta_limit']);
+  }
+
+  /**
+   * @covers ::defineOptions()
+   */
+  public function testDefineOptionsWithDefaultFormatter() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title',
+      'default_formatter_settings' => ['link_to_entity' => TRUE]
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+
+    // Setup the entity manager to allow fetching the storage definitions.
+    $title_storage = $this->getBaseFieldStorage();
+
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $title_storage,
+      ]);
+
+    $options = [];
+    $handler->init($this->executable, $this->display, $options);
+
+    $this->assertEquals(['link_to_entity' => TRUE], $handler->options['settings']);
+  }
+
+  /**
+   * @covers ::calculateDependencies()
+   */
+  public function testCalculateDependenciesWithBaseField() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title'
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+
+    $title_storage = $this->getBaseFieldStorage();
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $title_storage,
+      ]);
+
+    $dependencies = $handler->calculateDependencies();
+    $this->assertEmpty($dependencies);
+  }
+
+  /**
+   * @covers ::calculateDependencies()
+   */
+  public function testCalculateDependenciesWithConfiguredField() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'body'
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+
+    $body_storage = $this->getConfigFieldStorage();
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'body' => $body_storage,
+      ]);
+
+    $body_storage->expects($this->atLeastOnce())
+      ->method('getConfigDependencyName')
+      ->willReturn('field.field_storage_config.body');
+
+    $dependencies = $handler->calculateDependencies();
+    $this->assertEquals(['config' => ['field.field_storage_config.body']], $dependencies);
+  }
+
+  /**
+   * @covers ::access()
+   */
+  public function testAccess() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+    $handler->setViewsData($this->viewsData);
+
+    $this->view->expects($this->atLeastOnce())
+      ->method('get')
+      ->with('base_table')
+      ->willReturn('test_entity_table');
+
+    $this->viewsData->expects($this->atLeastOnce())
+      ->method('get')
+      ->with('test_entity_table')
+      ->willReturn([
+        'table' => ['entity type' => 'test_entity']
+      ]);
+
+    $access_control_handler = $this->getMock('Drupal\Core\Entity\EntityAccessControlHandlerInterface');
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getAccessControlHandler')
+      ->with('test_entity')
+      ->willReturn($access_control_handler);
+
+    $title_storage = $this->getBaseFieldStorage();
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $title_storage,
+      ]);
+
+    $account = $this->getMock('Drupal\Core\Session\AccountInterface');
+
+    $access_control_handler->expects($this->atLeastOnce())
+      ->method('fieldAccess')
+      ->with('view', $this->anything(), $account, NULL, FALSE)
+      ->willReturn(TRUE);
+
+    $this->assertTrue($handler->access($account));
+  }
+
+  /**
+   * @dataProvider providerSortOrders
+   *
+   * @param string $order
+   *   The sort order.
+   *
+   * @covers ::clickSort
+   */
+  public function testClickSortWithOutConfiguredColumn($order) {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+
+    $this->entityManager->expects($this->never())
+      ->method('getFieldStorageDefinitions');
+
+    $handler->clickSort($order);
+  }
+
+  /**
+   * @dataProvider providerSortOrders
+   *
+   * @param string $order
+   *   The sort order.
+   *
+   * @covers ::clickSort
+   */
+  public function testClickSortWithBaseField($order) {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+
+    $field_storage = $this->getBaseFieldStorage();
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $field_storage,
+      ]);
+
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping
+      ->expects($this->atLeastOnce())
+      ->method('getFieldColumnName')
+      ->with($field_storage, 'value')
+      ->willReturn('title');
+    $entity_storage = $this->getMock('Drupal\Core\Entity\Sql\SqlEntityStorageInterface');
+    $entity_storage->expects($this->atLeastOnce())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getStorage')
+      ->with('test_entity')
+      ->willReturn($entity_storage);
+
+    // Setup a click sort configuration.
+    $options = [
+      'click_sort_column' => 'value',
+      'table' => 'test_entity',
+    ];
+    $handler->init($this->executable, $this->display, $options);
+
+    $handler->query = $this->getMockBuilder('Drupal\views\Plugin\views\query\Sql')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $handler->query->expects($this->atLeastOnce())
+      ->method('ensureTable')
+      ->with('test_entity', NULL)
+      ->willReturn('test_entity');
+
+    $handler->query->expects($this->atLeastOnce())
+      ->method('addOrderBy')
+      ->with(NULL, NULL, $order, 'test_entity.title', []);
+    $handler->clickSort($order);
+  }
+
+  /**
+   * @dataProvider providerSortOrders
+   *
+   * @param string $order
+   *   The sort order.
+   *
+   * @covers ::clickSort
+   */
+  public function testClickSortWithConfiguredField($order) {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'body',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+
+    $field_storage = $this->getConfigFieldStorage();
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'body' => $field_storage,
+      ]);
+
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping
+      ->expects($this->atLeastOnce())
+      ->method('getFieldColumnName')
+      ->with($field_storage, 'value')
+      ->willReturn('body_value');
+    $entity_storage = $this->getMock('Drupal\Core\Entity\Sql\SqlEntityStorageInterface');
+    $entity_storage->expects($this->atLeastOnce())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+    $this->entityManager->expects($this->atLeastOnce())
+      ->method('getStorage')
+      ->with('test_entity')
+      ->willReturn($entity_storage);
+
+    // Setup a click sort configuration.
+    $options = [
+      'click_sort_column' => 'value',
+      'table' => 'test_entity__body',
+    ];
+    $handler->init($this->executable, $this->display, $options);
+
+    $handler->query = $this->getMockBuilder('Drupal\views\Plugin\views\query\Sql')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $handler->query->expects($this->atLeastOnce())
+      ->method('ensureTable')
+      ->with('test_entity__body', NULL)
+      ->willReturn('test_entity__body_alias');
+
+    $handler->query->expects($this->atLeastOnce())
+      ->method('addOrderBy')
+      ->with(NULL, NULL, $order, 'test_entity__body_alias.body_value', []);
+    $handler->clickSort($order);
+  }
+
+  /**
+   * @covers ::query
+   */
+  public function testQueryWithGroupByForBaseField() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'title',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+
+    $field_storage = $this->getBaseFieldStorage();
+    $this->entityManager->expects($this->any())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'title' => $field_storage,
+      ]);
+
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping
+      ->expects($this->any())
+      ->method('getFieldColumnName')
+      ->with($field_storage, 'value')
+      ->willReturn('title');
+    $entity_storage = $this->getMock('Drupal\Core\Entity\Sql\SqlEntityStorageInterface');
+    $entity_storage->expects($this->any())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+    $this->entityManager->expects($this->any())
+      ->method('getStorage')
+      ->with('test_entity')
+      ->willReturn($entity_storage);
+
+    $options = [
+      'group_column' => 'value',
+      'group_columns' => [],
+      'table' => 'test_entity_table',
+    ];
+    $handler->init($this->executable, $this->display, $options);
+
+    $query = $this->getMockBuilder('Drupal\views\Plugin\views\query\Sql')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $query->expects($this->once())
+      ->method('ensureTable')
+      ->with('test_entity_table', NULL)
+      ->willReturn('test_entity_table');
+    // Ensure that we add the title field to the query, if we group by some
+    // other field in the view.
+    $query->expects($this->once())
+      ->method('addField')
+      ->with('test_entity_table', 'title');
+
+    $this->executable->query = $query;
+
+    $handler->query(TRUE);
+  }
+
+  /**
+   * @covers ::query
+   */
+  public function testQueryWithGroupByForConfigField() {
+    $definition = [
+      'entity_type' => 'test_entity',
+      'field_name' => 'body',
+    ];
+    $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+    $handler->view = $this->executable;
+
+    $field_storage = $this->getConfigFieldStorage();
+    $this->entityManager->expects($this->any())
+      ->method('getFieldStorageDefinitions')
+      ->with('test_entity')
+      ->willReturn([
+        'body' => $field_storage,
+      ]);
+
+    $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+    $table_mapping
+      ->expects($this->any())
+      ->method('getFieldColumnName')
+      ->with($field_storage, 'value')
+      ->willReturn('body_value');
+    $entity_storage = $this->getMock('Drupal\Core\Entity\Sql\SqlEntityStorageInterface');
+    $entity_storage->expects($this->any())
+      ->method('getTableMapping')
+      ->willReturn($table_mapping);
+    $this->entityManager->expects($this->any())
+      ->method('getStorage')
+      ->with('test_entity')
+      ->willReturn($entity_storage);
+
+    $options = [
+      'group_column' => 'value',
+      'group_columns' => [],
+      'table' => 'test_entity__body',
+    ];
+    $handler->init($this->executable, $this->display, $options);
+
+    $query = $this->getMockBuilder('Drupal\views\Plugin\views\query\Sql')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $query->expects($this->once())
+      ->method('ensureTable')
+      ->with('test_entity__body', NULL)
+      ->willReturn('test_entity__body');
+    // Ensure that we add the title field to the query, if we group by some
+    // other field in the view.
+    $query->expects($this->once())
+      ->method('addField')
+      ->with('test_entity__body', 'body_value');
+
+    $this->executable->query = $query;
+
+    $handler->query(TRUE);
+  }
+
+  /**
+   * Returns a mocked base field storage object.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected function getBaseFieldStorage() {
+    $title_storage = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $title_storage->expects($this->any())
+      ->method('getColumns')
+      ->willReturn(['value' => ['type' => 'varchar']]);
+    $title_storage->expects($this->any())
+      ->method('getSettings')
+      ->willReturn([]);
+    $title_storage->expects($this->any())
+      ->method('getConstraints')
+      ->willReturn([]);
+    return $title_storage;
+  }
+
+  /**
+   * Returns a mocked configurable field storage object.
+   *
+   * @return \Drupal\field\FieldStorageConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected function getConfigFieldStorage() {
+    $title_storage = $this->getMock('Drupal\field\FieldStorageConfigInterface');
+    $title_storage->expects($this->any())
+      ->method('getColumns')
+      ->willReturn(['value' => ['type' => 'varchar']]);
+    $title_storage->expects($this->any())
+      ->method('getSettings')
+      ->willReturn([]);
+    $title_storage->expects($this->any())
+      ->method('getConstraints')
+      ->willReturn([]);
+    return $title_storage;
+  }
+
+  /**
+   * Provides sort orders for clickSort() test methods.
+   *
+   * @return array
+   */
+  public function providerSortOrders() {
+    return [
+      ['asc'],
+      ['desc'],
+      ['ASC'],
+      ['DESC'],
+    ];
+  }
+
+}
