diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..29d3d17dba0fb9456718173fc12621eb32000f8e --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/EntityViewBuilderTest.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\Entity; + +use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\Core\Entity\EntityViewBuilder; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Entity\EntityViewBuilder + * @group Entity + */ +class EntityViewBuilderTest extends UnitTestCase { + + const string ENTITY_TYPE_ID = 'test_entity_type'; + + /** + * The entity view builder under test. + * + * @var \Drupal\Core\Entity\EntityViewBuilder + */ + protected EntityViewBuilder $viewBuilder; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->viewBuilder = new class() extends EntityViewBuilder { + + public function __construct() { + $this->entityTypeId = EntityViewBuilderTest::ENTITY_TYPE_ID; + } + + }; + } + + /** + * Tests build components using a mocked Iterator. + */ + public function testBuildComponents(): void { + $field_name = $this->randomMachineName(); + $bundle = $this->randomMachineName(); + $entity_id = mt_rand(20, 30); + $field_item_list = $this->createStub(FieldItemListInterface::class); + $item = new \stdClass(); + $this->setupMockIterator($field_item_list, [$item]); + $entity = $this->createConfiguredStub(FieldableEntityInterface::class, [ + 'bundle' => $bundle, + 'hasField' => TRUE, + 'get' => $field_item_list, + ]); + $formatter_result = [ + $entity_id => ['#' . $this->randomMachineName() => $this->randomString()], + ]; + $display = $this->createConfiguredStub(EntityViewDisplayInterface::class, [ + 'getComponents' => [$field_name => []], + 'buildMultiple' => $formatter_result, + ]); + $entities = [$entity_id => $entity]; + $displays = [$bundle => $display]; + $build = [$entity_id => []]; + $view_mode = $this->randomMachineName(); + // Assert the hook is invoked. + $module_handler = $this->createMock(ModuleHandlerInterface::class); + $module_handler->expects($this->once()) + ->method('invokeAll') + ->with('entity_prepare_view', [self::ENTITY_TYPE_ID, $entities, $displays, $view_mode]); + $this->viewBuilder->setModuleHandler($module_handler); + $this->viewBuilder->buildComponents($build, $entities, $displays, $view_mode); + $this->assertSame([], $item->_attributes); + $this->assertSame($formatter_result, $build); + } + +} diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index 2257148d0698c6bf88b9b75e5f601ad2ce790f76..b279bd1ed250395f037a36d001f6e2d05a2ed49b 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -13,6 +13,7 @@ use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait; use Drupal\TestTools\Extension\Dump\DebugDump; use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\VarDumper\VarDumper; @@ -210,4 +211,32 @@ protected function getClassResolverStub() { return $class_resolver; } + /** + * Set up a traversable class mock to return specific items when iterated. + * + * Test doubles for types extending \Traversable are required to implement + * \Iterator which requires setting up five methods. Instead, this helper + * can be used. + * + * @param \PHPUnit\Framework\MockObject\MockObject&\Iterator $mock + * A mock object mocking a traversable class. + * @param array $items + * The items to return when this mock is iterated. + * + * @return \PHPUnit\Framework\MockObject\MockObject&\Iterator + * The same mock object ready to be iterated. + * + * @template T of \PHPUnit\Framework\MockObject\MockObject&\Iterator + * @phpstan-param T $mock + * @phpstan-return T + * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + */ + protected function setupMockIterator(MockObject&\Iterator $mock, array $items): MockObject&\Iterator { + $iterator = new \ArrayIterator($items); + foreach (get_class_methods(\Iterator::class) as $method) { + $mock->method($method)->willReturnCallback([$iterator, $method]); + } + return $mock; + } + }