diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..c95057f --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,27 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + +name: mutation test + +jobs: + mutation: + uses: php-forge/actions/.github/workflows/infection.yml@main + with: + development: true + phpstan: true + secrets: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/composer.json b/composer.json index 826163d..03dfc90 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "config": { "sort-packages": true, "allow-plugins": { + "infection/extension-installer": true, "phpstan/extension-installer": true, "yiisoft/yii2-composer": true } @@ -48,6 +49,7 @@ "scripts": { "check-dependencies": "./vendor/bin/composer-require-checker check", "ecs": "./vendor/bin/ecs --fix", + "mutation": "./vendor/bin/infection --threads=4 --ignore-msi-with-no-mutations --only-covered --min-msi=100 --min-covered-msi=100", "rector": "./vendor/bin/rector process src", "static": "./vendor/bin/phpstan --memory-limit=512M", "tests": "./vendor/bin/phpunit" diff --git a/infection.json.dist b/infection.json5 similarity index 62% rename from infection.json.dist rename to infection.json5 index d06b334..15361d9 100644 --- a/infection.json.dist +++ b/infection.json5 @@ -1,16 +1,17 @@ { - "source": { - "directories": [ - "src" - ] - }, + "$schema": "vendor/infection/infection/resources/schema.json", "logs": { "text": "php:\/\/stderr", "stryker": { "report": "main" } }, - "mutators": { - "@default": true - } + "source": { + "directories": [ + "src" + ] + }, + "phpStan": { + "configDir": "." + }, } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3ddadb0..2215bca 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ getOwner()->setAttribute($this->rightAttribute, $value + 1); } - $nodeDepthValue = $this->node?->getAttribute($this->depthAttribute) ?? 0; - $this->getOwner()->setAttribute($this->depthAttribute, $nodeDepthValue + $depth); + $nodeDepthValue = $this->node?->getAttribute($this->depthAttribute); - if ($this->treeAttribute !== false) { - $this->getOwner()->setAttribute($this->treeAttribute, $this->node?->getAttribute($this->treeAttribute)); + if ($nodeDepthValue !== null) { + $this->getOwner()->setAttribute($this->depthAttribute, $nodeDepthValue + $depth); + } + + if ($this->treeAttribute !== false && $this->node !== null) { + $this->getOwner()->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute)); } - $this->shiftLeftRightAttribute($value ?? 0, 2); + if ($value !== null) { + $this->shiftLeftRightAttribute($value, 2); + } } /** @@ -1225,7 +1230,7 @@ protected function moveNode(ActiveRecord $node, int $value, int $depth): void ); } - $this->shiftLeftRightAttribute($rightValue + 1, -$delta); + $this->shiftLeftRightAttribute($rightValue, -$delta); } else { $leftAttribute = $db->quoteColumnName($this->leftAttribute); $rightAttribute = $db->quoteColumnName($this->rightAttribute); diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index d311816..8c469bf 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -678,7 +678,7 @@ public function testThrowExceptionWhenAppendToTargetIsSame(): void $this->expectException(Exception::class); $this->expectExceptionMessage('Can not move a node when the target node is same.'); - $node->appendTo($childOfNode); + $node->appendTo($childOfNode, false); } public function testThrowExceptionWhenAppendToTargetIsChild(): void @@ -1073,7 +1073,7 @@ public function testThrowExceptionWhenInsertAfterTargetIsNewRecord(): void $this->expectException(Exception::class); $this->expectExceptionMessage('Can not move a node when the target node is new record.'); - $node->insertAfter(new Tree()); + $node->insertAfter(new Tree(), false); } public function testThrowExceptionWhenInsertAfterTargetIsSame(): void @@ -1620,4 +1620,152 @@ public function makeRoot(): bool $node->makeRoot(); } + + public function testAppendNodeToExistingChildNode(): void + { + $this->generateFixtureTree(); + + $node = new Tree(['name' => 'New node']); + + $childOfNode = Tree::findOne(2); + + self::assertNotNull( + $childOfNode, + 'Child node with ID \'2\' should exist before appending.', + ); + + self::assertTrue( + $node->appendTo($childOfNode), + 'Appending a new node to the existing child node should return \'true\'.', + ); + } + + public function testAppendToSetsCorrectDepthForNewNode(): void + { + $this->createDatabase(); + + $root = new Tree(['name' => 'Root']); + + $root->makeRoot(); + + $parent = new Tree(['name' => 'Parent']); + + $parent->appendTo($root); + + $parent->refresh(); + + self::assertEquals(1, $parent->depth); + + $child = new Tree(['name' => 'Child']); + + $child->appendTo($parent); + + $child->refresh(); + + self::assertEquals(2, $child->depth); + + $grandchild = new Tree(['name' => 'Grandchild']); + + $grandchild->appendTo($child); + $grandchild->refresh(); + + self::assertEquals(3, $grandchild->depth); + } + + public function testAppendToAndPrependToHaveSameDepthBehavior(): void + { + $this->createDatabase(); + + $root = new Tree(['name' => 'Root']); + + $root->makeRoot(); + + $parent = new Tree(['name' => 'Parent']); + + $parent->appendTo($root); + + $child1 = new Tree(['name' => 'Child1']); + + $child1->prependTo($parent); + + $child2 = new Tree(['name' => 'Child2']); + + $child2->appendTo($parent); + + $child1->refresh(); + + self::assertEquals($child1->depth, $child2->depth); + + $child2->refresh(); + + self::assertEquals(2, $child1->depth); + } + + public function testAppendToSetsCorrectDepthForMultipleTree(): void + { + $this->createDatabase(); + + $root = new MultipleTree(['name' => 'Root']); + + $root->makeRoot(); + + $level1 = new MultipleTree(['name' => 'Level1']); + + $level1->appendTo($root); + + $level2 = new MultipleTree(['name' => 'Level2']); + + $level2->appendTo($level1); + + $level3 = new MultipleTree(['name' => 'Level3']); + + $level3->appendTo($level2); + + $level1->refresh(); + + self::assertEquals(1, $level1->depth); + + $level2->refresh(); + + self::assertEquals(2, $level2->depth); + + $level3->refresh(); + + self::assertEquals(3, $level3->depth); + } + + public function testDepthDiffersBetweenSiblingsAndChildren(): void + { + $this->createDatabase(); + + $root = new Tree(['name' => 'Root']); + + $root->makeRoot(); + + $parent = new Tree(['name' => 'Parent']); + + $parent->appendTo($root); + + $sibling = new Tree(['name' => 'Sibling']); + + $sibling->insertAfter($parent); + + $child = new Tree(['name' => 'Child']); + + $child->appendTo($parent); + + $parent->refresh(); + + self::assertEquals($parent->depth, $sibling->depth); + self::assertEquals($parent->depth + 1, $child->depth); + self::assertEquals(1, $parent->depth); + + $sibling->refresh(); + + self::assertEquals(1, $sibling->depth); + + $child->refresh(); + + self::assertEquals(2, $child->depth); + } }