--- /dev/null
+<?php
+
+namespace Drupal\Tests\views\Kernel;
+
+use Drupal\entity_test\Entity\EntityTestMul;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\views\Views;
+
+/**
+ * Tests aggregate functionality of views, for example count.
+ *
+ * @group views
+ */
+class QueryGroupByTest extends ViewsKernelTestBase {
+
+ /**
+ * Views used by this test.
+ *
+ * @var array
+ */
+ public static $testViews = ['test_group_by_in_filters', 'test_aggregate_count', 'test_group_by_count', 'test_group_by_count_multicardinality', 'test_group_by_field_not_within_bundle'];
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = ['entity_test', 'system', 'field', 'user', 'language'];
+
+ /**
+ * The storage for the test entity type.
+ *
+ * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage
+ */
+ public $storage;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp($import_test_views = TRUE) {
+ parent::setUp();
+
+ $this->installEntitySchema('user');
+ $this->installEntitySchema('entity_test');
+ $this->installEntitySchema('entity_test_mul');
+
+ $this->storage = $this->container->get('entity.manager')->getStorage('entity_test');
+
+ ConfigurableLanguage::createFromLangcode('it')->save();
+ }
+
+
+ /**
+ * Tests aggregate count feature.
+ */
+ public function testAggregateCount() {
+ $this->setupTestEntities();
+
+ $view = Views::getView('test_aggregate_count');
+ $this->executeView($view);
+
+ $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+
+ $types = [];
+ foreach ($view->result as $item) {
+ // num_records is a alias for id.
+ $types[$item->entity_test_name] = $item->num_records;
+ }
+
+ $this->assertEqual($types['name1'], 4, 'Groupby the name: name1 returned the expected amount of results.');
+ $this->assertEqual($types['name2'], 3, 'Groupby the name: name2 returned the expected amount of results.');
+ }
+
+ /**
+ * Provides a test helper which runs a view with some aggregation function.
+ *
+ * @param string|null $aggregation_function
+ * Which aggregation function should be used, for example sum or count. If
+ * NULL is passed the aggregation will be tested with no function.
+ * @param array $values
+ * The expected views result.
+ */
+ public function groupByTestHelper($aggregation_function, $values) {
+ $this->setupTestEntities();
+
+ $view = Views::getView('test_group_by_count');
+ $view->setDisplay();
+ // There is no need for a function in order to have aggregation.
+ if (empty($aggregation_function)) {
+ // The test table has 2 fields ('id' and 'name'). We'll remove 'id'
+ // because it's unique and will test aggregation on 'name'.
+ unset($view->displayHandlers->get('default')->options['fields']['id']);
+ }
+ else {
+ $view->displayHandlers->get('default')->options['fields']['id']['group_type'] = $aggregation_function;
+ }
+
+ $this->executeView($view);
+
+ $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+ // Group by name to identify the right count.
+ $results = [];
+ foreach ($view->result as $item) {
+ $results[$item->entity_test_name] = $item->id;
+ }
+ $this->assertEqual($results['name1'], $values[0], format_string('Aggregation with @aggregation_function and groupby name: name1 returned the expected amount of results', ['@aggregation_function' => $aggregation_function]));
+ $this->assertEqual($results['name2'], $values[1], format_string('Aggregation with @aggregation_function and groupby name: name2 returned the expected amount of results', ['@aggregation_function' => $aggregation_function]));
+ }
+
+ /**
+ * Helper method that creates some test entities.
+ */
+ protected function setupTestEntities() {
+ // Create 4 entities with name1 and 3 entities with name2.
+ $entity_1 = [
+ 'name' => 'name1',
+ ];
+
+ $this->storage->create($entity_1)->save();
+ $this->storage->create($entity_1)->save();
+ $this->storage->create($entity_1)->save();
+ $this->storage->create($entity_1)->save();
+
+ $entity_2 = [
+ 'name' => 'name2',
+ ];
+ $this->storage->create($entity_2)->save();
+ $this->storage->create($entity_2)->save();
+ $this->storage->create($entity_2)->save();
+ }
+
+ /**
+ * Tests the count aggregation function.
+ */
+ public function testGroupByCount() {
+ $this->groupByTestHelper('count', [4, 3]);
+ }
+
+ /**
+ * Tests the sum aggregation function.
+ */
+ public function testGroupBySum() {
+ $this->groupByTestHelper('sum', [10, 18]);
+ }
+
+ /**
+ * Tests the average aggregation function.
+ */
+ public function testGroupByAverage() {
+ $this->groupByTestHelper('avg', [2.5, 6]);
+ }
+
+ /**
+ * Tests the min aggregation function.
+ */
+ public function testGroupByMin() {
+ $this->groupByTestHelper('min', [1, 5]);
+ }
+
+ /**
+ * Tests the max aggregation function.
+ */
+ public function testGroupByMax() {
+ $this->groupByTestHelper('max', [4, 7]);
+ }
+
+ /**
+ * Tests aggregation with no specific function.
+ */
+ public function testGroupByNone() {
+ $this->groupByTestHelper(NULL, [1, 5]);
+ }
+
+ /**
+ * Tests groupby with filters.
+ */
+ public function testGroupByCountOnlyFilters() {
+ // Check if GROUP BY and HAVING are included when a view
+ // doesn't display SUM, COUNT, MAX, etc. functions in SELECT statement.
+
+ for ($x = 0; $x < 10; $x++) {
+ $this->storage->create(['name' => 'name1'])->save();
+ }
+
+ $view = Views::getView('test_group_by_in_filters');
+ $this->executeView($view);
+
+ $this->assertTrue(strpos($view->build_info['query'], 'GROUP BY'), 'Make sure that GROUP BY is in the query');
+ $this->assertTrue(strpos($view->build_info['query'], 'HAVING'), 'Make sure that HAVING is in the query');
+ }
+
+ /**
+ * Tests grouping on base field.
+ */
+ public function testGroupByBaseField() {
+ $this->setupTestEntities();
+
+ $view = Views::getView('test_group_by_count');
+ $view->setDisplay();
+ // This tests that the GROUP BY portion of the query is properly formatted
+ // to include the base table to avoid ambiguous field errors.
+ $view->displayHandlers->get('default')->options['fields']['name']['group_type'] = 'min';
+ unset($view->displayHandlers->get('default')->options['fields']['id']['group_type']);
+ $this->executeView($view);
+ $this->assertTrue(strpos($view->build_info['query'], 'GROUP BY entity_test.id'), 'GROUP BY field includes the base table name when grouping on the base field.');
+ }
+
+ /**
+ * Tests grouping a field with cardinality > 1.
+ */
+ public function testGroupByFieldWithCardinality() {
+ $field_storage = FieldStorageConfig::create([
+ 'type' => 'integer',
+ 'field_name' => 'field_test',
+ 'cardinality' => 4,
+ 'entity_type' => 'entity_test_mul',
+ ]);
+ $field_storage->save();
+ $field = FieldConfig::create([
+ 'field_name' => 'field_test',
+ 'entity_type' => 'entity_test_mul',
+ 'bundle' => 'entity_test_mul',
+ ]);
+ $field->save();
+
+ $entities = [];
+ $entity = EntityTestMul::create([
+ 'field_test' => [1, 1, 1],
+ ]);
+ $entity->save();
+ $entities[] = $entity;
+
+ $entity = EntityTestMul::create([
+ 'field_test' => [2, 2, 2],
+ ]);
+ $entity->save();
+ $entities[] = $entity;
+
+ $entity = EntityTestMul::create([
+ 'field_test' => [2, 2, 2],
+ ]);
+ $entity->save();
+ $entities[] = $entity;
+
+ $view = Views::getView('test_group_by_count_multicardinality');
+ $this->executeView($view);
+ $this->assertEqual(2, count($view->result));
+
+ $this->assertEqual('3', $view->getStyle()->getField(0, 'id'));
+ $this->assertEqual('1', $view->getStyle()->getField(0, 'field_test'));
+ $this->assertEqual('6', $view->getStyle()->getField(1, 'id'));
+ $this->assertEqual('2', $view->getStyle()->getField(1, 'field_test'));
+
+ $entities[2]->field_test[0]->value = 3;
+ $entities[2]->field_test[1]->value = 4;
+ $entities[2]->field_test[2]->value = 5;
+ $entities[2]->save();
+
+ $view = Views::getView('test_group_by_count_multicardinality');
+ $this->executeView($view);
+ $this->assertEqual(5, count($view->result));
+
+ $this->assertEqual('3', $view->getStyle()->getField(0, 'id'));
+ $this->assertEqual('1', $view->getStyle()->getField(0, 'field_test'));
+ $this->assertEqual('3', $view->getStyle()->getField(1, 'id'));
+ $this->assertEqual('2', $view->getStyle()->getField(1, 'field_test'));
+ $this->assertEqual('1', $view->getStyle()->getField(2, 'id'));
+ $this->assertEqual('3', $view->getStyle()->getField(2, 'field_test'));
+ $this->assertEqual('1', $view->getStyle()->getField(3, 'id'));
+ $this->assertEqual('4', $view->getStyle()->getField(3, 'field_test'));
+ $this->assertEqual('1', $view->getStyle()->getField(4, 'id'));
+ $this->assertEqual('5', $view->getStyle()->getField(4, 'field_test'));
+
+ // Check that translated values are correctly retrieved and are not grouped
+ // into the original entity.
+ $translation = $entity->addTranslation('it');
+ $translation->field_test = [6, 6, 6];
+ $translation->save();
+
+ $view = Views::getView('test_group_by_count_multicardinality');
+ $this->executeView($view);
+
+ $this->assertEqual(6, count($view->result));
+ $this->assertEqual('3', $view->getStyle()->getField(5, 'id'));
+ $this->assertEqual('6', $view->getStyle()->getField(5, 'field_test'));
+ }
+
+ /**
+ * Tests groupby with a field not existing on some bundle.
+ */
+ public function testGroupByWithFieldsNotExistingOnBundle() {
+ $field_storage = FieldStorageConfig::create([
+ 'type' => 'integer',
+ 'field_name' => 'field_test',
+ 'cardinality' => 4,
+ 'entity_type' => 'entity_test_mul',
+ ]);
+ $field_storage->save();
+ $field = FieldConfig::create([
+ 'field_name' => 'field_test',
+ 'entity_type' => 'entity_test_mul',
+ 'bundle' => 'entity_test_mul',
+ ]);
+ $field->save();
+
+ $entities = [];
+ $entity = EntityTestMul::create([
+ 'field_test' => [1],
+ 'type' => 'entity_test_mul',
+ ]);
+ $entity->save();
+ $entities[] = $entity;
+
+ $entity = EntityTestMul::create([
+ 'type' => 'entity_test_mul2',
+ ]);
+ $entity->save();
+ $entities[] = $entity;
+
+ $view = Views::getView('test_group_by_field_not_within_bundle');
+ $this->executeView($view);
+
+ $this->assertEqual(2, count($view->result));
+ // The first result is coming from entity_test_mul2, so no field could be
+ // rendered.
+ $this->assertEqual('', $view->getStyle()->getField(0, 'field_test'));
+ // The second result is coming from entity_test_mul, so its field value
+ // could be rendered.
+ $this->assertEqual('1', $view->getStyle()->getField(1, 'field_test'));
+ }
+
+}