diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php index dd01f10da5..65d25423c9 100644 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\Request; /** - * Converts the query paramater for layout rebuild status to a route default. + * Converts the query parameter for layout rebuild status to a route default. * * @internal */ diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php index 0c59762056..563021ed57 100644 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php @@ -53,9 +53,6 @@ public function getRoutes() { foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) { $defaults = []; $defaults['entity_type_id'] = $entity_type_id; - $defaults['section_storage_type'] = 'overrides'; - // Provide an empty value to allow the section storage to be upcast. - $defaults['section_storage'] = ''; $requirements = []; if ($this->hasIntegerId($entity_type)) { @@ -67,7 +64,7 @@ public function getRoutes() { $options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id; $template = $entity_type->getLinkTemplate('layout-builder'); - $routes += $this->buildRoute('entity.' . $entity_type_id, $template, $defaults, $requirements, $options); + $routes += $this->buildRoute('overrides', 'entity.' . $entity_type_id, $template, $defaults, $requirements, $options); } return $routes; } @@ -75,6 +72,8 @@ public function getRoutes() { /** * Builds the layout routes for the given values. * + * @param string $type + * The section storage type. * @param string $route_name_prefix * The prefix to use for the route name. * @param string $path @@ -89,14 +88,19 @@ public function getRoutes() { * @return \Symfony\Component\Routing\Route[] * An array of route objects. */ - protected function buildRoute($route_name_prefix, $path, array $defaults, array $requirements, array $options) { + protected function buildRoute($type, $route_name_prefix, $path, array $defaults, array $requirements, array $options) { $routes = []; - $defaults['is_rebuilding'] = FALSE; + $defaults['section_storage_type'] = $type; + // Provide an empty value to allow the section storage to be upcast. + $defaults['section_storage'] = ''; + // Trigger the layout builder access check. $requirements['_has_layout_section'] = 'true'; + // Trigger the layout builder RouteEnhancer. $options['_layout_builder'] = TRUE; $main_defaults = $defaults; + $main_defaults['is_rebuilding'] = FALSE; $main_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::layout'; $main_defaults['_title_callback'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::title'; $route = (new Route($path)) diff --git a/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php index ff63f3fac4..228a786764 100644 --- a/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php +++ b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php @@ -45,14 +45,40 @@ public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore * {@inheritdoc} */ public function convert($value, $definition, $name, array $defaults) { - if (isset($defaults['section_storage_type'])) { - $converter = $this->classResolver->getInstanceFromDefinition('layout_builder.section_storage_param_converter.' . $defaults['section_storage_type']); + if ($converter = $this->getParamConverterFromDefaults($defaults)) { if ($object = $converter->convert($value, $definition, $name, $defaults)) { + // Pass the result of the storage param converter through the + // tempstore repository. return $this->layoutTempstoreRepository->get($object); } } } + /** + * Gets a param converter based on the provided defaults. + * + * @param array $defaults + * The route defaults array. + * + * @return \Drupal\layout_builder\Routing\SectionStorageParamConverterInterface|null + * A section storage param converter if found, NULL otherwise. + */ + protected function getParamConverterFromDefaults(array $defaults) { + // If a storage type was specified, get the corresponding param converter. + if (isset($defaults['section_storage_type'])) { + try { + $converter = $this->classResolver->getInstanceFromDefinition('layout_builder.section_storage_param_converter.' . $defaults['section_storage_type']); + } + catch (\InvalidArgumentException $e) { + $converter = NULL; + } + + if ($converter instanceof SectionStorageParamConverterInterface) { + return $converter; + } + } + } + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Routing/SectionStorageOverridesParamConverter.php b/core/modules/layout_builder/src/Routing/SectionStorageOverridesParamConverter.php index da54759738..8d8ae58059 100644 --- a/core/modules/layout_builder/src/Routing/SectionStorageOverridesParamConverter.php +++ b/core/modules/layout_builder/src/Routing/SectionStorageOverridesParamConverter.php @@ -15,6 +15,11 @@ class SectionStorageOverridesParamConverter extends EntityConverter implements S */ public function convert($value, $definition, $name, array $defaults) { $entity_id = $this->getEntityIdFromDefaults($value, $defaults); + $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults); + if (!$entity_id || !$entity_type_id) { + return NULL; + } + $entity = parent::convert($entity_id, $definition, $name, $defaults); if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) { return $entity->get('layout_builder__layout'); @@ -40,7 +45,7 @@ protected function getEntityIdFromDefaults($value, array $defaults) { list(, $entity_id) = explode(':', $value); } // Overridden routes have the entity ID available in the defaults. - else { + elseif (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) { $entity_id = $defaults[$defaults['entity_type_id']]; } return $entity_id; diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php index 22bdaf51bd..6c376fa7cf 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php @@ -97,23 +97,25 @@ protected function installLayoutBuilder() { } /** - * Renders the provided entity. + * Asserts that the rendered entity has the correct fields. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity to render. - * @param string $view_mode - * (optional) The view mode that should be used to render the entity. - * @param string $langcode - * (optional) For which language the entity should be rendered, defaults to - * the current content language. + * @param array $attributes + * An array of field attributes to assert. * * @return string * The rendered string output (typically HTML). */ - protected function renderEntity(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { + protected function assertFieldAttributes(EntityInterface $entity, array $attributes) { $view_builder = $this->container->get('entity_type.manager')->getViewBuilder($entity->getEntityTypeId()); - $build = $view_builder->view($entity, $view_mode, $langcode); - return $this->render($build); + $build = $view_builder->view($entity); + $this->render($build); + + $actual = array_map(function (\SimpleXMLElement $element) { + return (string) $element->attributes(); + }, $this->cssSelect('.field')); + $this->assertSame($attributes, $actual); } } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php index 3dc4021ce7..59a76615c1 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php @@ -35,7 +35,14 @@ protected function setUp() { public function testCompatibility() { // Ensure that the configurable field is shown in the correct region and // that the non-configurable field is shown outside the layout. - $original_markup = $this->renderEntity($this->entity); + $expected_fields = [ + 'field field--name-name field--type-string field--label-hidden field__item', + 'field field--name-test-field-display-configurable field--type-boolean field--label-above', + 'clearfix text-formatted field field--name-test-display-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-non-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-multiple field--type-text field--label-above', + ]; + $this->assertFieldAttributes($this->entity, $expected_fields); $this->assertNotEmpty($this->cssSelect('.layout__region--first .field--name-test-display-configurable')); $this->assertNotEmpty($this->cssSelect('.layout__region--first .field--name-test-field-display-configurable')); $this->assertNotEmpty($this->cssSelect('.field--name-test-display-non-configurable')); @@ -44,8 +51,7 @@ public function testCompatibility() { $this->installLayoutBuilder(); // Without using Layout Builder for an override, the result has not changed. - $new_markup = $this->renderEntity($this->entity); - $this->assertSame($original_markup, $new_markup); + $this->assertFieldAttributes($this->entity, $expected_fields); // Add a layout override. /** @var \Drupal\layout_builder\SectionStorageInterface $field_list */ @@ -56,18 +62,18 @@ public function testCompatibility() { // The rendered entity has now changed. The non-configurable field is shown // outside the layout, the configurable field is not shown at all, and the // layout itself is rendered (but empty). - $new_markup = $this->renderEntity($this->entity); - $this->assertNotSame($original_markup, $new_markup); - $this->assertEmpty($this->cssSelect('.field--name-test-display-configurable')); - $this->assertEmpty($this->cssSelect('.field--name-test-field-display-configurable')); - $this->assertNotEmpty($this->cssSelect('.field--name-test-display-non-configurable')); + $new_expected_fields = [ + 'field field--name-name field--type-string field--label-hidden field__item', + 'clearfix text-formatted field field--name-test-display-non-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-multiple field--type-text field--label-above', + ]; + $this->assertFieldAttributes($this->entity, $new_expected_fields); $this->assertNotEmpty($this->cssSelect('.layout--onecol')); // Removing the layout restores the original rendering of the entity. $field_list->removeSection(0); $this->entity->save(); - $new_markup = $this->renderEntity($this->entity); - $this->assertSame($original_markup, $new_markup); + $this->assertFieldAttributes($this->entity, $expected_fields); } } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php index 8d491de135..3873af81b0 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php @@ -16,16 +16,19 @@ class LayoutBuilderInstallTest extends LayoutBuilderCompatibilityTestBase { */ public function testCompatibility() { // Ensure that the fields are shown. - $original_markup = $this->renderEntity($this->entity); - $this->assertNotEmpty($this->cssSelect('.field--name-test-display-configurable')); - $this->assertNotEmpty($this->cssSelect('.field--name-test-field-display-configurable')); - $this->assertNotEmpty($this->cssSelect('.field--name-test-display-non-configurable')); + $expected_fields = [ + 'field field--name-name field--type-string field--label-hidden field__item', + 'field field--name-test-field-display-configurable field--type-boolean field--label-above', + 'clearfix text-formatted field field--name-test-display-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-non-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-multiple field--type-text field--label-above', + ]; + $this->assertFieldAttributes($this->entity, $expected_fields); $this->installLayoutBuilder(); // Without using Layout Builder for an override, the result has not changed. - $new_markup = $this->renderEntity($this->entity); - $this->assertSame($original_markup, $new_markup); + $this->assertFieldAttributes($this->entity, $expected_fields); // Add a layout override. $this->entity->get('layout_builder__layout')->appendSection(new Section('layout_onecol')); @@ -34,18 +37,18 @@ public function testCompatibility() { // The rendered entity has now changed. The non-configurable field is shown // outside the layout, the configurable field is not shown at all, and the // layout itself is rendered (but empty). - $new_markup = $this->renderEntity($this->entity); - $this->assertNotSame($original_markup, $new_markup); - $this->assertEmpty($this->cssSelect('.field--name-test-display-configurable')); - $this->assertEmpty($this->cssSelect('.field--name-test-field-display-configurable')); - $this->assertNotEmpty($this->cssSelect('.field--name-test-display-non-configurable')); + $new_expected_fields = [ + 'field field--name-name field--type-string field--label-hidden field__item', + 'clearfix text-formatted field field--name-test-display-non-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-multiple field--type-text field--label-above', + ]; + $this->assertFieldAttributes($this->entity, $new_expected_fields); $this->assertNotEmpty($this->cssSelect('.layout--onecol')); // Removing the layout restores the original rendering of the entity. $this->entity->get('layout_builder__layout')->removeSection(0); $this->entity->save(); - $new_markup = $this->renderEntity($this->entity); - $this->assertSame($original_markup, $new_markup); + $this->assertFieldAttributes($this->entity, $expected_fields); } } diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRoutesTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRoutesTest.php new file mode 100644 index 0000000000..e209f6ad43 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRoutesTest.php @@ -0,0 +1,402 @@ + 'no_link_template']); + $entity_types['with_link_template'] = new EntityType([ + 'id' => 'with_link_template', + 'links' => ['layout-builder' => '/entity/{entity}/layout'], + 'entity_keys' => ['id' => 'id'], + 'field_ui_base_route' => 'unknown', + ]); + $entity_types['with_integer_id'] = new EntityType([ + 'id' => 'with_integer_id', + 'links' => ['layout-builder' => '/entity/{entity}/layout'], + 'entity_keys' => ['id' => 'id'], + ]); + $entity_types['with_field_ui_route'] = new EntityType([ + 'id' => 'with_field_ui_route', + 'links' => ['layout-builder' => '/entity/{entity}/layout'], + 'entity_keys' => ['id' => 'id'], + 'field_ui_base_route' => 'known', + ]); + $entity_types['with_bundle_key'] = new EntityType([ + 'id' => 'with_field_ui_route', + 'links' => ['layout-builder' => '/entity/{entity}/layout'], + 'entity_keys' => ['id' => 'id', 'bundle' => 'bundle'], + 'bundle_entity_type' => 'my_bundle_type', + 'field_ui_base_route' => 'known', + ]); + $entity_types['with_bundle_parameter'] = new EntityType([ + 'id' => 'with_bundle_parameter', + 'links' => ['layout-builder' => '/entity/{entity}/layout'], + 'entity_keys' => ['id' => 'id'], + 'field_ui_base_route' => 'with_bundle', + ]); + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + $entity_type_manager->getDefinitions()->willReturn($entity_types); + + $string_id = $this->prophesize(FieldStorageDefinitionInterface::class); + $string_id->getType()->willReturn('string'); + $integer_id = $this->prophesize(FieldStorageDefinitionInterface::class); + $integer_id->getType()->willReturn('integer'); + $entity_field_manager = $this->prophesize(EntityFieldManagerInterface::class); + $entity_field_manager->getFieldStorageDefinitions('no_link_template')->shouldNotBeCalled(); + $entity_field_manager->getFieldStorageDefinitions('with_link_template')->willReturn(['id' => $string_id->reveal()]); + $entity_field_manager->getFieldStorageDefinitions('with_integer_id')->willReturn(['id' => $integer_id->reveal()]); + $entity_field_manager->getFieldStorageDefinitions('with_field_ui_route')->willReturn(['id' => $integer_id->reveal()]); + $entity_field_manager->getFieldStorageDefinitions('with_bundle_parameter')->willReturn(['id' => $integer_id->reveal()]); + + $this->routeBuilder = new LayoutBuilderRoutes($entity_type_manager->reveal(), $entity_field_manager->reveal()); + } + + /** + * @covers ::getRoutes + * @covers ::buildRoute + * @covers ::hasIntegerId + * @covers ::getEntityTypes + */ + public function testGetRoutes() { + $expected = [ + 'entity.with_link_template.layout_builder' => new Route( + '/entity/{entity}/layout', + [ + 'entity_type_id' => 'with_link_template', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + 'is_rebuilding' => FALSE, + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', + ], + [ + '_has_layout_section' => 'true', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_link_template' => ['type' => 'entity:with_link_template'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_link_template.layout_builder_save' => new Route( + '/entity/{entity}/layout/save', + [ + 'entity_type_id' => 'with_link_template', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout', + ], + [ + '_has_layout_section' => 'true', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_link_template' => ['type' => 'entity:with_link_template'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_link_template.layout_builder_cancel' => new Route( + '/entity/{entity}/layout/cancel', + [ + 'entity_type_id' => 'with_link_template', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout', + ], + [ + '_has_layout_section' => 'true', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_link_template' => ['type' => 'entity:with_link_template'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_integer_id.layout_builder' => new Route( + '/entity/{entity}/layout', + [ + 'entity_type_id' => 'with_integer_id', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + 'is_rebuilding' => FALSE, + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', + ], + [ + '_has_layout_section' => 'true', + 'with_integer_id' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_integer_id' => ['type' => 'entity:with_integer_id'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_integer_id.layout_builder_save' => new Route( + '/entity/{entity}/layout/save', + [ + 'entity_type_id' => 'with_integer_id', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_integer_id' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_integer_id' => ['type' => 'entity:with_integer_id'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_integer_id.layout_builder_cancel' => new Route( + '/entity/{entity}/layout/cancel', + [ + 'entity_type_id' => 'with_integer_id', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_integer_id' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_integer_id' => ['type' => 'entity:with_integer_id'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_field_ui_route.layout_builder' => new Route( + '/entity/{entity}/layout', + [ + 'entity_type_id' => 'with_field_ui_route', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + 'is_rebuilding' => FALSE, + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', + ], + [ + '_has_layout_section' => 'true', + 'with_field_ui_route' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_field_ui_route' => ['type' => 'entity:with_field_ui_route'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_field_ui_route.layout_builder_save' => new Route( + '/entity/{entity}/layout/save', + [ + 'entity_type_id' => 'with_field_ui_route', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_field_ui_route' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_field_ui_route' => ['type' => 'entity:with_field_ui_route'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_field_ui_route.layout_builder_cancel' => new Route( + '/entity/{entity}/layout/cancel', + [ + 'entity_type_id' => 'with_field_ui_route', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_field_ui_route' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_field_ui_route' => ['type' => 'entity:with_field_ui_route'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_key.layout_builder' => new Route( + '/entity/{entity}/layout', + [ + 'entity_type_id' => 'with_bundle_key', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + 'is_rebuilding' => FALSE, + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_key' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_key' => ['type' => 'entity:with_bundle_key'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_key.layout_builder_save' => new Route( + '/entity/{entity}/layout/save', + [ + 'entity_type_id' => 'with_bundle_key', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_key' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_key' => ['type' => 'entity:with_bundle_key'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_key.layout_builder_cancel' => new Route( + '/entity/{entity}/layout/cancel', + [ + 'entity_type_id' => 'with_bundle_key', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_key' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_key' => ['type' => 'entity:with_bundle_key'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_parameter.layout_builder' => new Route( + '/entity/{entity}/layout', + [ + 'entity_type_id' => 'with_bundle_parameter', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + 'is_rebuilding' => FALSE, + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_parameter' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_parameter' => ['type' => 'entity:with_bundle_parameter'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_parameter.layout_builder_save' => new Route( + '/entity/{entity}/layout/save', + [ + 'entity_type_id' => 'with_bundle_parameter', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_parameter' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_parameter' => ['type' => 'entity:with_bundle_parameter'], + ], + '_layout_builder' => TRUE, + ] + ), + 'entity.with_bundle_parameter.layout_builder_cancel' => new Route( + '/entity/{entity}/layout/cancel', + [ + 'entity_type_id' => 'with_bundle_parameter', + 'section_storage_type' => 'overrides', + 'section_storage' => '', + '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout', + ], + [ + '_has_layout_section' => 'true', + 'with_bundle_parameter' => '\d+', + ], + [ + 'parameters' => [ + 'section_storage' => ['layout_builder_tempstore' => TRUE], + 'with_bundle_parameter' => ['type' => 'entity:with_bundle_parameter'], + ], + '_layout_builder' => TRUE, + ] + ), + ]; + + $this->assertEquals($expected, $this->routeBuilder->getRoutes()); + } + +} diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php new file mode 100644 index 0000000000..3c3c9f4065 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreParamConverterTest.php @@ -0,0 +1,86 @@ +prophesize(LayoutTempstoreRepositoryInterface::class); + $class_resolver = $this->prophesize(ClassResolverInterface::class); + $param_converter = $this->prophesize(SectionStorageParamConverterInterface::class); + $converter = new LayoutTempstoreParamConverter($layout_tempstore_repository->reveal(), $class_resolver->reveal()); + + $value = 'some_value'; + $definition = ['layout_builder_tempstore' => TRUE]; + $name = 'the_parameter_name'; + $defaults = ['section_storage_type' => 'my_type']; + $section_storage = $this->prophesize(SectionStorageInterface::class); + $expected = 'the_return_value'; + + $class_resolver->getInstanceFromDefinition('layout_builder.section_storage_param_converter.my_type')->willReturn($param_converter->reveal()); + $param_converter->convert($value, $definition, $name, $defaults)->willReturn($section_storage->reveal()); + $layout_tempstore_repository->get($section_storage->reveal())->willReturn($expected); + + $result = $converter->convert($value, $definition, $name, $defaults); + $this->assertEquals($expected, $result); + } + + /** + * @covers ::convert + * @covers ::getParamConverterFromDefaults + */ + public function testConvertNoType() { + $layout_tempstore_repository = $this->prophesize(LayoutTempstoreRepositoryInterface::class); + $class_resolver = $this->prophesize(ClassResolverInterface::class); + $converter = new LayoutTempstoreParamConverter($layout_tempstore_repository->reveal(), $class_resolver->reveal()); + + $value = 'some_value'; + $definition = ['layout_builder_tempstore' => TRUE]; + $name = 'the_parameter_name'; + $defaults = ['section_storage_type' => NULL]; + + $class_resolver->getInstanceFromDefinition()->shouldNotBeCalled(); + $layout_tempstore_repository->get()->shouldNotBeCalled(); + + $result = $converter->convert($value, $definition, $name, $defaults); + $this->assertNull($result); + } + + /** + * @covers ::convert + * @covers ::getParamConverterFromDefaults + */ + public function testConvertInvalidConverter() { + $layout_tempstore_repository = $this->prophesize(LayoutTempstoreRepositoryInterface::class); + $class_resolver = $this->prophesize(ClassResolverInterface::class); + $converter = new LayoutTempstoreParamConverter($layout_tempstore_repository->reveal(), $class_resolver->reveal()); + + $value = 'some_value'; + $definition = ['layout_builder_tempstore' => TRUE]; + $name = 'the_parameter_name'; + $defaults = ['section_storage_type' => 'invalid']; + + $class_resolver->getInstanceFromDefinition('layout_builder.section_storage_param_converter.invalid')->willThrow(\InvalidArgumentException::class); + $layout_tempstore_repository->get()->shouldNotBeCalled(); + + $result = $converter->convert($value, $definition, $name, $defaults); + $this->assertNull($result); + } + +} diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageOverridesParamConverterTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageOverridesParamConverterTest.php new file mode 100644 index 0000000000..f0c01468e5 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageOverridesParamConverterTest.php @@ -0,0 +1,119 @@ +entityManager = $this->prophesize(EntityManagerInterface::class); + $this->converter = new SectionStorageOverridesParamConverter($this->entityManager->reveal()); + } + + /** + * @covers ::convert + * @covers ::getEntityTypeFromDefaults + * @covers ::getEntityIdFromDefaults + * + * @dataProvider providerTestConvert + */ + public function testConvert($success, $expected_entity_type_id, $value, array $defaults) { + $defaults['the_parameter_name'] = $value; + + if ($expected_entity_type_id) { + $entity_storage = $this->prophesize(EntityStorageInterface::class); + + $entity_without_layout = $this->prophesize(FieldableEntityInterface::class); + $entity_without_layout->hasField('layout_builder__layout')->willReturn(FALSE); + $entity_without_layout->get('layout_builder__layout')->shouldNotBeCalled(); + $entity_storage->load('entity_without_layout')->willReturn($entity_without_layout->reveal()); + + $entity_with_layout = $this->prophesize(FieldableEntityInterface::class); + $entity_with_layout->hasField('layout_builder__layout')->willReturn(TRUE); + $entity_with_layout->get('layout_builder__layout')->willReturn('the_return_value'); + $entity_storage->load('entity_with_layout')->willReturn($entity_with_layout->reveal()); + + $this->entityManager->getDefinition($expected_entity_type_id)->willReturn(new EntityType(['id' => 'entity_view_display'])); + $this->entityManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal()); + } + else { + $this->entityManager->getDefinition(Argument::any())->shouldNotBeCalled(); + $this->entityManager->getStorage(Argument::any())->shouldNotBeCalled(); + } + + $result = $this->converter->convert($value, [], 'the_parameter_name', $defaults); + if ($success) { + $this->assertEquals('the_return_value', $result); + } + else { + $this->assertNull($result); + } + } + + /** + * Provides data for ::testConvert(). + */ + public function providerTestConvert() { + $data = []; + $data['with value, with layout'] = [ + TRUE, + 'my_entity_type', + 'my_entity_type:entity_with_layout', + [], + ]; + $data['with value, without layout'] = [ + FALSE, + 'my_entity_type', + 'my_entity_type:entity_without_layout', + [], + ]; + $data['empty value, populated defaults'] = [ + TRUE, + 'my_entity_type', + '', + [ + 'entity_type_id' => 'my_entity_type', + 'my_entity_type' => 'entity_with_layout', + ], + ]; + $data['empty value, empty defaults'] = [ + FALSE, + NULL, + '', + [], + ]; + return $data; + } + +}