diff --git a/src/Plugin/DataType/ComponentInputs.php b/src/Plugin/DataType/ComponentInputs.php index 87333e027b22123db433457df5b61ad622577eb2..704600a1cba275230b08af8bc335182964205922 100644 --- a/src/Plugin/DataType/ComponentInputs.php +++ b/src/Plugin/DataType/ComponentInputs.php @@ -8,7 +8,9 @@ use Drupal\Component\Serialization\Json; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\TypedData; +use Drupal\experience_builder\ComponentSource\ComponentSourceInterface; use Drupal\experience_builder\MissingComponentInputsException; +use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem; /** * @todo Implement ListInterface because it conceptually fits, but … what does it get us? @@ -125,8 +127,17 @@ class ComponentInputs extends TypedData implements \Stringable { * @throws \Drupal\experience_builder\MissingComponentInputsException */ public function getValues(string $component_instance_uuid): array { + $item = $this->parent; + assert($item instanceof ComponentTreeItem); + $tree = $item->get('tree'); + assert($tree instanceof ComponentTreeStructure); + $source = $tree->getComponentSource($component_instance_uuid); + \assert($source instanceof ComponentSourceInterface); if (!array_key_exists($component_instance_uuid, $this->inputs)) { - throw new MissingComponentInputsException($component_instance_uuid); + if ($source->requiresExplicitInput()) { + throw new MissingComponentInputsException($component_instance_uuid); + } + return []; } return $this->inputs[$component_instance_uuid]; diff --git a/src/Plugin/Field/FieldType/ComponentTreeItem.php b/src/Plugin/Field/FieldType/ComponentTreeItem.php index 1dcceffbe1e7a3a455ff5a99895048aeddd088cb..72e5856fd75144bab54d32a8c334801ef1bcfcb8 100644 --- a/src/Plugin/Field/FieldType/ComponentTreeItem.php +++ b/src/Plugin/Field/FieldType/ComponentTreeItem.php @@ -16,6 +16,7 @@ use Drupal\Core\Render\RenderableInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\DataDefinition; use Drupal\experience_builder\Entity\Component; +use Drupal\experience_builder\Plugin\DataType\ComponentInputs; use Drupal\experience_builder\Plugin\DataType\ComponentTreeStructure; use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester; use Symfony\Component\Validator\ConstraintViolation; @@ -261,6 +262,7 @@ class ComponentTreeItem extends FieldItemBase implements RenderableInterface { public function preSave(): void { $tree = $this->get('tree'); $inputs = $this->get('inputs'); + assert($inputs instanceof ComponentInputs); $component_instance_uuids = $tree->getComponentInstanceUuids(); $entity = $this->getRoot() === $this ? NULL : $this->getEntity(); @@ -305,7 +307,8 @@ class ComponentTreeItem extends FieldItemBase implements RenderableInterface { // This *internal-only* validation does not need to happen using validation // constraints because it does not validate user input: it only helps ensure // that the logic of this field type is correct. - if (array_intersect($component_instance_uuids, $inputs->getComponentInstanceUuids()) !== $component_instance_uuids) { + $input_required_uuids = array_filter($tree->getComponentInstanceUuids(), static fn(string $uuid) => $tree->getComponentSource($uuid)?->requiresExplicitInput() === TRUE); + if (array_intersect($input_required_uuids, $inputs->getComponentInstanceUuids()) !== $input_required_uuids) { throw new \LogicException(sprintf('The component UUIDs in the tree and inputs values do not match! Put a breakpoint here and figure out why.')); } diff --git a/tests/src/Unit/DataType/ComponentInputsTest.php b/tests/src/Unit/DataType/ComponentInputsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0e841d92967ca08b84a65d070004d55522a6f416 --- /dev/null +++ b/tests/src/Unit/DataType/ComponentInputsTest.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\experience_builder\Unit\Plugin\DataType; + +use Drupal\Component\Serialization\Json; +use Drupal\Core\TypedData\DataDefinitionInterface; +use Drupal\experience_builder\ComponentSource\ComponentSourceInterface; +use Drupal\experience_builder\MissingComponentInputsException; +use Drupal\experience_builder\Plugin\DataType\ComponentInputs; +use Drupal\experience_builder\Plugin\DataType\ComponentTreeStructure; +use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\experience_builder\Plugin\DataType\ComponentInputs + */ +class ComponentInputsTest extends UnitTestCase { + + /** + * @covers ::getValues + */ + public function testGetValues(): void { + $existing_component_uuid = 'test-component-uuid'; + + // Create test data. + $test_inputs = [ + $existing_component_uuid => [ + 'title' => [ + 'sourceType' => 'static:text', + 'value' => 'Test Title', + 'expression' => '', + ], + 'body' => [ + 'sourceType' => 'static:text', + 'value' => 'Test Body', + 'expression' => '', + ], + ], + ]; + $component_source = $this->prophesize(ComponentSourceInterface::class); + + $tree = $this->prophesize(ComponentTreeStructure::class); + $tree->getComponentId($existing_component_uuid)->willReturn('test-component-id'); + $tree->getComponentSource($existing_component_uuid)->willReturn($component_source->reveal()); + + $item = $this->prophesize(ComponentTreeItem::class); + $item->get('tree')->willReturn($tree->reveal()); + $item->onChange(NULL)->shouldBeCalledTimes(1); + + $component_inputs = new ComponentInputs( + $this->prophesize(DataDefinitionInterface::class)->reveal(), + NULL, + $item->reveal() + ); + $component_inputs->setValue(Json::encode($test_inputs)); + + // Test getting values for a existing UUID. + $this->assertEquals( + $test_inputs[$existing_component_uuid], + $component_inputs->getValues($existing_component_uuid) + ); + + // Test getting values for a non-existing UUID that doesn't require explicit input. + $non_existing_uuid = 'non-existing-uuid'; + $tree->getComponentSource($non_existing_uuid)->willReturn($component_source->reveal()); + $component_source->requiresExplicitInput()->willReturn(FALSE); + + $values = $component_inputs->getValues($non_existing_uuid); + $this->assertEquals([], $values); + + // Test getting values for a non-existing UUID that requires explicit input. + $component_source->requiresExplicitInput()->willReturn(TRUE); + $this->expectException(MissingComponentInputsException::class); + $component_inputs->getValues($non_existing_uuid); + } + +}