Issue #3026434 by tedbow, tim.plunkett, xjm, phenaproxima, plach, Kristen Pol, alexpott: Ensure that Layout Builder Inline Blocks doesn't assume section storage internals
parent
5b3b69f75b
commit
30654bd41d
|
@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\layout_builder\InlineBlockUsage;
|
||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
|
@ -64,11 +65,14 @@ class SetInlineBlockDependency implements EventSubscriberInterface {
|
|||
* The database connection.
|
||||
* @param \Drupal\layout_builder\InlineBlockUsage $usage
|
||||
* The inline block usage service.
|
||||
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
||||
* The section storage manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsage $usage) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsage $usage, SectionStorageManagerInterface $section_storage_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->database = $database;
|
||||
$this->usage = $usage;
|
||||
$this->sectionStorageManager = $section_storage_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\layout_builder\Plugin\Block\InlineBlock;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -43,17 +44,36 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
/**
|
||||
* Constructs a new EntityOperations object.
|
||||
*
|
||||
* @todo This constructor has one optional parameter, $section_storage_manager
|
||||
* and one totally unused $database parameter. Deprecate the current
|
||||
* constructor signature in https://www.drupal.org/node/3031492 after the
|
||||
* general policy for constructor backwards compatibility is determined in
|
||||
* https://www.drupal.org/node/3030640.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\layout_builder\InlineBlockUsage $usage
|
||||
* Inline block usage tracking service.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
||||
* (optional) The section storage manager.
|
||||
*
|
||||
* @todo The current constructor signature is deprecated:
|
||||
* - The $section_storage_manager parameter is optional, but should become
|
||||
* required.
|
||||
* - The $database parameter is unused and should be removed.
|
||||
* Deprecate in https://www.drupal.org/node/3031492.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database) {
|
||||
public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database, SectionStorageManagerInterface $section_storage_manager = NULL) {
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
$this->blockContentStorage = $entityTypeManager->getStorage('block_content');
|
||||
$this->usage = $usage;
|
||||
if ($section_storage_manager === NULL) {
|
||||
@trigger_error('The plugin.manager.layout_builder.section_storage service must be passed to \Drupal\layout_builder\InlineBlockEntityOperations::__construct(). It was added in Drupal 8.7.0 and will be required before Drupal 9.0.0.', E_USER_DEPRECATED);
|
||||
$section_storage_manager = \Drupal::service('plugin.manager.layout_builder.section_storage');
|
||||
}
|
||||
$this->sectionStorageManager = $section_storage_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +83,8 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('inline_block.usage'),
|
||||
$container->get('database')
|
||||
$container->get('database'),
|
||||
$container->get('plugin.manager.layout_builder.section_storage')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -85,10 +106,10 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) {
|
||||
return;
|
||||
}
|
||||
$sections = $this->getEntitySections($entity);
|
||||
// If this is a layout override and there are no sections then it is a new
|
||||
// override.
|
||||
if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) {
|
||||
// If the original entity used the default storage then we cannot remove
|
||||
// unused inline blocks because they will still be referenced in the
|
||||
// defaults.
|
||||
if ($this->originalEntityUsesDefaultStorage($entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -132,9 +153,9 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
* The parent entity.
|
||||
*/
|
||||
public function handleEntityDelete(EntityInterface $entity) {
|
||||
if ($this->isLayoutCompatibleEntity($entity)) {
|
||||
$this->usage->removeByLayoutEntity($entity);
|
||||
}
|
||||
// @todo In https://www.drupal.org/node/3008943 call
|
||||
// \Drupal\layout_builder\LayoutEntityHelperTrait::isLayoutCompatibleEntity().
|
||||
$this->usage->removeByLayoutEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,14 +171,10 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||
$duplicate_blocks = FALSE;
|
||||
|
||||
if ($sections = $this->getEntitySections($entity)) {
|
||||
if ($this->isEntityUsingFieldOverride($entity)) {
|
||||
if (!$entity->isNew() && isset($entity->original)) {
|
||||
if (empty($this->getEntitySections($entity->original))) {
|
||||
// If there were no sections in the original entity then this is a
|
||||
// new override from a default and the blocks need to be duplicated.
|
||||
$duplicate_blocks = TRUE;
|
||||
}
|
||||
}
|
||||
if ($this->originalEntityUsesDefaultStorage($entity)) {
|
||||
// This is a new override from a default and the blocks need to be
|
||||
// duplicated.
|
||||
$duplicate_blocks = TRUE;
|
||||
}
|
||||
$new_revision = FALSE;
|
||||
if ($entity instanceof RevisionableInterface) {
|
||||
|
|
|
@ -33,6 +33,7 @@ class LayoutBuilderServiceProvider implements ServiceProviderInterface {
|
|||
new Reference('entity_type.manager'),
|
||||
new Reference('database'),
|
||||
new Reference('inline_block.usage'),
|
||||
new Reference('plugin.manager.layout_builder.section_storage'),
|
||||
]);
|
||||
$definition->addTag('event_subscriber');
|
||||
$container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition);
|
||||
|
|
|
@ -3,8 +3,13 @@
|
|||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
|
||||
|
@ -15,6 +20,13 @@ use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
|||
*/
|
||||
trait LayoutEntityHelperTrait {
|
||||
|
||||
/**
|
||||
* The section storage manager.
|
||||
*
|
||||
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
||||
*/
|
||||
protected $sectionStorageManager;
|
||||
|
||||
/**
|
||||
* Determines if an entity can have a layout.
|
||||
*
|
||||
|
@ -25,7 +37,7 @@ trait LayoutEntityHelperTrait {
|
|||
* TRUE if the entity can have a layout otherwise FALSE.
|
||||
*/
|
||||
protected function isLayoutCompatibleEntity(EntityInterface $entity) {
|
||||
return $entity instanceof LayoutEntityDisplayInterface || $this->isEntityUsingFieldOverride($entity);
|
||||
return $this->getSectionStorageForEntity($entity) !== NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,24 +63,15 @@ trait LayoutEntityHelperTrait {
|
|||
/**
|
||||
* Gets the sections for an entity if any.
|
||||
*
|
||||
* @todo Replace this method with calls to the SectionStorageManagerInterface
|
||||
* method for getting sections from an entity in
|
||||
* https://www.drupal.org/node/2986403.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return \Drupal\layout_builder\Section[]|null
|
||||
* @return \Drupal\layout_builder\Section[]
|
||||
* The entity layout sections if available.
|
||||
*/
|
||||
protected function getEntitySections(EntityInterface $entity) {
|
||||
if ($entity instanceof LayoutEntityDisplayInterface) {
|
||||
return $entity->getSections();
|
||||
}
|
||||
elseif ($this->isEntityUsingFieldOverride($entity)) {
|
||||
return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections();
|
||||
}
|
||||
return NULL;
|
||||
$section_storage = $this->getSectionStorageForEntity($entity);
|
||||
return $section_storage ? $section_storage->getSections() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,6 +96,35 @@ trait LayoutEntityHelperTrait {
|
|||
return $inline_block_components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the section storage for an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return \Drupal\layout_builder\SectionStorageInterface|null
|
||||
* The section storage if found otherwise NULL.
|
||||
*/
|
||||
protected function getSectionStorageForEntity(EntityInterface $entity) {
|
||||
// @todo Take into account other view modes in
|
||||
// https://www.drupal.org/node/3008924.
|
||||
$view_mode = 'full';
|
||||
if ($entity instanceof LayoutEntityDisplayInterface) {
|
||||
$contexts['display'] = EntityContext::fromEntity($entity);
|
||||
}
|
||||
else {
|
||||
$contexts['entity'] = EntityContext::fromEntity($entity);
|
||||
if ($entity instanceof FieldableEntityInterface) {
|
||||
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
|
||||
if ($display instanceof LayoutEntityDisplayInterface) {
|
||||
$contexts['display'] = EntityContext::fromEntity($display);
|
||||
}
|
||||
$contexts['view_mode'] = new Context(new ContextDefinition('string'), $view_mode);
|
||||
}
|
||||
}
|
||||
return $this->sectionStorageManager()->findByContext($contexts, new CacheableMetadata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an entity is using a field for the layout override.
|
||||
*
|
||||
|
@ -101,9 +133,50 @@ trait LayoutEntityHelperTrait {
|
|||
*
|
||||
* @return bool
|
||||
* TRUE if the entity is using a field for a layout override.
|
||||
*
|
||||
* @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0.
|
||||
* To determine if an entity has a layout override, use
|
||||
* \Drupal\layout_builder\LayoutEntityHelperTrait::getSectionStorageForEntity()
|
||||
* and check whether the result is an instance of
|
||||
* \Drupal\layout_builder\DefaultsSectionStorageInterface.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3030609
|
||||
*/
|
||||
protected function isEntityUsingFieldOverride(EntityInterface $entity) {
|
||||
@trigger_error('\Drupal\layout_builder\LayoutEntityHelperTrait::isEntityUsingFieldOverride() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Internal storage of overrides may change so the existence of the field does not necessarily guarantee an overridable entity. See https://www.drupal.org/node/3030609.', E_USER_DEPRECATED);
|
||||
return $entity instanceof FieldableEntityInterface && $entity->hasField(OverridesSectionStorage::FIELD_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the original entity used the default section storage.
|
||||
*
|
||||
* This method can be used during the entity save process to determine whether
|
||||
* $entity->original is set and used the default section storage plugin as
|
||||
* determined by ::getSectionStorageForEntity().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the original entity used the default storage.
|
||||
*/
|
||||
protected function originalEntityUsesDefaultStorage(EntityInterface $entity) {
|
||||
$section_storage = $this->getSectionStorageForEntity($entity);
|
||||
if ($section_storage instanceof OverridesSectionStorageInterface && !$entity->isNew() && isset($entity->original)) {
|
||||
$original_section_storage = $this->getSectionStorageForEntity($entity->original);
|
||||
return $original_section_storage instanceof DefaultsSectionStorageInterface;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the section storage manager.
|
||||
*
|
||||
* @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
||||
* The section storage manager.
|
||||
*/
|
||||
private function sectionStorageManager() {
|
||||
return $this->sectionStorageManager ?: \Drupal::service('plugin.manager.layout_builder.section_storage');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
|
||||
use Drupal\layout_builder\Section;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||
use Drupal\layout_builder\SectionStorageInterface;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\layout_builder\LayoutEntityHelperTrait
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class LayoutEntityHelperTraitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'layout_builder',
|
||||
'entity_test',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('system', ['key_value_expire']);
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('entity_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for testGetSectionStorageForEntity().
|
||||
*/
|
||||
public function providerTestGetSectionStorageForEntity() {
|
||||
$data = [];
|
||||
$data['entity_view_display'] = [
|
||||
'entity_view_display',
|
||||
[
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
'third_party_settings' => [
|
||||
'layout_builder' => [
|
||||
'enabled' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
['display'],
|
||||
];
|
||||
$data['fieldable entity'] = [
|
||||
'entity_test',
|
||||
[],
|
||||
['entity', 'display', 'view_mode'],
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getSectionStorageForEntity
|
||||
*
|
||||
* @dataProvider providerTestGetSectionStorageForEntity
|
||||
*/
|
||||
public function testGetSectionStorageForEntity($entity_type_id, $values, $expected_context_keys) {
|
||||
$section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class);
|
||||
$section_storage_manager->load('')->willReturn(NULL);
|
||||
$section_storage_manager->findByContext(Argument::cetera())->will(function ($arguments) {
|
||||
return $arguments[0];
|
||||
});
|
||||
$this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal());
|
||||
$entity = $this->container->get('entity_type.manager')->getStorage($entity_type_id)->create($values);
|
||||
$entity->save();
|
||||
$class = new TestLayoutEntityHelperTrait();
|
||||
$result = $class->getSectionStorageForEntity($entity);
|
||||
$this->assertEquals($expected_context_keys, array_keys($result));
|
||||
if ($entity instanceof EntityViewDisplayInterface) {
|
||||
$this->assertEquals(EntityContext::fromEntity($entity), $result['display']);
|
||||
}
|
||||
elseif ($entity instanceof FieldableEntityInterface) {
|
||||
$this->assertEquals(EntityContext::fromEntity($entity), $result['entity']);
|
||||
$this->assertInstanceOf(Context::class, $result['view_mode']);
|
||||
$this->assertEquals('full', $result['view_mode']->getContextData()->getValue());
|
||||
|
||||
$expected_display = EntityViewDisplay::collectRenderDisplay($entity, 'full');
|
||||
$this->assertInstanceOf(EntityContext::class, $result['display']);
|
||||
/** @var \Drupal\Core\Plugin\Context\EntityContext $display_entity_context */
|
||||
$display_entity_context = $result['display'];
|
||||
|
||||
/** @var \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay $display_entity */
|
||||
$display_entity = $display_entity_context->getContextData()->getValue();
|
||||
$this->assertInstanceOf(LayoutBuilderEntityViewDisplay::class, $display_entity);
|
||||
|
||||
$this->assertEquals('full', $display_entity->getMode());
|
||||
$this->assertEquals($expected_display->getEntityTypeId(), $display_entity->getEntityTypeId());
|
||||
$this->assertEquals($expected_display->getComponents(), $display_entity->getComponents());
|
||||
$this->assertEquals($expected_display->getThirdPartySettings('layout_builder'), $display_entity->getThirdPartySettings('layout_builder'));
|
||||
}
|
||||
else {
|
||||
throw new \UnexpectedValueException("Unexpected entity type.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for testOriginalEntityUsesDefaultStorage().
|
||||
*/
|
||||
public function providerTestOriginalEntityUsesDefaultStorage() {
|
||||
return [
|
||||
'original uses default' => [
|
||||
[
|
||||
'updated' => 'override',
|
||||
'original' => 'default',
|
||||
],
|
||||
FALSE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
],
|
||||
'original uses override' => [
|
||||
[
|
||||
'updated' => 'override',
|
||||
'original' => 'override',
|
||||
],
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
],
|
||||
'no original use override' => [
|
||||
[
|
||||
'updated' => 'override',
|
||||
],
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
'no original uses default' => [
|
||||
[
|
||||
'updated' => 'default',
|
||||
],
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
'is new use override' => [
|
||||
[
|
||||
'updated' => 'override',
|
||||
],
|
||||
TRUE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
'is new use default' => [
|
||||
[
|
||||
'updated' => 'default',
|
||||
],
|
||||
TRUE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::originalEntityUsesDefaultStorage
|
||||
*
|
||||
* @dataProvider providerTestOriginalEntityUsesDefaultStorage
|
||||
*/
|
||||
public function testOriginalEntityUsesDefaultStorage($entity_storages, $is_new, $has_original, $expected) {
|
||||
$this->assertFalse($is_new && $has_original);
|
||||
$entity = EntityTest::create(['name' => 'updated']);
|
||||
if (!$is_new) {
|
||||
$entity->save();
|
||||
if ($has_original) {
|
||||
$original_entity = EntityTest::create(['name' => 'original']);
|
||||
$entity->original = $original_entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class);
|
||||
$section_storage_manager->load('')->willReturn(NULL);
|
||||
$storages = [
|
||||
'default' => $this->prophesize(DefaultsSectionStorageInterface::class)->reveal(),
|
||||
'override' => $this->prophesize(OverridesSectionStorageInterface::class)->reveal(),
|
||||
];
|
||||
|
||||
$section_storage_manager->findByContext(Argument::cetera())->will(function ($arguments) use ($storages, $entity_storages) {
|
||||
$contexts = $arguments[0];
|
||||
if (isset($contexts['entity'])) {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTest $entity */
|
||||
$entity = $contexts['entity']->getContextData()->getValue();
|
||||
return $storages[$entity_storages[$entity->getName()]];
|
||||
}
|
||||
});
|
||||
|
||||
$this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal());
|
||||
$class = new TestLayoutEntityHelperTrait();
|
||||
$this->assertSame($expected, $class->originalEntityUsesDefaultStorage($entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getEntitySections
|
||||
*/
|
||||
public function testGetEntitySections() {
|
||||
$entity = EntityTest::create(['name' => 'updated']);
|
||||
$section_storage_manager = $this->prophesize(SectionStorageManagerInterface::class);
|
||||
$section_storage_manager->load('')->willReturn(NULL);
|
||||
$section_storage = $this->prophesize(SectionStorageInterface::class);
|
||||
$sections = [
|
||||
new Section('layout_onecol'),
|
||||
];
|
||||
$this->assertCount(1, $sections);
|
||||
$section_storage->getSections()->willReturn($sections);
|
||||
$section_storage->count()->willReturn(1);
|
||||
|
||||
$section_storage_manager->findByContext(Argument::cetera())->willReturn($section_storage->reveal());
|
||||
$this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal());
|
||||
$class = new TestLayoutEntityHelperTrait();
|
||||
// Ensure that if the entity has a section storage the sections will be
|
||||
// returned.
|
||||
$this->assertSame($sections, $class->getEntitySections($entity));
|
||||
|
||||
$section_storage_manager->findByContext(Argument::cetera())->willReturn(NULL);
|
||||
$this->container->set('plugin.manager.layout_builder.section_storage', $section_storage_manager->reveal());
|
||||
// Ensure that if the entity has no section storage an empty array will be
|
||||
// returned.
|
||||
$this->assertSame([], $class->getEntitySections($entity));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class using the trait.
|
||||
*/
|
||||
class TestLayoutEntityHelperTrait {
|
||||
use LayoutEntityHelperTrait {
|
||||
getSectionStorageForEntity as public;
|
||||
originalEntityUsesDefaultStorage as public;
|
||||
getEntitySections as public;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Unit;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\Section;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\layout_builder\LayoutEntityHelperTrait
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class LayoutEntityHelperTraitTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::isEntityUsingFieldOverride
|
||||
*
|
||||
* @dataProvider providerTestIsEntityUsingFieldOverride
|
||||
*
|
||||
* @expectedDeprecation \Drupal\layout_builder\LayoutEntityHelperTrait::isEntityUsingFieldOverride() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Internal storage of overrides may change so the existence of the field does not necessarily guarantee an overridable entity. See https://www.drupal.org/node/3030609.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testIsEntityUsingFieldOverride(EntityInterface $entity, $expected) {
|
||||
$test_class = new TestClass();
|
||||
$this->assertSame($expected, $test_class->isEntityUsingFieldOverride($entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for testIsEntityUsingFieldOverride().
|
||||
*/
|
||||
public function providerTestIsEntityUsingFieldOverride() {
|
||||
$data['non fieldable entity'] = [
|
||||
$this->prophesize(EntityInterface::class)->reveal(),
|
||||
FALSE,
|
||||
];
|
||||
$fieldable_entity = $this->prophesize(FieldableEntityInterface::class);
|
||||
$fieldable_entity->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(FALSE);
|
||||
$data['fieldable entity without layout field'] = [
|
||||
$fieldable_entity->reveal(),
|
||||
FALSE,
|
||||
];
|
||||
$entity_using_field = $this->prophesize(FieldableEntityInterface::class);
|
||||
$entity_using_field->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(TRUE);
|
||||
$data['fieldable entity with layout field'] = [
|
||||
$entity_using_field->reveal(),
|
||||
TRUE,
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider method for tests that need sections with inline blocks.
|
||||
*/
|
||||
public function providerSectionsWithInlineComponents() {
|
||||
$components = [];
|
||||
|
||||
// Ensure a non-derivative component is not returned.
|
||||
$non_derivative_component = $this->prophesize(SectionComponent::class);
|
||||
$non_derivative_component->getPlugin()->willReturn($this->prophesize(PluginInspectionInterface::class)->reveal());
|
||||
$components[] = $non_derivative_component->reveal();
|
||||
|
||||
// Ensure a derivative component with a different base Id is not returned.
|
||||
$derivative_non_inline_component = $this->prophesize(SectionComponent::class);
|
||||
$plugin = $this->prophesize(DerivativeInspectionInterface::class);
|
||||
$plugin->getBaseId()->willReturn('some_other_base_id_which_we_do_not_care_about_but_it_is_nothing_personal');
|
||||
$derivative_non_inline_component->getPlugin()->willReturn($plugin);
|
||||
$components[] = $derivative_non_inline_component->reveal();
|
||||
|
||||
// Ensure that inline block component is returned.
|
||||
$inline_component = $this->prophesize(SectionComponent::class);
|
||||
$inline_plugin = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class);
|
||||
$inline_plugin->getBaseId()->willReturn('inline_block');
|
||||
$inline_plugin->getConfiguration()->willReturn(['block_revision_id' => 'the_revision_id']);
|
||||
$inline_component->getPlugin()->willReturn($inline_plugin->reveal());
|
||||
$inline_component = $inline_component->reveal();
|
||||
$components[] = $inline_component;
|
||||
|
||||
// Ensure that inline block component without revision is returned.
|
||||
$inline_component_without_revision_id = $this->prophesize(SectionComponent::class);
|
||||
$inline_plugin_without_revision_id = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class);
|
||||
$inline_plugin_without_revision_id->getBaseId()->willReturn('inline_block');
|
||||
$inline_plugin_without_revision_id->getConfiguration()->willReturn(['other_key' => 'other_value']);
|
||||
$inline_component_without_revision_id->getPlugin()->willReturn($inline_plugin_without_revision_id->reveal());
|
||||
$inline_component_without_revision_id = $inline_component_without_revision_id->reveal();
|
||||
$components[] = $inline_component_without_revision_id;
|
||||
|
||||
$section = $this->prophesize(Section::class);
|
||||
$section->getComponents()->willReturn($components);
|
||||
|
||||
$components = [];
|
||||
// Ensure that inline block components in all sections are returned.
|
||||
$inline_component2 = $this->prophesize(SectionComponent::class);
|
||||
$inline_plugin2 = $this->prophesize(DerivativeInspectionInterface::class)->willImplement(ConfigurablePluginInterface::class);
|
||||
$inline_plugin2->getBaseId()->willReturn('inline_block');
|
||||
$inline_plugin2->getConfiguration()->willReturn(['block_revision_id' => 'the_other_revision_id']);
|
||||
$inline_component2->getPlugin()->willReturn($inline_plugin2->reveal());
|
||||
$inline_component2 = $inline_component2->reveal();
|
||||
$components[] = $inline_component2;
|
||||
|
||||
$section2 = $this->prophesize(Section::class);
|
||||
$section2->getComponents()->willReturn($components);
|
||||
|
||||
return [
|
||||
[
|
||||
[$section->reveal(), $section2->reveal()],
|
||||
// getInlineBlockComponents() should return inline blocks even if they
|
||||
// have no revision Id.
|
||||
[
|
||||
$inline_component,
|
||||
$inline_component_without_revision_id,
|
||||
$inline_component2,
|
||||
],
|
||||
// getInlineBlockRevisionIdsInSections should just the revision Ids.
|
||||
['the_revision_id', 'the_other_revision_id'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getInlineBlockComponents
|
||||
*
|
||||
* @dataProvider providerSectionsWithInlineComponents
|
||||
*/
|
||||
public function testGetInlineBlockComponents($sections, $expected_components) {
|
||||
$test_class = new TestClass();
|
||||
$this->assertSame($expected_components, $test_class->getInlineBlockComponents($sections));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getInlineBlockRevisionIdsInSections
|
||||
*
|
||||
* @dataProvider providerSectionsWithInlineComponents
|
||||
*/
|
||||
public function testGetInlineBlockRevisionIdsInSections($sections, $components, $expected_revision_ids) {
|
||||
$test_class = new TestClass();
|
||||
$this->assertSame($expected_revision_ids, $test_class->getInlineBlockRevisionIdsInSections($sections));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class using the trait.
|
||||
*/
|
||||
class TestClass {
|
||||
use LayoutEntityHelperTrait {
|
||||
isEntityUsingFieldOverride as public;
|
||||
getInlineBlockComponents as public;
|
||||
getInlineBlockRevisionIdsInSections as public;
|
||||
}
|
||||
|
||||
}
|
|
@ -108,6 +108,15 @@ class ResolvedLibraryDefinitionsFilesMatchTest extends KernelTestBase {
|
|||
}
|
||||
return TRUE;
|
||||
});
|
||||
|
||||
// Install the 'user' entity schema because the workspaces module's install
|
||||
// hook creates a workspace with default uid of 1. Then the layout_builder
|
||||
// module's implementation of hook_entity_presave will cause
|
||||
// \Drupal\Core\TypedData\Validation\RecursiveValidator::validate() to run
|
||||
// on the workspace which will fail because the user table is not present.
|
||||
// @todo Remove this in https://www.drupal.org/node/3039217.
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
// Remove demo_umami_content module as its install hook creates content
|
||||
// that relies on the presence of entity tables and various other elements
|
||||
// not present in a kernel test.
|
||||
|
|
Loading…
Reference in New Issue