diff --git a/composer.json b/composer.json index f07d3222f..192eea6d9 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "phpstan/phpstan-phpunit": "1.4.2 || 2.0.4", "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.3", "phpunit/phpunit": "8.5.41", + "rawr/cross-data-providers": "2.4.0", "rector/rector": "1.2.10 || 2.0.7", "rector/type-perfect": "1.0.0 || 2.0.2" }, diff --git a/src/Comment/CommentContainer.php b/src/Comment/CommentContainer.php new file mode 100644 index 000000000..87f6ff46c --- /dev/null +++ b/src/Comment/CommentContainer.php @@ -0,0 +1,44 @@ + + */ + protected $comments = []; + + /** + * @param list $comments + */ + public function addComments(array $comments): void + { + $this->comments = \array_merge($this->comments, $comments); + } + + /** + * @return list + */ + public function getComments(): array + { + return $this->comments; + } + + /** + * @param list $comments + */ + public function setComments(array $comments): void + { + $this->comments = $comments; + } +} diff --git a/src/Comment/Commentable.php b/src/Comment/Commentable.php index e6eb6a0be..5f28021de 100644 --- a/src/Comment/Commentable.php +++ b/src/Comment/Commentable.php @@ -4,6 +4,9 @@ namespace Sabberworm\CSS\Comment; +/** + * A standard implementation of this interface is available in the `CommentContainer` trait. + */ interface Commentable { /** diff --git a/tests/Unit/Comment/CommentContainerTest.php b/tests/Unit/Comment/CommentContainerTest.php new file mode 100644 index 000000000..a29691b9d --- /dev/null +++ b/tests/Unit/Comment/CommentContainerTest.php @@ -0,0 +1,232 @@ +subject = new ConcreteCommentContainer(); + } + + /** + * @test + */ + public function getCommentsInitiallyReturnsEmptyArray(): void + { + self::assertSame([], $this->subject->getComments()); + } + + /** + * @return array}> + */ + public function provideCommentArray(): array + { + return [ + 'no comment' => [[]], + 'one comment' => [[new Comment('Is this really a spoon?')]], + 'two comments' => [[ + new Comment('I’m a teapot.'), + new Comment('I’m a cafetière.'), + ]], + ]; + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function addCommentsOnVirginContainerAddsCommentsProvided(array $comments): void + { + $this->subject->addComments($comments); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function addCommentsWithEmptyArrayKeepsOriginalCommentsUnchanged(array $comments): void + { + $this->subject->setComments($comments); + + $this->subject->addComments([]); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @return array}> + */ + public function provideAlternativeCommentArray(): array + { + return [ + 'no comment' => [[]], + 'one comment' => [[new Comment('Can I eat it with my hands?')]], + 'two comments' => [[ + new Comment('I’m a beer barrel.'), + new Comment('I’m a vineyard.'), + ]], + ]; + } + + /** + * @return array}> + */ + public function provideAlternativeNonemptyCommentArray(): array + { + $data = $this->provideAlternativeCommentArray(); + + unset($data['no comment']); + + return $data; + } + + /** + * This provider crosses two comment arrays (0, 1 or 2 comments) with different comments, + * so that all combinations can be tested. + * + * @return array, 1: list}> + */ + public function provideTwoDistinctCommentArrays(): array + { + return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeCommentArray()); + } + + /** + * @return array, 1: non-empty-list}> + */ + public function provideTwoDistinctCommentArraysWithSecondNonempty(): array + { + return DataProviders::cross($this->provideCommentArray(), $this->provideAlternativeNonemptyCommentArray()); + } + + private static function createContainsContstraint(Comment $comment): TraversableContains + { + return new TraversableContains($comment); + } + + /** + * @param non-empty-list $comments + * + * @return non-empty-list + */ + private static function createContainsContstraints(array $comments): array + { + return \array_map([self::class, 'createContainsContstraint'], $comments); + } + + /** + * @test + * + * @param list $commentsToAdd + * @param non-empty-list $originalComments + * + * @dataProvider provideTwoDistinctCommentArraysWithSecondNonempty + */ + public function addCommentsKeepsOriginalComments(array $commentsToAdd, array $originalComments): void + { + $this->subject->setComments($originalComments); + + $this->subject->addComments($commentsToAdd); + + self::assertThat( + $this->subject->getComments(), + LogicalAnd::fromConstraints(...self::createContainsContstraints($originalComments)) + ); + } + + /** + * @test + * + * @param list $originalComments + * @param non-empty-list $commentsToAdd + * + * @dataProvider provideTwoDistinctCommentArraysWithSecondNonempty + */ + public function addCommentsAfterCommentsSetAddsCommentsProvided(array $originalComments, array $commentsToAdd): void + { + $this->subject->setComments($originalComments); + + $this->subject->addComments($commentsToAdd); + + self::assertThat( + $this->subject->getComments(), + LogicalAnd::fromConstraints(...self::createContainsContstraints($commentsToAdd)) + ); + } + + /** + * @test + * + * @param non-empty-list $comments + * + * @dataProvider provideAlternativeNonemptyCommentArray + */ + public function addCommentsAppends(array $comments): void + { + $firstComment = new Comment('I must be first!'); + $this->subject->setComments([$firstComment]); + + $this->subject->addComments($comments); + + $result = $this->subject->getComments(); + self::assertNotEmpty($result); + self::assertSame($firstComment, $result[0]); + } + + /** + * @test + * + * @param list $comments + * + * @dataProvider provideCommentArray + */ + public function setCommentsOnVirginContainerSetsCommentsProvided(array $comments): void + { + $this->subject->setComments($comments); + + self::assertSame($comments, $this->subject->getComments()); + } + + /** + * @test + * + * @param list $originalComments + * @param list $commentsToSet + * + * @dataProvider provideTwoDistinctCommentArrays + */ + public function setCommentsReplacesWithCommentsProvided(array $originalComments, array $commentsToSet): void + { + $this->subject->setComments($originalComments); + + $this->subject->setComments($commentsToSet); + + self::assertSame($commentsToSet, $this->subject->getComments()); + } +} diff --git a/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php b/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php new file mode 100644 index 000000000..39f6ec37f --- /dev/null +++ b/tests/Unit/Comment/Fixtures/ConcreteCommentContainer.php @@ -0,0 +1,13 @@ +