1<?php 2 3namespace Drupal\Tests\Core\Field; 4 5use Drupal\Component\Plugin\Exception\PluginNotFoundException; 6use Drupal\Core\Cache\CacheBackendInterface; 7use Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface; 8use Drupal\Core\Entity\EntityFieldManagerInterface; 9use Drupal\Core\Entity\EntityInterface; 10use Drupal\Core\Entity\EntityTypeInterface; 11use Drupal\Core\Entity\EntityTypeManagerInterface; 12use Drupal\Core\Field\FieldDefinitionInterface; 13use Drupal\Core\Field\FieldDefinitionListener; 14use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; 15use Drupal\Core\KeyValueStore\KeyValueStoreInterface; 16use Drupal\Tests\UnitTestCase; 17use Prophecy\Argument; 18 19/** 20 * @coversDefaultClass \Drupal\Core\Field\FieldDefinitionListener 21 * @group Field 22 */ 23class FieldDefinitionListenerTest extends UnitTestCase { 24 25 /** 26 * The key-value factory. 27 * 28 * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\Prophecy\Prophecy\ProphecyInterface 29 */ 30 protected $keyValueFactory; 31 32 /** 33 * The entity type manager. 34 * 35 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface 36 */ 37 protected $entityTypeManager; 38 39 /** 40 * The entity field manager. 41 * 42 * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\Prophecy\Prophecy\ProphecyInterface 43 */ 44 protected $entityFieldManager; 45 46 /** 47 * The cache backend. 48 * 49 * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface 50 */ 51 protected $cacheBackend; 52 53 /** 54 * The field definition listener under test. 55 * 56 * @var \Drupal\Core\Field\FieldDefinitionListener 57 */ 58 protected $fieldDefinitionListener; 59 60 /** 61 * {@inheritdoc} 62 */ 63 protected function setUp(): void { 64 parent::setUp(); 65 66 $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class); 67 $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class); 68 $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); 69 $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); 70 71 $this->fieldDefinitionListener = new FieldDefinitionListener($this->entityTypeManager->reveal(), $this->entityFieldManager->reveal(), $this->keyValueFactory->reveal(), $this->cacheBackend->reveal()); 72 } 73 74 /** 75 * Sets up the entity type manager to be tested. 76 * 77 * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions 78 * (optional) An array of entity type definitions. 79 */ 80 protected function setUpEntityTypeManager($definitions = []) { 81 $class = $this->getMockClass(EntityInterface::class); 82 foreach ($definitions as $key => $entity_type) { 83 // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called 84 // by \Drupal\Core\Entity\EntityTypeManager::processDefinition() so it must 85 // always be mocked. 86 $entity_type->getLinkTemplates()->willReturn([]); 87 88 // Give the entity type a legitimate class to return. 89 $entity_type->getClass()->willReturn($class); 90 91 $definitions[$key] = $entity_type->reveal(); 92 } 93 94 $this->entityTypeManager->getDefinition(Argument::cetera()) 95 ->will(function ($args) use ($definitions) { 96 $entity_type_id = $args[0]; 97 $exception_on_invalid = $args[1]; 98 if (isset($definitions[$entity_type_id])) { 99 return $definitions[$entity_type_id]; 100 } 101 elseif (!$exception_on_invalid) { 102 return NULL; 103 } 104 else { 105 throw new PluginNotFoundException($entity_type_id); 106 } 107 }); 108 $this->entityTypeManager->getDefinitions()->willReturn($definitions); 109 } 110 111 /** 112 * @covers ::onFieldDefinitionCreate 113 */ 114 public function testOnFieldDefinitionCreateNewField() { 115 $field_definition = $this->prophesize(FieldDefinitionInterface::class); 116 $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type'); 117 $field_definition->getTargetBundle()->willReturn('test_bundle'); 118 $field_definition->getName()->willReturn('test_field'); 119 $field_definition->getType()->willReturn('test_type'); 120 121 $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class); 122 $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1); 123 $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal()); 124 125 $entity = $this->prophesize(EntityTypeInterface::class); 126 $this->setUpEntityTypeManager(['test_entity_type' => $entity]); 127 128 // Set up the stored bundle field map. 129 $key_value_store = $this->prophesize(KeyValueStoreInterface::class); 130 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal()); 131 $key_value_store->get('test_entity_type')->willReturn([]); 132 $key_value_store->set('test_entity_type', [ 133 'test_field' => [ 134 'type' => 'test_type', 135 'bundles' => ['test_bundle' => 'test_bundle'], 136 ], 137 ])->shouldBeCalled(); 138 139 $this->fieldDefinitionListener->onFieldDefinitionCreate($field_definition->reveal()); 140 } 141 142 /** 143 * @covers ::onFieldDefinitionCreate 144 */ 145 public function testOnFieldDefinitionCreateExistingField() { 146 $field_definition = $this->prophesize(FieldDefinitionInterface::class); 147 $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type'); 148 $field_definition->getTargetBundle()->willReturn('test_bundle'); 149 $field_definition->getName()->willReturn('test_field'); 150 151 $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class); 152 $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1); 153 $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal()); 154 155 $entity = $this->prophesize(EntityTypeInterface::class); 156 $this->setUpEntityTypeManager(['test_entity_type' => $entity]); 157 158 // Set up the stored bundle field map. 159 $key_value_store = $this->prophesize(KeyValueStoreInterface::class); 160 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal()); 161 $key_value_store->get('test_entity_type')->willReturn([ 162 'test_field' => [ 163 'type' => 'test_type', 164 'bundles' => ['existing_bundle' => 'existing_bundle'], 165 ], 166 ]); 167 $key_value_store->set('test_entity_type', [ 168 'test_field' => [ 169 'type' => 'test_type', 170 'bundles' => ['existing_bundle' => 'existing_bundle', 'test_bundle' => 'test_bundle'], 171 ], 172 ]) 173 ->shouldBeCalled(); 174 175 $this->fieldDefinitionListener->onFieldDefinitionCreate($field_definition->reveal()); 176 } 177 178 /** 179 * @covers ::onFieldDefinitionUpdate 180 */ 181 public function testOnFieldDefinitionUpdate() { 182 $field_definition = $this->prophesize(FieldDefinitionInterface::class); 183 $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type'); 184 185 $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class); 186 $storage->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal())->shouldBeCalledTimes(1); 187 $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal()); 188 189 $entity = $this->prophesize(EntityTypeInterface::class); 190 $this->setUpEntityTypeManager(['test_entity_type' => $entity]); 191 192 $this->fieldDefinitionListener->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal()); 193 } 194 195 /** 196 * @covers ::onFieldDefinitionDelete 197 */ 198 public function testOnFieldDefinitionDeleteMultipleBundles() { 199 $field_definition = $this->prophesize(FieldDefinitionInterface::class); 200 $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type'); 201 $field_definition->getTargetBundle()->willReturn('test_bundle'); 202 $field_definition->getName()->willReturn('test_field'); 203 204 $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class); 205 $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1); 206 $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal()); 207 208 $entity = $this->prophesize(EntityTypeInterface::class); 209 $this->setUpEntityTypeManager(['test_entity_type' => $entity]); 210 211 // Set up the stored bundle field map. 212 $key_value_store = $this->prophesize(KeyValueStoreInterface::class); 213 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal()); 214 $key_value_store->get('test_entity_type')->willReturn([ 215 'test_field' => [ 216 'type' => 'test_type', 217 'bundles' => ['test_bundle' => 'test_bundle'], 218 ], 219 'second_field' => [ 220 'type' => 'test_type', 221 'bundles' => ['test_bundle' => 'test_bundle'], 222 ], 223 ]); 224 $key_value_store->set('test_entity_type', [ 225 'second_field' => [ 226 'type' => 'test_type', 227 'bundles' => ['test_bundle' => 'test_bundle'], 228 ], 229 ]) 230 ->shouldBeCalled(); 231 232 $this->fieldDefinitionListener->onFieldDefinitionDelete($field_definition->reveal()); 233 } 234 235 /** 236 * @covers ::onFieldDefinitionDelete 237 */ 238 public function testOnFieldDefinitionDeleteSingleBundles() { 239 $field_definition = $this->prophesize(FieldDefinitionInterface::class); 240 $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type'); 241 $field_definition->getTargetBundle()->willReturn('test_bundle'); 242 $field_definition->getName()->willReturn('test_field'); 243 244 $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class); 245 $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1); 246 $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal()); 247 248 $entity = $this->prophesize(EntityTypeInterface::class); 249 $this->setUpEntityTypeManager(['test_entity_type' => $entity]); 250 251 // Set up the stored bundle field map. 252 $key_value_store = $this->prophesize(KeyValueStoreInterface::class); 253 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal()); 254 $key_value_store->get('test_entity_type')->willReturn([ 255 'test_field' => [ 256 'type' => 'test_type', 257 'bundles' => ['test_bundle' => 'test_bundle', 'second_bundle' => 'second_bundle'], 258 ], 259 ]); 260 $key_value_store->set('test_entity_type', [ 261 'test_field' => [ 262 'type' => 'test_type', 263 'bundles' => ['second_bundle' => 'second_bundle'], 264 ], 265 ]) 266 ->shouldBeCalled(); 267 268 $this->fieldDefinitionListener->onFieldDefinitionDelete($field_definition->reveal()); 269 } 270 271} 272