3 namespace Drupal\Tests\field_ui\Kernel;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\Core\Database\Database;
9 use Drupal\Core\Entity\Display\EntityDisplayInterface;
10 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
11 use Drupal\Core\Entity\Entity\EntityFormDisplay;
12 use Drupal\Core\Entity\Entity\EntityViewDisplay;
13 use Drupal\Core\Entity\Entity\EntityViewMode;
14 use Drupal\field\Entity\FieldConfig;
15 use Drupal\field\Entity\FieldStorageConfig;
16 use Drupal\node\Entity\NodeType;
17 use Drupal\KernelTests\KernelTestBase;
18 use Drupal\user\Entity\Role;
21 * Tests the entity display configuration entities.
25 class EntityDisplayTest extends KernelTestBase {
32 public static $modules = ['field_ui', 'field', 'entity_test', 'user', 'text', 'field_test', 'node', 'system'];
34 protected function setUp() {
36 $this->installEntitySchema('node');
37 $this->installEntitySchema('user');
38 $this->installConfig(['field', 'node', 'user']);
42 * Tests basic CRUD operations on entity display objects.
44 public function testEntityDisplayCRUD() {
45 $display = EntityViewDisplay::create([
46 'targetEntityType' => 'entity_test',
47 'bundle' => 'entity_test',
53 // Check that providing no 'weight' results in the highest current weight
54 // being assigned. The 'name' field's formatter has weight -5, therefore
56 $expected['component_1'] = ['weight' => -4, 'settings' => [], 'third_party_settings' => []];
57 $expected['component_2'] = ['weight' => -3, 'settings' => [], 'third_party_settings' => []];
58 $display->setComponent('component_1');
59 $display->setComponent('component_2');
60 $this->assertEqual($display->getComponent('component_1'), $expected['component_1']);
61 $this->assertEqual($display->getComponent('component_2'), $expected['component_2']);
63 // Check that arbitrary options are correctly stored.
64 $expected['component_3'] = ['weight' => 10, 'third_party_settings' => ['field_test' => ['foo' => 'bar']], 'settings' => []];
65 $display->setComponent('component_3', $expected['component_3']);
66 $this->assertEqual($display->getComponent('component_3'), $expected['component_3']);
68 // Check that the display can be properly saved and read back.
70 $display = EntityViewDisplay::load($display->id());
71 foreach (['component_1', 'component_2', 'component_3'] as $name) {
72 $expected[$name]['region'] = 'content';
73 $this->assertEqual($display->getComponent($name), $expected[$name]);
76 // Ensure that third party settings were added to the config entity.
77 // These are added by entity_test_entity_presave() implemented in
78 // entity_test module.
79 $this->assertEqual('bar', $display->getThirdPartySetting('entity_test', 'foo'), 'Third party settings were added to the entity view display.');
81 // Check that getComponents() returns options for all components.
87 'link_to_entity' => FALSE,
89 'third_party_settings' => [],
92 $this->assertEqual($display->getComponents(), $expected);
94 // Check that a component can be removed.
95 $display->removeComponent('component_3');
96 $this->assertNULL($display->getComponent('component_3'));
98 // Check that the removal is correctly persisted.
100 $display = EntityViewDisplay::load($display->id());
101 $this->assertNULL($display->getComponent('component_3'));
103 // Check that createCopy() creates a new component that can be correctly
105 EntityViewMode::create(['id' => $display->getTargetEntityTypeId() . '.other_view_mode', 'targetEntityType' => $display->getTargetEntityTypeId()])->save();
106 $new_display = $display->createCopy('other_view_mode');
107 $new_display->save();
108 $new_display = EntityViewDisplay::load($new_display->id());
109 $dependencies = $new_display->calculateDependencies()->getDependencies();
110 $this->assertEqual(['config' => ['core.entity_view_mode.entity_test.other_view_mode'], 'module' => ['entity_test']], $dependencies);
111 $this->assertEqual($new_display->getTargetEntityTypeId(), $display->getTargetEntityTypeId());
112 $this->assertEqual($new_display->getTargetBundle(), $display->getTargetBundle());
113 $this->assertEqual($new_display->getMode(), 'other_view_mode');
114 $this->assertEqual($new_display->getComponents(), $display->getComponents());
118 * Test sorting of components by name on basic CRUD operations
120 public function testEntityDisplayCRUDSort() {
121 $display = EntityViewDisplay::create([
122 'targetEntityType' => 'entity_test',
123 'bundle' => 'entity_test',
126 $display->setComponent('component_3');
127 $display->setComponent('component_1');
128 $display->setComponent('component_2');
130 $components = array_keys($display->getComponents());
131 // The name field is not configurable so will be added automatically.
132 $expected = [0 => 'component_1', 1 => 'component_2', 2 => 'component_3', 'name'];
133 $this->assertIdentical($components, $expected);
137 * Tests entity_get_display().
139 public function testEntityGetDisplay() {
140 // Check that entity_get_display() returns a fresh object when no
141 // configuration entry exists.
142 $display = entity_get_display('entity_test', 'entity_test', 'default');
143 $this->assertTrue($display->isNew());
145 // Add some components and save the display.
146 $display->setComponent('component_1', ['weight' => 10, 'settings' => []])
149 // Check that entity_get_display() returns the correct object.
150 $display = entity_get_display('entity_test', 'entity_test', 'default');
151 $this->assertFalse($display->isNew());
152 $this->assertEqual($display->id(), 'entity_test.entity_test.default');
153 $this->assertEqual($display->getComponent('component_1'), ['weight' => 10, 'settings' => [], 'third_party_settings' => [], 'region' => 'content']);
157 * Tests the behavior of a field component within an entity display object.
159 public function testExtraFieldComponent() {
160 entity_test_create_bundle('bundle_with_extra_fields');
161 $display = EntityViewDisplay::create([
162 'targetEntityType' => 'entity_test',
163 'bundle' => 'bundle_with_extra_fields',
167 // Check that the default visibility taken into account for extra fields
168 // unknown in the display.
169 $this->assertEqual($display->getComponent('display_extra_field'), ['weight' => 5, 'region' => 'content']);
170 $this->assertNull($display->getComponent('display_extra_field_hidden'));
172 // Check that setting explicit options overrides the defaults.
173 $display->removeComponent('display_extra_field');
174 $display->setComponent('display_extra_field_hidden', ['weight' => 10]);
175 $this->assertNull($display->getComponent('display_extra_field'));
176 $this->assertEqual($display->getComponent('display_extra_field_hidden'), ['weight' => 10, 'settings' => [], 'third_party_settings' => []]);
180 * Tests the behavior of an extra field component with initial invalid values.
182 public function testExtraFieldComponentInitialInvalidConfig() {
183 entity_test_create_bundle('bundle_with_extra_fields');
184 $display = EntityViewDisplay::create([
185 'targetEntityType' => 'entity_test',
186 'bundle' => 'bundle_with_extra_fields',
188 // Add the extra field to the initial config, without a 'type'.
190 'display_extra_field' => [
196 // Check that the default visibility taken into account for extra fields
197 // unknown in the display that were included in the initial config.
198 $this->assertEqual($display->getComponent('display_extra_field'), ['weight' => 5, 'region' => 'content']);
199 $this->assertNull($display->getComponent('display_extra_field_hidden'));
201 // Check that setting explicit options overrides the defaults.
202 $display->removeComponent('display_extra_field');
203 $display->setComponent('display_extra_field_hidden', ['weight' => 10]);
204 $this->assertNull($display->getComponent('display_extra_field'));
205 $this->assertEqual($display->getComponent('display_extra_field_hidden'), ['weight' => 10, 'settings' => [], 'third_party_settings' => []]);
209 * Tests the behavior of a field component within an entity display object.
211 public function testFieldComponent() {
212 $field_name = 'test_field';
213 // Create a field storage and a field.
214 $field_storage = FieldStorageConfig::create([
215 'field_name' => $field_name,
216 'entity_type' => 'entity_test',
217 'type' => 'test_field'
219 $field_storage->save();
220 $field = FieldConfig::create([
221 'field_storage' => $field_storage,
222 'bundle' => 'entity_test',
226 $display = EntityViewDisplay::create([
227 'targetEntityType' => 'entity_test',
228 'bundle' => 'entity_test',
232 // Check that providing no options results in default values being used.
233 $display->setComponent($field_name);
234 $field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType());
235 $default_formatter = $field_type_info['default_formatter'];
236 $formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings($default_formatter);
240 'type' => $default_formatter,
241 'settings' => $formatter_settings,
242 'third_party_settings' => [],
244 $this->assertEqual($display->getComponent($field_name), $expected);
246 // Check that the getFormatter() method returns the correct formatter plugin.
247 $formatter = $display->getRenderer($field_name);
248 $this->assertEqual($formatter->getPluginId(), $default_formatter);
249 $this->assertEqual($formatter->getSettings(), $formatter_settings);
251 // Check that the formatter is statically persisted, by assigning an
252 // arbitrary property and reading it back.
253 $random_value = $this->randomString();
254 $formatter->randomValue = $random_value;
255 $formatter = $display->getRenderer($field_name);
256 $this->assertEqual($formatter->randomValue, $random_value);
258 // Check that changing the definition creates a new formatter.
259 $display->setComponent($field_name, [
260 'type' => 'field_test_multiple',
262 $formatter = $display->getRenderer($field_name);
263 $this->assertEqual($formatter->getPluginId(), 'field_test_multiple');
264 $this->assertFalse(isset($formatter->randomValue));
266 // Check that the display has dependencies on the field and the module that
267 // provides the formatter.
268 $dependencies = $display->calculateDependencies()->getDependencies();
269 $this->assertEqual(['config' => ['field.field.entity_test.entity_test.test_field'], 'module' => ['entity_test', 'field_test']], $dependencies);
273 * Tests the behavior of a field component for a base field.
275 public function testBaseFieldComponent() {
276 $display = EntityViewDisplay::create([
277 'targetEntityType' => 'entity_test_base_field_display',
278 'bundle' => 'entity_test_base_field_display',
282 // Check that default options are correctly filled in.
283 $formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('text_default');
285 'test_no_display' => NULL,
286 'test_display_configurable' => [
288 'type' => 'text_default',
289 'settings' => $formatter_settings,
290 'third_party_settings' => [],
292 'region' => 'content',
294 'test_display_non_configurable' => [
296 'type' => 'text_default',
297 'settings' => $formatter_settings,
298 'third_party_settings' => [],
300 'region' => 'content',
303 foreach ($expected as $field_name => $options) {
304 $this->assertEqual($display->getComponent($field_name), $options);
307 // Check that saving the display only writes data for fields whose display
310 $config = $this->config('core.entity_view_display.' . $display->id());
311 $data = $config->get();
312 $this->assertFalse(isset($data['content']['test_no_display']));
313 $this->assertFalse(isset($data['hidden']['test_no_display']));
314 $this->assertEqual($data['content']['test_display_configurable'], $expected['test_display_configurable']);
315 $this->assertFalse(isset($data['content']['test_display_non_configurable']));
316 $this->assertFalse(isset($data['hidden']['test_display_non_configurable']));
318 // Check that defaults are correctly filled when loading the display.
319 $display = EntityViewDisplay::load($display->id());
320 foreach ($expected as $field_name => $options) {
321 $this->assertEqual($display->getComponent($field_name), $options);
324 // Check that data manually written for fields whose display is not
325 // configurable is discarded when loading the display.
326 $data['content']['test_display_non_configurable'] = $expected['test_display_non_configurable'];
327 $data['content']['test_display_non_configurable']['weight']++;
328 $config->setData($data)->save();
329 $display = EntityViewDisplay::load($display->id());
330 foreach ($expected as $field_name => $options) {
331 $this->assertEqual($display->getComponent($field_name), $options);
336 * Tests deleting a bundle.
338 public function testDeleteBundle() {
339 // Create a node bundle, display and form display object.
340 $type = NodeType::create(['type' => 'article']);
342 node_add_body_field($type);
343 entity_get_display('node', 'article', 'default')->save();
344 entity_get_form_display('node', 'article', 'default')->save();
346 // Delete the bundle.
348 $display = EntityViewDisplay::load('node.article.default');
349 $this->assertFalse((bool) $display);
350 $form_display = EntityFormDisplay::load('node.article.default');
351 $this->assertFalse((bool) $form_display);
355 * Tests deleting field.
357 public function testDeleteField() {
358 $field_name = 'test_field';
359 // Create a field storage and a field.
360 $field_storage = FieldStorageConfig::create([
361 'field_name' => $field_name,
362 'entity_type' => 'entity_test',
363 'type' => 'test_field'
365 $field_storage->save();
366 $field = FieldConfig::create([
367 'field_storage' => $field_storage,
368 'bundle' => 'entity_test',
372 // Create default and teaser entity display.
373 EntityViewMode::create(['id' => 'entity_test.teaser', 'targetEntityType' => 'entity_test'])->save();
374 EntityViewDisplay::create([
375 'targetEntityType' => 'entity_test',
376 'bundle' => 'entity_test',
378 ])->setComponent($field_name)->save();
379 EntityViewDisplay::create([
380 'targetEntityType' => 'entity_test',
381 'bundle' => 'entity_test',
383 ])->setComponent($field_name)->save();
385 // Check the component exists.
386 $display = entity_get_display('entity_test', 'entity_test', 'default');
387 $this->assertTrue($display->getComponent($field_name));
388 $display = entity_get_display('entity_test', 'entity_test', 'teaser');
389 $this->assertTrue($display->getComponent($field_name));
394 // Check that the component has been removed from the entity displays.
395 $display = entity_get_display('entity_test', 'entity_test', 'default');
396 $this->assertFalse($display->getComponent($field_name));
397 $display = entity_get_display('entity_test', 'entity_test', 'teaser');
398 $this->assertFalse($display->getComponent($field_name));
402 * Tests \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval().
404 public function testOnDependencyRemoval() {
405 $this->enableModules(['field_plugins_test']);
407 $field_name = 'test_field';
409 $field_storage = FieldStorageConfig::create([
410 'field_name' => $field_name,
411 'entity_type' => 'entity_test',
414 $field_storage->save();
415 $field = FieldConfig::create([
416 'field_storage' => $field_storage,
417 'bundle' => 'entity_test',
421 EntityViewDisplay::create([
422 'targetEntityType' => 'entity_test',
423 'bundle' => 'entity_test',
425 ])->setComponent($field_name, ['type' => 'field_plugins_test_text_formatter'])->save();
427 // Check the component exists and is of the correct type.
428 $display = entity_get_display('entity_test', 'entity_test', 'default');
429 $this->assertEqual($display->getComponent($field_name)['type'], 'field_plugins_test_text_formatter');
431 // Removing the field_plugins_test module should change the component to use
432 // the default formatter for test fields.
433 \Drupal::service('config.manager')->uninstall('module', 'field_plugins_test');
434 $display = entity_get_display('entity_test', 'entity_test', 'default');
435 $this->assertEqual($display->getComponent($field_name)['type'], 'text_default');
437 // Removing the text module should remove the field from the view display.
438 \Drupal::service('config.manager')->uninstall('module', 'text');
439 $display = entity_get_display('entity_test', 'entity_test', 'default');
440 $this->assertFalse($display->getComponent($field_name));
444 * Ensure that entity view display changes invalidates cache tags.
446 public function testEntityDisplayInvalidateCacheTags() {
447 $cache = \Drupal::cache();
448 $cache->set('cid', 'kittens', Cache::PERMANENT, ['config:entity_view_display_list']);
449 $display = EntityViewDisplay::create([
450 'targetEntityType' => 'entity_test',
451 'bundle' => 'entity_test',
454 $display->setComponent('kitten');
456 $this->assertFalse($cache->get('cid'));
460 * Test getDisplayModeOptions().
462 public function testGetDisplayModeOptions() {
463 NodeType::create(['type' => 'article'])->save();
465 EntityViewDisplay::create([
466 'targetEntityType' => 'node',
467 'bundle' => 'article',
469 ])->setStatus(TRUE)->save();
471 $display_teaser = EntityViewDisplay::create([
472 'targetEntityType' => 'node',
473 'bundle' => 'article',
476 $display_teaser->save();
478 EntityFormDisplay::create([
479 'targetEntityType' => 'user',
482 ])->setStatus(TRUE)->save();
484 $form_display_teaser = EntityFormDisplay::create([
485 'targetEntityType' => 'user',
487 'mode' => 'register',
489 $form_display_teaser->save();
491 // Test getViewModeOptionsByBundle().
492 $view_modes = \Drupal::entityManager()->getViewModeOptionsByBundle('node', 'article');
493 $this->assertEqual($view_modes, ['default' => 'Default']);
494 $display_teaser->setStatus(TRUE)->save();
495 $view_modes = \Drupal::entityManager()->getViewModeOptionsByBundle('node', 'article');
496 $this->assertEqual($view_modes, ['default' => 'Default', 'teaser' => 'Teaser']);
498 // Test getFormModeOptionsByBundle().
499 $form_modes = \Drupal::entityManager()->getFormModeOptionsByBundle('user', 'user');
500 $this->assertEqual($form_modes, ['default' => 'Default']);
501 $form_display_teaser->setStatus(TRUE)->save();
502 $form_modes = \Drupal::entityManager()->getFormModeOptionsByBundle('user', 'user');
503 $this->assertEqual($form_modes, ['default' => 'Default', 'register' => 'Register']);
507 * Tests components dependencies additions.
509 public function testComponentDependencies() {
510 $this->enableModules(['dblog', 'color']);
511 $this->installSchema('dblog', ['watchdog']);
512 $this->installEntitySchema('user');
513 /** @var \Drupal\user\RoleInterface[] $roles */
515 // Create two arbitrary user roles.
516 for ($i = 0; $i < 2; $i++) {
517 $roles[$i] = Role::create([
518 'id' => Unicode::strtolower($this->randomMachineName()),
519 'label' => $this->randomString(),
524 // Create a field of type 'test_field' attached to 'entity_test'.
525 $field_name = Unicode::strtolower($this->randomMachineName());
526 FieldStorageConfig::create([
527 'field_name' => $field_name,
528 'entity_type' => 'entity_test',
529 'type' => 'test_field',
531 FieldConfig::create([
532 'field_name' => $field_name,
533 'entity_type' => 'entity_test',
534 'bundle' => 'entity_test',
537 // Create a new form display without components.
538 /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
539 $form_display = EntityFormDisplay::create([
540 'targetEntityType' => 'entity_test',
541 'bundle' => 'entity_test',
544 $form_display->save();
546 $dependencies = ['user.role.' . $roles[0]->id(), 'user.role.' . $roles[1]->id()];
548 // The config object should not depend on none of the two $roles.
549 $this->assertNoDependency('config', $dependencies[0], $form_display);
550 $this->assertNoDependency('config', $dependencies[1], $form_display);
552 // Add a widget of type 'test_field_widget'.
554 'type' => 'test_field_widget',
556 'test_widget_setting' => $this->randomString(),
557 'role' => $roles[0]->id(),
558 'role2' => $roles[1]->id(),
560 'third_party_settings' => [
561 'color' => ['foo' => 'bar'],
564 $form_display->setComponent($field_name, $component);
565 $form_display->save();
567 // Now, the form display should depend on both user roles $roles.
568 $this->assertDependency('config', $dependencies[0], $form_display);
569 $this->assertDependency('config', $dependencies[1], $form_display);
570 // The form display should depend on 'color' module.
571 $this->assertDependency('module', 'color', $form_display);
573 // Delete the first user role entity.
576 // Reload the form display.
577 $form_display = EntityFormDisplay::load($form_display->id());
578 // The display exists.
579 $this->assertFalse(empty($form_display));
580 // The form display should not depend on $role[0] anymore.
581 $this->assertNoDependency('config', $dependencies[0], $form_display);
582 // The form display should depend on 'anonymous' user role.
583 $this->assertDependency('config', 'user.role.anonymous', $form_display);
584 // The form display should depend on 'color' module.
585 $this->assertDependency('module', 'color', $form_display);
587 // Manually trigger the removal of configuration belonging to the module
588 // because KernelTestBase::disableModules() is not aware of this.
589 $this->container->get('config.manager')->uninstall('module', 'color');
590 // Uninstall 'color' module.
591 $this->disableModules(['color']);
593 // Reload the form display.
594 $form_display = EntityFormDisplay::load($form_display->id());
595 // The display exists.
596 $this->assertFalse(empty($form_display));
597 // The component is still enabled.
598 $this->assertNotNull($form_display->getComponent($field_name));
599 // The form display should not depend on 'color' module anymore.
600 $this->assertNoDependency('module', 'color', $form_display);
602 // Delete the 2nd user role entity.
605 // Reload the form display.
606 $form_display = EntityFormDisplay::load($form_display->id());
607 // The display exists.
608 $this->assertFalse(empty($form_display));
609 // The component has been disabled.
610 $this->assertNull($form_display->getComponent($field_name));
611 $this->assertTrue($form_display->get('hidden')[$field_name]);
612 // The correct warning message has been logged.
613 $arguments = ['@display' => (string) t('Entity form display'), '@id' => $form_display->id(), '@name' => $field_name];
614 $logged = (bool) Database::getConnection()->select('watchdog', 'w')
615 ->fields('w', ['wid'])
616 ->condition('type', 'system')
617 ->condition('message', "@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.")
618 ->condition('variables', serialize($arguments))
621 $this->assertTrue($logged);
625 * Asserts that $key is a $type type dependency of $display config entity.
627 * @param string $type
628 * The dependency type: 'config', 'content', 'module' or 'theme'.
630 * The string to be checked.
631 * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
632 * The entity display object to get dependencies from.
635 * TRUE if the assertion succeeded, FALSE otherwise.
637 protected function assertDependency($type, $key, EntityDisplayInterface $display) {
638 return $this->assertDependencyHelper(TRUE, $type, $key, $display);
642 * Asserts that $key is not a $type type dependency of $display config entity.
644 * @param string $type
645 * The dependency type: 'config', 'content', 'module' or 'theme'.
647 * The string to be checked.
648 * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
649 * The entity display object to get dependencies from.
652 * TRUE if the assertion succeeded, FALSE otherwise.
654 protected function assertNoDependency($type, $key, EntityDisplayInterface $display) {
655 return $this->assertDependencyHelper(FALSE, $type, $key, $display);
659 * Provides a helper for dependency assertions.
661 * @param bool $assertion
662 * Assertion: positive or negative.
663 * @param string $type
664 * The dependency type: 'config', 'content', 'module' or 'theme'.
666 * The string to be checked.
667 * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
668 * The entity display object to get dependencies from.
671 * TRUE if the assertion succeeded, FALSE otherwise.
673 protected function assertDependencyHelper($assertion, $type, $key, EntityDisplayInterface $display) {
674 $all_dependencies = $display->getDependencies();
675 $dependencies = !empty($all_dependencies[$type]) ? $all_dependencies[$type] : [];
676 $context = $display instanceof EntityViewDisplayInterface ? 'View' : 'Form';
677 $value = $assertion ? in_array($key, $dependencies) : !in_array($key, $dependencies);
678 $args = ['@context' => $context, '@id' => $display->id(), '@type' => $type, '@key' => $key];
679 $message = $assertion ? new FormattableMarkup("@context display '@id' depends on @type '@key'.", $args) : new FormattableMarkup("@context display '@id' do not depend on @type '@key'.", $args);
680 return $this->assert($value, $message);