og-8.x-1.x-dev/tests/src/Kernel/Views/OgGroupRelationshipsViewTest.php

tests/src/Kernel/Views/OgGroupRelationshipsViewTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\og\Kernel\Views;

use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
use Drupal\node\Entity\NodeType;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;

/**
 * Tests the OG Views relationships that aggregate across audience fields.
 *
 * @group og
 */
class OgGroupRelationshipsViewTest extends ViewsKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'field',
    'node',
    'views',
    'og',
    'og_test',
    'options',
  ];

  /**
   * Views used by this test.
   *
   * @var list<string>
   *
   * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint
   */
  public static $testViews = [
    'test_og_group_content_to_group',
    'test_og_group_to_group_content',
  ];

  /**
   * Machine name of the group bundle used for groups in this test.
   */
  protected string $groupBundle;

  /**
   * Machine name of the content bundle used for group content.
   */
  protected string $contentBundle;

  /**
   * First group entity used in the fixtures.
   */
  protected NodeInterface $groupA;

  /**
   * Second group entity used in the fixtures.
   */
  protected NodeInterface $groupB;

  /**
   * Group content entity that references the two groups.
   */
  protected NodeInterface $contentNode;

  /**
   * Field name of the first OG audience reference field.
   */
  protected string $audienceField1;

  /**
   * Field name of the second OG audience reference field.
   */
  protected string $audienceField2;

  /**
   * {@inheritdoc}
   */
  protected function setUp($import_test_views = FALSE): void {
    parent::setUp($import_test_views);
    // Register the test views from og_test module.
    ViewTestData::createTestViews(static::class, ['og_test']);
  }

  /**
   * {@inheritdoc}
   */
  protected function setUpFixtures() {
    // Install OG config and required entity schemas.
    $this->installConfig(['og']);
    $this->installConfig(['og_test']);
    $this->installEntitySchema('og_membership');
    $this->installEntitySchema('user');
    $this->installEntitySchema('node');

    // Create a group bundle and designate it as an OG group.
    $this->groupBundle = mb_strtolower($this->randomMachineName());
    NodeType::create([
      'type' => $this->groupBundle,
      'name' => $this->randomString(),
    ])->save();
    Og::groupTypeManager()->addGroup('node', $this->groupBundle);

    // Create a content bundle.
    $this->contentBundle = mb_strtolower($this->randomMachineName());
    NodeType::create([
      'type' => $this->contentBundle,
      'name' => $this->randomString(),
    ])->save();

    // Add two distinct OG audience fields to the content bundle.
    $this->audienceField1 = mb_strtolower($this->randomMachineName());
    $this->audienceField2 = mb_strtolower($this->randomMachineName());

    Og::createField(OgGroupAudienceHelperInterface::DEFAULT_FIELD, 'node', $this->contentBundle, ['field_name' => $this->audienceField1]);
    Og::createField(OgGroupAudienceHelperInterface::DEFAULT_FIELD, 'node', $this->contentBundle, ['field_name' => $this->audienceField2]);

    // Create two groups.
    $this->groupA = Node::create([
      'type' => $this->groupBundle,
      'title' => $this->randomString(),
    ]);
    $this->groupA->save();

    $this->groupB = Node::create([
      'type' => $this->groupBundle,
      'title' => $this->randomString(),
    ]);
    $this->groupB->save();

    // Create one content node that references two groups via the two audience
    // fields.
    $this->contentNode = Node::create([
      'type' => $this->contentBundle,
      'title' => $this->randomString(),
      $this->audienceField1 => [
        ['target_id' => (int) $this->groupA->id()],
      ],
      $this->audienceField2 => [
        ['target_id' => (int) $this->groupB->id()],
      ],
    ]);
    $this->contentNode->save();

    parent::setUpFixtures();
  }

  /**
   * Verifies group content → group relationship duplicates per audience ref.
   */
  public function testGroupContentToGroupRelationship(): void {
    $view = Views::getView('test_og_group_content_to_group');

    // Filter the base to the content bundle so we only consider our content
    // node.
    $view->setDisplay('default');
    $view->display_handler->setOption('filters', [
      'type' => [
        'id' => 'type',
        'table' => 'node_field_data',
        'field' => 'type',
        'value' => [$this->contentBundle => $this->contentBundle],
        'entity_type' => 'node',
        'entity_field' => 'type',
        'plugin_id' => 'bundle',
      ],
    ]);

    $this->executeView($view);

    // Expect two rows for the single content, one per related group across the
    // two different audience fields.
    $this->assertCount(2, $view->result, 'Content appears once per audience relationship.');

    $nids = array_map(fn($row) => (int) $row->_entity->id(), $view->result);
    $this->assertEquals([$this->contentNode->id(), $this->contentNode->id()], $nids, 'Both rows are the same content nid.');
  }

  /**
   * Verifies group → group content relationship returns the two groups.
   */
  public function testGroupToGroupContentRelationship(): void {
    $view = Views::getView('test_og_group_to_group_content');

    // Filter the base to the group bundle so the base rows are groups.
    $view->setDisplay('default');
    $view->display_handler->setOption('filters', [
      'type' => [
        'id' => 'type',
        'table' => 'node_field_data',
        'field' => 'type',
        'value' => [$this->groupBundle => $this->groupBundle],
        'entity_type' => 'node',
        'entity_field' => 'type',
        'plugin_id' => 'bundle',
      ],
    ]);

    $this->executeView($view);

    // Each of the two groups is related to the single content entity via a
    // different audience field, so we expect both groups to appear.
    $this->assertCount(2, $view->result, 'Both groups appear once.');

    $nids = array_map(fn($row) => (int) $row->_entity->id(), $view->result);
    sort($nids);
    $expected = [
      (int) $this->groupA->id(),
      (int) $this->groupB->id(),
    ];
    sort($expected);
    $this->assertEquals($expected, $nids, 'The two groups are returned.');
  }

  /**
   * Verifies no duplicates when both audience fields reference the same group.
   */
  public function testNoDuplicateWhenSameGroupAcrossMultipleFields(): void {
    // Create an additional content node that references the same group in
    // both audience fields. The UNION mapping should deduplicate the pair
    // (entity_id, group_id) so the joined result has a single row.
    $content_node = Node::create([
      'type' => $this->contentBundle,
      'title' => $this->randomString(),
      $this->audienceField1 => [
        ['target_id' => (int) $this->groupA->id()],
      ],
      $this->audienceField2 => [
        ['target_id' => (int) $this->groupA->id()],
      ],
    ]);
    $content_node->save();

    $view = Views::getView('test_og_group_content_to_group');
    $view->setDisplay('default');

    // Filter to only this content node using both type and nid filters.
    $view->display_handler->setOption('filters', [
      'type' => [
        'id' => 'type',
        'table' => 'node_field_data',
        'field' => 'type',
        'value' => [$this->contentBundle => $this->contentBundle],
        'entity_type' => 'node',
        'entity_field' => 'type',
        'plugin_id' => 'bundle',
      ],
      'nid' => [
        'id' => 'nid',
        'table' => 'node_field_data',
        'field' => 'nid',
        'value' => ['value' => (string) $content_node->id()],
        'plugin_id' => 'numeric',
        'operator' => '=',
      ],
    ]);

    $this->executeView($view);

    // Expect a single row because both audience fields reference the same
    // group, and the UNION mapping removes duplicates.
    $this->assertCount(1, $view->result, 'Content appears once when both audience fields reference the same group.');

    $nids = array_map(fn($row) => (int) $row->_entity->id(), $view->result);
    $this->assertEquals([(int) $content_node->id()], $nids, 'The content nid appears once.');
  }

  /**
   * Verifies LEFT vs INNER join behavior via the relationship's required flag.
   */
  public function testRelationshipRequiredVsOptional(): void {
    // Create a second content bundle with no audience fields.
    $lonely_bundle = mb_strtolower($this->randomMachineName());
    NodeType::create([
      'type' => $lonely_bundle,
      'name' => $this->randomString(),
    ])->save();

    // Create one node in that bundle (no audience references).
    $lonely = Node::create([
      'type' => $lonely_bundle,
      'title' => $this->randomString(),
    ]);
    $lonely->save();

    // Use the group content → group test view.
    $view = Views::getView('test_og_group_content_to_group');
    $view->setDisplay('default');

    // Filter base rows to only our two content bundles, excluding group bundle.
    $view->display_handler->setOption('filters', [
      'type' => [
        'id' => 'type',
        'table' => 'node_field_data',
        'field' => 'type',
        'value' => [
          $this->contentBundle => $this->contentBundle,
          $lonely_bundle => $lonely_bundle,
        ],
        'entity_type' => 'node',
        'entity_field' => 'type',
        'plugin_id' => 'bundle',
      ],
    ]);

    // Toggle relationship to optional (LEFT join).
    $relationships = $view->display_handler->getOption('relationships') ?: [];
    $relationships['og_group_content_to_group__node']['required'] = FALSE;
    $view->display_handler->setOption('relationships', $relationships);

    // Enable DISTINCT for this test to ensure unique base rows when using
    // LEFT join.
    $query_option = $view->display_handler->getOption('query') ?: ['type' => 'views_query', 'options' => []];
    $query_option['options']['distinct'] = TRUE;
    $view->display_handler->setOption('query', $query_option);

    $this->executeView($view);
    // Verify both base entities are present regardless of duplicate expansion
    // from the relationship.
    $nids = array_map(fn($row) => (int) $row->_entity->id(), $view->result);
    $unique_nids = array_values(array_unique($nids));
    sort($unique_nids);
    $expected_left = [
      (int) $this->contentNode->id(),
      (int) $lonely->id(),
    ];
    sort($expected_left);
    $this->assertEquals($expected_left, $unique_nids, 'LEFT join returns both content nodes (unique by nid).');

    // Now toggle relationship to required (INNER join).
    $relationships['og_group_content_to_group__node']['required'] = TRUE;
    $view->display_handler->setOption('relationships', $relationships);

    // Ensure the relationship join is retained by referencing a related-side
    // field. Add a hidden field from the related table (group side) using the
    // relationship. Rebuild the view so the updated relationship 'required'
    // flag is applied.
    $view->destroy();
    // Use a fresh view instance to avoid any cached joins/handlers.
    $view = Views::getView('test_og_group_content_to_group');
    $view->setDisplay('default');

    // Re-apply filters for the two content bundles.
    $view->display_handler->setOption('filters', [
      'type' => [
        'id' => 'type',
        'table' => 'node_field_data',
        'field' => 'type',
        'value' => [
          $this->contentBundle => $this->contentBundle,
          $lonely_bundle => $lonely_bundle,
        ],
        'entity_type' => 'node',
        'entity_field' => 'type',
        'plugin_id' => 'bundle',
      ],
    ]);

    // Ensure relationship remains required after rebuild.
    $relationships = $view->display_handler->getOption('relationships') ?: [];
    $relationships['og_group_content_to_group__node']['required'] = TRUE;
    $view->display_handler->setOption('relationships', $relationships);

    // Re-add a hidden field from the related table (group side) using the
    // relationship to ensure Views keeps the relationship join active in the
    // query build.
    $fields = $view->display_handler->getOption('fields') ?: [];
    $fields['related_group_nid'] = [
      'id' => 'nid',
      'table' => 'node_field_data',
      'field' => 'nid',
      'relationship' => 'og_group_content_to_group__node',
      'plugin_id' => 'field',
      'entity_type' => 'node',
      'entity_field' => 'nid',
      'exclude' => TRUE,
      'label' => '',
    ];
    $view->display_handler->setOption('fields', $fields);

    $this->executeView($view);
    // Expect only the content that has an audience reference
    // (lonely node absent).
    $nids_inner = array_map(fn($row) => (int) $row->_entity->id(), $view->result);
    $unique_inner = array_values(array_unique($nids_inner));
    sort($unique_inner);
    $this->assertNotContains((int) $lonely->id(), $unique_inner, 'INNER join hides rows without relationships.');
    $this->assertEquals([(int) $this->contentNode->id()], $unique_inner, 'INNER join returns only related content (unique by nid).');
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc