From ac25107a4c96d5493cce220e413216cb4253bbb6 Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 31 Oct 2024 11:00:06 +0000 Subject: [PATCH 1/2] Add extra properties to slices to cater for shared slices --- src/Document/Fragment/Factory.php | 23 ++++++++- src/Document/Fragment/Slice.php | 57 +++++++++++++++++++++-- src/Value/DataAssertionBehaviour.php | 23 +++++++++ test/Unit/Document/Fragment/SliceTest.php | 55 ++++++++++++++++++++-- test/fixture/basic-slices.json | 22 +++++++++ 5 files changed, 169 insertions(+), 11 deletions(-) diff --git a/src/Document/Fragment/Factory.php b/src/Document/Fragment/Factory.php index 5c06c4d..c4e6e0e 100644 --- a/src/Document/Fragment/Factory.php +++ b/src/Document/Fragment/Factory.php @@ -21,6 +21,7 @@ use function is_int; use function is_object; use function is_scalar; +use function is_string; use function preg_match; use function property_exists; use function strpos; @@ -251,9 +252,27 @@ private static function sliceFactory(object $data): Fragment return self::factory($value); }, $primary)); + $type = self::assertObjectPropertyIsNonEmptyString($data, 'slice_type'); + $label = self::optionalNonEmptyStringProperty($data, 'slice_label'); + $variation = self::optionalStringProperty($data, 'variation'); + $version = self::optionalNonEmptyStringProperty($data, 'version'); + $id = self::optionalNonEmptyStringProperty($data, 'id'); + + if (is_string($id) && is_string($variation)) { + return Slice::shared( + $type, + $label, + $primary, + $items, + $variation, + $version, + $id, + ); + } + return Slice::new( - self::assertObjectPropertyIsString($data, 'slice_type'), - self::optionalStringProperty($data, 'slice_label'), + $type, + $label, $primary, $items, ); diff --git a/src/Document/Fragment/Slice.php b/src/Document/Fragment/Slice.php index 8d54af8..ca638d3 100644 --- a/src/Document/Fragment/Slice.php +++ b/src/Document/Fragment/Slice.php @@ -15,28 +15,59 @@ final class Slice implements Fragment, Stringable { + /** + * @param non-empty-string $type + * @param non-empty-string|null $label + * @param non-empty-string|null $id + */ private function __construct( - private string $type, - private string|null $label, - private FragmentCollection $primary, - private FragmentCollection $items, + private readonly string $type, + private readonly string|null $label, + private readonly FragmentCollection $primary, + private readonly FragmentCollection $items, + private readonly string|null $variation, + private readonly string|null $version, + private readonly string|null $id, ) { } + /** + * @param non-empty-string $type + * @param non-empty-string|null $label + */ public static function new( string $type, string|null $label, FragmentCollection $primary, FragmentCollection $items, ): self { - return new self($type, $label, $primary, $items); + return new self($type, $label, $primary, $items, null, null, null); } + /** + * @param non-empty-string $type + * @param non-empty-string|null $label + * @param non-empty-string $id + */ + public static function shared( + string $type, + string|null $label, + FragmentCollection $primary, + FragmentCollection $items, + string $variation, + string|null $version, + string $id, + ): self { + return new self($type, $label, $primary, $items, $variation, $version, $id); + } + + /** @return non-empty-string */ public function type(): string { return $this->type; } + /** @return non-empty-string|null */ public function label(): string|null { return $this->label; @@ -52,6 +83,22 @@ public function items(): FragmentCollection return $this->items; } + public function variation(): string|null + { + return $this->variation; + } + + public function version(): string|null + { + return $this->version; + } + + /** @return non-empty-string|null */ + public function id(): string|null + { + return $this->id; + } + public function isEmpty(): bool { return $this->primary->isEmpty() && $this->items->isEmpty(); diff --git a/src/Value/DataAssertionBehaviour.php b/src/Value/DataAssertionBehaviour.php index dcc049a..a431380 100644 --- a/src/Value/DataAssertionBehaviour.php +++ b/src/Value/DataAssertionBehaviour.php @@ -39,6 +39,18 @@ private static function assertObjectPropertyIsString(object $object, string $pro return $value; } + /** @return non-empty-string */ + private static function assertObjectPropertyIsNonEmptyString(object $object, string $property): string + { + self::assertPropertyExists($object, $property); + $value = $object->{$property}; + if (! is_string($value) || $value === '') { + throw UnexpectedValue::withInvalidPropertyType($object, $property, 'non-empty-string'); + } + + return $value; + } + private static function assertObjectPropertyIsInteger(object $object, string $property): int { self::assertPropertyExists($object, $property); @@ -139,6 +151,17 @@ private static function optionalStringProperty(object $object, string $property) return $value; } + /** @return non-empty-string|null */ + private static function optionalNonEmptyStringProperty(object $object, string $property): string|null + { + $value = self::optionalStringProperty($object, $property); + if ($value === '') { + return null; + } + + return $value; + } + /** @return mixed[]|null */ private static function optionalArrayProperty(object $object, string $property): array|null { diff --git a/test/Unit/Document/Fragment/SliceTest.php b/test/Unit/Document/Fragment/SliceTest.php index 2aee2d9..72728f5 100644 --- a/test/Unit/Document/Fragment/SliceTest.php +++ b/test/Unit/Document/Fragment/SliceTest.php @@ -4,6 +4,7 @@ namespace PrismicTest\Document\Fragment; +use PHPUnit\Framework\Attributes\Depends; use Prismic\Document\Fragment\Slice; use Prismic\Document\FragmentCollection; use Prismic\Json; @@ -47,25 +48,47 @@ public function testThatAnEmptySliceCanBeFound(): Slice return $slice; } - /** @depends testThatASliceCanBeFound */ + public function testThatASharedSliceCanBeFound(): Slice + { + self::assertInstanceOf(FragmentCollection::class, $this->slices); + $slice = $this->slices->filter(static function (Slice $slice): bool { + return $slice->type() === 'shared_slice'; + })->first(); + self::assertInstanceOf(Slice::class, $slice); + + return $slice; + } + + public function testThatASharedSliceCanBeFoundWithAnEmptyVersionString(): Slice + { + self::assertInstanceOf(FragmentCollection::class, $this->slices); + $slice = $this->slices->filter(static function (Slice $slice): bool { + return $slice->type() === 'shared_slice_empty_version'; + })->first(); + self::assertInstanceOf(Slice::class, $slice); + + return $slice; + } + + #[Depends('testThatASliceCanBeFound')] public function testThatTheLabelIsTheExpectedValue(Slice $slice): void { self::assertEquals('custom-label', $slice->label()); } - /** @depends testThatASliceCanBeFound */ + #[Depends('testThatASliceCanBeFound')] public function testThatTheSliceIsNotEmpty(Slice $slice): void { self::assertFalse($slice->isEmpty()); } - /** @depends testThatAnEmptySliceCanBeFound */ + #[Depends('testThatAnEmptySliceCanBeFound')] public function testThatTheEmptySliceIsEmpty(Slice $slice): void { self::assertTrue($slice->isEmpty()); } - /** @depends testThatASliceCanBeFound */ + #[Depends('testThatASliceCanBeFound')] public function testThatToStringWillYieldTheExpectedValue(Slice $slice): void { $expect = <<<'TEXT' @@ -85,4 +108,28 @@ public function testThatTheEmptySliceIsAnEmptyStringWhenCastToAString(Slice $sli { self::assertEquals('', (string) $slice); } + + #[Depends('testThatASliceCanBeFound')] + public function testSharedSlicePropertiesAreNullForRegularSlices(Slice $slice): void + { + self::assertNull($slice->id()); + self::assertNull($slice->variation()); + self::assertNull($slice->version()); + } + + #[Depends('testThatASharedSliceCanBeFound')] + public function testSharedSlicesHaveAdditionalProperties(Slice $slice): void + { + self::assertNotEmpty($slice->id()); + self::assertNotEmpty($slice->variation()); + self::assertNotEmpty($slice->version()); + } + + #[Depends('testThatASharedSliceCanBeFoundWithAnEmptyVersionString')] + public function testSliceWithEmptyVersionWillHaveNullVersion(Slice $slice): void + { + self::assertNotEmpty($slice->id()); + self::assertNotEmpty($slice->variation()); + self::assertNull($slice->version()); + } } diff --git a/test/fixture/basic-slices.json b/test/fixture/basic-slices.json index 5fd089c..20fe5e2 100644 --- a/test/fixture/basic-slices.json +++ b/test/fixture/basic-slices.json @@ -45,6 +45,28 @@ "slice_label": null, "items": [], "primary": {} + }, + { + "slice_type": "shared_slice", + "slice_label": null, + "id": "shared_slice$62db7b7f-1191-41b4-833f-cee6d499d82e", + "variation": "default", + "version": "123", + "items": [ ], + "primary": { + "text": 1 + } + }, + { + "slice_type": "shared_slice_empty_version", + "slice_label": null, + "id": "shared_slice_empty_version$62db7b7f-1191-41b4-833f-cee6d499d82e", + "variation": "default", + "version": "", + "items": [ ], + "primary": { + "text": 1 + } } ] } From 9147136f257464e9b754230b17458b5e1b9184f7 Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 31 Oct 2024 12:03:17 +0000 Subject: [PATCH 2/2] Improve coverage --- src/Value/DataAssertionBehaviour.php | 4 +-- test/Unit/Document/Fragment/FactoryTest.php | 32 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Value/DataAssertionBehaviour.php b/src/Value/DataAssertionBehaviour.php index a431380..decdd82 100644 --- a/src/Value/DataAssertionBehaviour.php +++ b/src/Value/DataAssertionBehaviour.php @@ -155,9 +155,7 @@ private static function optionalStringProperty(object $object, string $property) private static function optionalNonEmptyStringProperty(object $object, string $property): string|null { $value = self::optionalStringProperty($object, $property); - if ($value === '') { - return null; - } + assert($value !== ''); return $value; } diff --git a/test/Unit/Document/Fragment/FactoryTest.php b/test/Unit/Document/Fragment/FactoryTest.php index 650c262..a018e19 100644 --- a/test/Unit/Document/Fragment/FactoryTest.php +++ b/test/Unit/Document/Fragment/FactoryTest.php @@ -292,4 +292,36 @@ public function testAMediaLinkWithNonNumericSizeIsExceptional(): void Factory::factory($data); } + + public function testSlicesMustProvideANonEmptySliceType(): void + { + $data = Json::decodeObject('{ + "slice_type": "", + "items":[], + "primary":{} + }'); + + $this->expectException(UnexpectedValue::class); + $this->expectExceptionMessage('Expected the object property "slice_type" to be a non-empty-string'); + + Factory::factory($data); + } + + public function testSharedSlicesMayHaveAnEmptyVersion(): void + { + $data = Json::decodeObject('{ + "slice_type": "foo", + "items":[], + "primary":{}, + "id":"bar", + "version":"", + "variation":"" + }'); + + $slice = Factory::factory($data); + self::assertInstanceOf(Fragment\Slice::class, $slice); + + self::assertNull($slice->version()); + self::assertNull($slice->variation()); + } }