From c3916ea707364c8bfa7594f127921aa4c51b1924 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 15:00:54 -0400 Subject: [PATCH 01/28] feat: Add mutation testing configuration and update `PHPUnit` schema location. --- .github/workflows/mutation.yml | 24 ++++++++++++++++++++++++ composer.json | 3 +++ infection.json.dist => infection.json5 | 15 +++++++-------- phpunit.xml.dist | 2 +- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/mutation.yml rename infection.json.dist => infection.json5 (69%) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..0c66610 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,24 @@ +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 + secrets: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/composer.json b/composer.json index 826163d..35d3f24 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ }, "require-dev": { "ext-simplexml": "*", + "infection/infection": "^0.27|^0.29", "maglnet/composer-require-checker": "^4.1", "phpstan/extension-installer": "^1.4", "phpstan/phpstan-strict-rules": "^2.0.3", @@ -41,6 +42,7 @@ "config": { "sort-packages": true, "allow-plugins": { + "infection/extension-installer": true, "phpstan/extension-installer": true, "yiisoft/yii2-composer": true } @@ -48,6 +50,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 69% rename from infection.json.dist rename to infection.json5 index d06b334..b47e1f0 100644 --- a/infection.json.dist +++ b/infection.json5 @@ -1,16 +1,15 @@ { - "source": { - "directories": [ - "src" - ] - }, + "$schema": "vendor/infection/infection/resources/schema.json", "logs": { "text": "php:\/\/stderr", "stryker": { "report": "main" } }, - "mutators": { - "@default": true - } + "source": { + "directories": [ + "src" + ] + }, + "timeout": 45 } 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 @@ Date: Sat, 28 Jun 2025 15:28:33 -0400 Subject: [PATCH 02/28] test: Add test for shifting left and right attributes after deleting a leaf node. --- tests/NestedSetsBehaviorTest.php | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index d311816..b3ec09b 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1620,4 +1620,132 @@ public function makeRoot(): bool $node->makeRoot(); } + + public function testShiftLeftRightAttributesAfterDeleteLeafNode(): void + { + $this->generateFixtureTree(); + + $nodeToDelete = Tree::findOne(4); + + self::assertNotNull( + $nodeToDelete, + 'Node with ID \'4\' should exist before deletion.', + ); + self::assertTrue( + $nodeToDelete->isLeaf(), + 'Node with ID \'4\' should be a leaf node before deletion.', + ); + self::assertEquals( + 4, + $nodeToDelete->getAttribute('lft'), + 'Node with ID \'4\' should have left value \'4\' before deletion.', + ); + self::assertEquals( + 5, + $nodeToDelete->getAttribute('rgt'), + 'Node with ID \'4\' should have right value \'5\' before deletion.', + ); + + $node5 = Tree::findOne(5); + + self::assertNotNull( + $node5, + 'Node with ID \'5\' should exist before deletion.', + ); + self::assertEquals( + 6, + $node5->getAttribute('lft'), + 'Node with ID \'5\' should have left value \'6\' before deletion.', + ); + self::assertEquals( + 7, + $node5->getAttribute('rgt'), + 'Node with ID \'5\' should have right value \'7\' before deletion.', + ); + + $node6 = Tree::findOne(6); + + self::assertNotNull( + $node6, + 'Node with ID \'6\' should exist before deletion.', + ); + self::assertEquals( + 9, + $node6->getAttribute('lft'), + 'Node with ID \'6\' should have left value \'9\' before deletion.', + ); + self::assertEquals( + 14, + $node6->getAttribute('rgt'), + 'Node with ID \'6\' should have right value \'14\' before deletion.', + ); + + $node9 = Tree::findOne(9); + + self::assertNotNull( + $node9, + 'Node with ID \'9\' should exist before deletion.', + ); + self::assertEquals( + 16, + $node9->getAttribute('lft'), + 'Node with ID \'9\' should have left value \'16\' before deletion.', + ); + self::assertEquals( + 29, + $node9->getAttribute('rgt'), + 'Node with ID \'9\' should have right value \'29\' before deletion.', + ); + + $result = $nodeToDelete->delete(); + + self::assertEquals( + 1, + $result, + 'Deleting the leaf node with ID \'4\' should affect exactly one row.', + ); + self::assertNull( + Tree::findOne(4), + 'Node with ID \'4\' should not exist after deletion.', + ); + + $node5->refresh(); + + self::assertEquals( + 4, + $node5->getAttribute('lft'), + 'Node with ID \'5\' should have left value \'4\' after shifting.', + ); + self::assertEquals( + 5, + $node5->getAttribute('rgt'), + 'Node with ID \'5\' should have right value \'5\' after shifting.', + ); + + $node6->refresh(); + + self::assertEquals( + 7, + $node6->getAttribute('lft'), + 'Node with ID \'6\' should have left value \'7\' after shifting.', + ); + self::assertEquals( + 12, + $node6->getAttribute('rgt'), + 'Node with ID \'6\' should have right value \'12\' after shifting.', + ); + + $node9->refresh(); + + self::assertEquals( + 14, + $node9->getAttribute('lft'), + 'Node with ID \'9\' should have left value \'14\' after shifting.', + ); + self::assertEquals( + 27, + $node9->getAttribute('rgt'), + 'Node with ID \'9\' should have right value \'27\' after shifting.', + ); + } } From 1dbbb1732118a5adcc5cbfe912c998f82d0a902e Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 15:45:16 -0400 Subject: [PATCH 03/28] test: Rename test for shifting left and right attributes after deleting a leaf node for clarity. --- tests/NestedSetsBehaviorTest.php | 126 ++++++------------------------- 1 file changed, 24 insertions(+), 102 deletions(-) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index b3ec09b..c45d324 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1621,131 +1621,53 @@ public function makeRoot(): bool $node->makeRoot(); } - public function testShiftLeftRightAttributesAfterDeleteLeafNode(): void + public function testAfterDeleteShiftsLeftRightAttributesCorrectly(): void { $this->generateFixtureTree(); $nodeToDelete = Tree::findOne(4); - self::assertNotNull( - $nodeToDelete, - 'Node with ID \'4\' should exist before deletion.', - ); - self::assertTrue( - $nodeToDelete->isLeaf(), - 'Node with ID \'4\' should be a leaf node before deletion.', - ); - self::assertEquals( - 4, - $nodeToDelete->getAttribute('lft'), - 'Node with ID \'4\' should have left value \'4\' before deletion.', - ); - self::assertEquals( - 5, - $nodeToDelete->getAttribute('rgt'), - 'Node with ID \'4\' should have right value \'5\' before deletion.', - ); + self::assertNotNull($nodeToDelete); + self::assertTrue($nodeToDelete->isLeaf()); + self::assertEquals(4, $nodeToDelete->getAttribute('lft')); + self::assertEquals(5, $nodeToDelete->getAttribute('rgt')); $node5 = Tree::findOne(5); - self::assertNotNull( - $node5, - 'Node with ID \'5\' should exist before deletion.', - ); - self::assertEquals( - 6, - $node5->getAttribute('lft'), - 'Node with ID \'5\' should have left value \'6\' before deletion.', - ); - self::assertEquals( - 7, - $node5->getAttribute('rgt'), - 'Node with ID \'5\' should have right value \'7\' before deletion.', - ); + self::assertNotNull($node5); + self::assertEquals(6, $node5->getAttribute('lft')); + self::assertEquals(7, $node5->getAttribute('rgt')); $node6 = Tree::findOne(6); - self::assertNotNull( - $node6, - 'Node with ID \'6\' should exist before deletion.', - ); - self::assertEquals( - 9, - $node6->getAttribute('lft'), - 'Node with ID \'6\' should have left value \'9\' before deletion.', - ); - self::assertEquals( - 14, - $node6->getAttribute('rgt'), - 'Node with ID \'6\' should have right value \'14\' before deletion.', - ); + self::assertNotNull($node6); + self::assertEquals(9, $node6->getAttribute('lft')); + self::assertEquals(14, $node6->getAttribute('rgt')); - $node9 = Tree::findOne(9); + $node3 = Tree::findOne(3); - self::assertNotNull( - $node9, - 'Node with ID \'9\' should exist before deletion.', - ); - self::assertEquals( - 16, - $node9->getAttribute('lft'), - 'Node with ID \'9\' should have left value \'16\' before deletion.', - ); - self::assertEquals( - 29, - $node9->getAttribute('rgt'), - 'Node with ID \'9\' should have right value \'29\' before deletion.', - ); + self::assertNotNull($node3); + self::assertEquals(3, $node3->getAttribute('lft')); + self::assertEquals(8, $node3->getAttribute('rgt')); $result = $nodeToDelete->delete(); - self::assertEquals( - 1, - $result, - 'Deleting the leaf node with ID \'4\' should affect exactly one row.', - ); - self::assertNull( - Tree::findOne(4), - 'Node with ID \'4\' should not exist after deletion.', - ); + self::assertEquals(1, $result); + self::assertNull(Tree::findOne(4)); $node5->refresh(); - self::assertEquals( - 4, - $node5->getAttribute('lft'), - 'Node with ID \'5\' should have left value \'4\' after shifting.', - ); - self::assertEquals( - 5, - $node5->getAttribute('rgt'), - 'Node with ID \'5\' should have right value \'5\' after shifting.', - ); + self::assertEquals(4, $node5->getAttribute('lft')); + self::assertEquals(5, $node5->getAttribute('rgt')); $node6->refresh(); - self::assertEquals( - 7, - $node6->getAttribute('lft'), - 'Node with ID \'6\' should have left value \'7\' after shifting.', - ); - self::assertEquals( - 12, - $node6->getAttribute('rgt'), - 'Node with ID \'6\' should have right value \'12\' after shifting.', - ); + self::assertEquals(7, $node6->getAttribute('lft')); + self::assertEquals(12, $node6->getAttribute('rgt')); - $node9->refresh(); + $node3->refresh(); - self::assertEquals( - 14, - $node9->getAttribute('lft'), - 'Node with ID \'9\' should have left value \'14\' after shifting.', - ); - self::assertEquals( - 27, - $node9->getAttribute('rgt'), - 'Node with ID \'9\' should have right value \'27\' after shifting.', - ); + self::assertEquals(3, $node3->getAttribute('lft')); + self::assertEquals(6, $node3->getAttribute('rgt')); } } From d039c60ff3f2e4575e0e39017f0a4ead34e88c55 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 16:15:46 -0400 Subject: [PATCH 04/28] feat: Update mutation testing configuration and clean up JSON files. --- .github/workflows/mutation.yml | 3 +++ composer.json | 2 +- infection.json5 | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 0c66610..b3d7530 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -20,5 +20,8 @@ name: mutation test jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main + with: + command: phpstan-mutant-killer-infection-runner + composer-command: composer require --dev phpstan/mutant-killer-infection-runner secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/composer.json b/composer.json index 35d3f24..0f59fd4 100644 --- a/composer.json +++ b/composer.json @@ -9,13 +9,13 @@ "behavior" ], "license": "BSD-3-Clause", + "minimum-stability": "dev", "require": { "php": ">=8.1", "yiisoft/yii2": "^2.0.53|^22" }, "require-dev": { "ext-simplexml": "*", - "infection/infection": "^0.27|^0.29", "maglnet/composer-require-checker": "^4.1", "phpstan/extension-installer": "^1.4", "phpstan/phpstan-strict-rules": "^2.0.3", diff --git a/infection.json5 b/infection.json5 index b47e1f0..d6406fa 100644 --- a/infection.json5 +++ b/infection.json5 @@ -10,6 +10,5 @@ "directories": [ "src" ] - }, - "timeout": 45 + } } From fd62276e2ed87b7e117a1e76a43b9fe722886ce0 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 16:41:51 -0400 Subject: [PATCH 05/28] feat: Update mutation testing configuration and remove minimum stability requirement from `composer.json`. --- .github/workflows/mutation.yml | 4 ++-- composer.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index b3d7530..c7ade8a 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - command: phpstan-mutant-killer-infection-runner - composer-command: composer require --dev phpstan/mutant-killer-infection-runner + command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered + composer-command: composer require --dev infection/infection:dev-master secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/composer.json b/composer.json index 0f59fd4..03dfc90 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,6 @@ "behavior" ], "license": "BSD-3-Clause", - "minimum-stability": "dev", "require": { "php": ">=8.1", "yiisoft/yii2": "^2.0.53|^22" From d885491c2d460c7099e0746f689d4d44dbbe83ad Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 16:51:29 -0400 Subject: [PATCH 06/28] test: Enhance deletion tests for tree nodes and verify attribute shifts. --- tests/NestedSetsBehaviorTest.php | 119 +++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 22 deletions(-) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index c45d324..d9beec7 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1627,47 +1627,122 @@ public function testAfterDeleteShiftsLeftRightAttributesCorrectly(): void $nodeToDelete = Tree::findOne(4); - self::assertNotNull($nodeToDelete); - self::assertTrue($nodeToDelete->isLeaf()); - self::assertEquals(4, $nodeToDelete->getAttribute('lft')); - self::assertEquals(5, $nodeToDelete->getAttribute('rgt')); + self::assertNotNull($nodeToDelete, 'Node 4 should exist for deletion test'); + self::assertTrue($nodeToDelete->isLeaf(), 'Node 4 should be a leaf node'); + self::assertEquals(4, $nodeToDelete->getAttribute('lft'), 'Node 4 should have lft=4'); + self::assertEquals(5, $nodeToDelete->getAttribute('rgt'), 'Node 4 should have rgt=5'); $node5 = Tree::findOne(5); + $node6 = Tree::findOne(6); + $node3 = Tree::findOne(3); self::assertNotNull($node5); + self::assertNotNull($node6); + self::assertNotNull($node3); + self::assertEquals(6, $node5->getAttribute('lft')); self::assertEquals(7, $node5->getAttribute('rgt')); - - $node6 = Tree::findOne(6); - - self::assertNotNull($node6); self::assertEquals(9, $node6->getAttribute('lft')); self::assertEquals(14, $node6->getAttribute('rgt')); - - $node3 = Tree::findOne(3); - - self::assertNotNull($node3); self::assertEquals(3, $node3->getAttribute('lft')); self::assertEquals(8, $node3->getAttribute('rgt')); $result = $nodeToDelete->delete(); + self::assertEquals(1, $result, 'Delete should affect exactly 1 row'); - self::assertEquals(1, $result); - self::assertNull(Tree::findOne(4)); + self::assertNull(Tree::findOne(4), 'Node 4 should no longer exist'); $node5->refresh(); + $node6->refresh(); + $node3->refresh(); - self::assertEquals(4, $node5->getAttribute('lft')); - self::assertEquals(5, $node5->getAttribute('rgt')); + self::assertEquals(4, $node5->getAttribute('lft'), 'Node 5 left should shift from 6 to 4'); + self::assertEquals(5, $node5->getAttribute('rgt'), 'Node 5 right should shift from 7 to 5'); - $node6->refresh(); + self::assertEquals(7, $node6->getAttribute('lft'), 'Node 6 left should shift from 9 to 7'); + self::assertEquals(12, $node6->getAttribute('rgt'), 'Node 6 right should shift from 14 to 12'); - self::assertEquals(7, $node6->getAttribute('lft')); - self::assertEquals(12, $node6->getAttribute('rgt')); + self::assertEquals(3, $node3->getAttribute('lft'), 'Node 3 left should remain 3'); + self::assertEquals(6, $node3->getAttribute('rgt'), 'Node 3 right should shift from 8 to 6'); + } - $node3->refresh(); + public function testAfterDeleteShiftStartsAtCorrectPosition(): void + { + $this->generateFixtureTree(); - self::assertEquals(3, $node3->getAttribute('lft')); - self::assertEquals(6, $node3->getAttribute('rgt')); + $nodeToDelete = Tree::findOne(11); + + self::assertNotNull($nodeToDelete); + self::assertEquals(18, $nodeToDelete->getAttribute('lft')); + self::assertEquals(19, $nodeToDelete->getAttribute('rgt')); + + $node12 = Tree::findOne(12); + $node10 = Tree::findOne(10); + $node13 = Tree::findOne(13); + + self::assertNotNull($node12); + self::assertNotNull($node10); + self::assertNotNull($node13); + + self::assertEquals(20, $node12->getAttribute('lft'), 'Node 12 should start at 20'); + self::assertEquals(21, $node12->getAttribute('rgt'), 'Node 12 should end at 21'); + self::assertEquals(17, $node10->getAttribute('lft'), 'Node 10 should start at 17'); + self::assertEquals(22, $node10->getAttribute('rgt'), 'Node 10 should end at 22'); + self::assertEquals(23, $node13->getAttribute('lft'), 'Node 13 should start at 23'); + self::assertEquals(28, $node13->getAttribute('rgt'), 'Node 13 should end at 28'); + + $result = $nodeToDelete->delete(); + self::assertEquals(1, $result); + self::assertNull(Tree::findOne(11), 'Node 11 should be deleted'); + + $node12->refresh(); + $node10->refresh(); + $node13->refresh(); + + self::assertEquals(18, $node12->getAttribute('lft'), 'Node 12 left should shift from 20 to 18'); + self::assertEquals(19, $node12->getAttribute('rgt'), 'Node 12 right should shift from 21 to 19'); + self::assertEquals(17, $node10->getAttribute('lft'), 'Node 10 left should remain 17'); + self::assertEquals(20, $node10->getAttribute('rgt'), 'Node 10 right should shift from 22 to 20'); + self::assertEquals(21, $node13->getAttribute('lft'), 'Node 13 left should shift from 23 to 21'); + self::assertEquals(26, $node13->getAttribute('rgt'), 'Node 13 right should shift from 28 to 26'); + self::assertLessThan( + $node10->getAttribute('rgt'), + $node12->getAttribute('rgt'), + 'Child node 12 must be contained within parent node 10' + ); + self::assertGreaterThan( + $node10->getAttribute('lft'), + $node12->getAttribute('lft'), + 'Child node 12 must be contained within parent node 10' + ); + } + + public function testDeleteWithChildrenShiftsCorrectly(): void + { + $this->generateFixtureTree(); + + $nodeToDelete = Tree::findOne(3); + + self::assertNotNull($nodeToDelete); + self::assertEquals(3, $nodeToDelete->getAttribute('lft')); + self::assertEquals(8, $nodeToDelete->getAttribute('rgt')); + + $node6 = Tree::findOne(6); + + self::assertNotNull($node6); + self::assertEquals(9, $node6->getAttribute('lft')); + self::assertEquals(14, $node6->getAttribute('rgt')); + + $result = $nodeToDelete->deleteWithChildren(); + + self::assertEquals(3, $result); + self::assertNull(Tree::findOne(3)); + self::assertNull(Tree::findOne(4)); + self::assertNull(Tree::findOne(5)); + + $node6->refresh(); + + self::assertEquals(3, $node6->getAttribute('lft')); + self::assertEquals(8, $node6->getAttribute('rgt')); } } From d6d891ba1358923376f19392b7ce78c60e8d9b45 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 16:58:25 -0400 Subject: [PATCH 07/28] feat: Update mutation testing workflow to include static analysis command. --- .github/workflows/mutation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index c7ade8a..9ff3b73 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -22,6 +22,8 @@ jobs: uses: php-forge/actions/.github/workflows/infection.yml@main with: command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered - composer-command: composer require --dev infection/infection:dev-master + composer-command: | + composer require --dev infection/infection:dev-master + vendor/bin/phpstan --configuration=phpstan.neon analyse secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From daf60d726ca8afba4cb19802a642512b1f7023df Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 17:01:44 -0400 Subject: [PATCH 08/28] fix: Simplify composer command in mutation testing workflow. --- .github/workflows/mutation.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 9ff3b73..d08baea 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -22,8 +22,6 @@ jobs: uses: php-forge/actions/.github/workflows/infection.yml@main with: command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered - composer-command: | - composer require --dev infection/infection:dev-master - vendor/bin/phpstan --configuration=phpstan.neon analyse + composer-command: composer require --dev infection/infection:dev-master && vendor/bin/phpstan --configuration=phpstan.neon analyse secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From e6af6b2198a757605b6f25f2014b5cc216c30411 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 17:22:29 -0400 Subject: [PATCH 09/28] feat: Add phpStan configuration to `infection.json5`. --- infection.json5 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infection.json5 b/infection.json5 index d6406fa..15361d9 100644 --- a/infection.json5 +++ b/infection.json5 @@ -10,5 +10,8 @@ "directories": [ "src" ] - } + }, + "phpStan": { + "configDir": "." + }, } From 6c1c41a6c1d9b32d7b444c210c9a280ec1f23382 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 17:29:10 -0400 Subject: [PATCH 10/28] feat: Add log verbosity option to mutation testing command. --- .github/workflows/mutation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index d08baea..1741a2a 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered + command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered --log-verbosity=all composer-command: composer require --dev infection/infection:dev-master && vendor/bin/phpstan --configuration=phpstan.neon analyse secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From af317f9f0b80026dbb96cf2249caa84a0d8174a9 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 17:45:55 -0400 Subject: [PATCH 11/28] test: Refactor deletion tests to improve clarity and accuracy of node attribute shifts. --- tests/NestedSetsBehaviorTest.php | 211 +++++++++++++++---------------- 1 file changed, 101 insertions(+), 110 deletions(-) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index d9beec7..8fc6682 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1621,128 +1621,119 @@ public function makeRoot(): bool $node->makeRoot(); } - public function testAfterDeleteShiftsLeftRightAttributesCorrectly(): void + public function testDeleteNodeDoesNotAffectNodeWithLeftEqualToDeletedRight(): void { - $this->generateFixtureTree(); - - $nodeToDelete = Tree::findOne(4); - - self::assertNotNull($nodeToDelete, 'Node 4 should exist for deletion test'); - self::assertTrue($nodeToDelete->isLeaf(), 'Node 4 should be a leaf node'); - self::assertEquals(4, $nodeToDelete->getAttribute('lft'), 'Node 4 should have lft=4'); - self::assertEquals(5, $nodeToDelete->getAttribute('rgt'), 'Node 4 should have rgt=5'); - - $node5 = Tree::findOne(5); - $node6 = Tree::findOne(6); - $node3 = Tree::findOne(3); - - self::assertNotNull($node5); - self::assertNotNull($node6); - self::assertNotNull($node3); - - self::assertEquals(6, $node5->getAttribute('lft')); - self::assertEquals(7, $node5->getAttribute('rgt')); - self::assertEquals(9, $node6->getAttribute('lft')); - self::assertEquals(14, $node6->getAttribute('rgt')); - self::assertEquals(3, $node3->getAttribute('lft')); - self::assertEquals(8, $node3->getAttribute('rgt')); - - $result = $nodeToDelete->delete(); - self::assertEquals(1, $result, 'Delete should affect exactly 1 row'); - - self::assertNull(Tree::findOne(4), 'Node 4 should no longer exist'); - - $node5->refresh(); - $node6->refresh(); - $node3->refresh(); - - self::assertEquals(4, $node5->getAttribute('lft'), 'Node 5 left should shift from 6 to 4'); - self::assertEquals(5, $node5->getAttribute('rgt'), 'Node 5 right should shift from 7 to 5'); - - self::assertEquals(7, $node6->getAttribute('lft'), 'Node 6 left should shift from 9 to 7'); - self::assertEquals(12, $node6->getAttribute('rgt'), 'Node 6 right should shift from 14 to 12'); + $this->createDatabase(); - self::assertEquals(3, $node3->getAttribute('lft'), 'Node 3 left should remain 3'); - self::assertEquals(6, $node3->getAttribute('rgt'), 'Node 3 right should shift from 8 to 6'); + $root = new Tree(['name' => 'Root']); + $root->makeRoot(); + + $node1 = new Tree(['name' => 'Node1']); + $node1->appendTo($root); + + $child1 = new Tree(['name' => 'Child1']); + $child1->appendTo($node1); + + $node2 = new Tree(['name' => 'Node2']); + $node2->appendTo($root); + + $child2 = new Tree(['name' => 'Child2']); + $child2->appendTo($node2); + + $root->refresh(); + $node1->refresh(); + $child1->refresh(); + $node2->refresh(); + $child2->refresh(); + + self::assertEquals(1, $root->lft); + self::assertEquals(10, $root->rgt); + self::assertEquals(2, $node1->lft); + self::assertEquals(5, $node1->rgt); + self::assertEquals(3, $child1->lft); + self::assertEquals(4, $child1->rgt); + self::assertEquals(6, $node2->lft); + self::assertEquals(9, $node2->rgt); + self::assertEquals(7, $child2->lft); + self::assertEquals(8, $child2->rgt); + + $node1->deleteWithChildren(); + + $root->refresh(); + $node2->refresh(); + $child2->refresh(); + + self::assertEquals(1, $root->lft); + self::assertEquals(6, $root->rgt); + self::assertEquals(2, $node2->lft); + self::assertEquals(5, $node2->rgt); + self::assertEquals(3, $child2->lft); + self::assertEquals(4, $child2->rgt); } - public function testAfterDeleteShiftStartsAtCorrectPosition(): void + public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void { - $this->generateFixtureTree(); - - $nodeToDelete = Tree::findOne(11); - - self::assertNotNull($nodeToDelete); - self::assertEquals(18, $nodeToDelete->getAttribute('lft')); - self::assertEquals(19, $nodeToDelete->getAttribute('rgt')); - - $node12 = Tree::findOne(12); - $node10 = Tree::findOne(10); - $node13 = Tree::findOne(13); - - self::assertNotNull($node12); - self::assertNotNull($node10); - self::assertNotNull($node13); - - self::assertEquals(20, $node12->getAttribute('lft'), 'Node 12 should start at 20'); - self::assertEquals(21, $node12->getAttribute('rgt'), 'Node 12 should end at 21'); - self::assertEquals(17, $node10->getAttribute('lft'), 'Node 10 should start at 17'); - self::assertEquals(22, $node10->getAttribute('rgt'), 'Node 10 should end at 22'); - self::assertEquals(23, $node13->getAttribute('lft'), 'Node 13 should start at 23'); - self::assertEquals(28, $node13->getAttribute('rgt'), 'Node 13 should end at 28'); - - $result = $nodeToDelete->delete(); - self::assertEquals(1, $result); - self::assertNull(Tree::findOne(11), 'Node 11 should be deleted'); - - $node12->refresh(); - $node10->refresh(); - $node13->refresh(); - - self::assertEquals(18, $node12->getAttribute('lft'), 'Node 12 left should shift from 20 to 18'); - self::assertEquals(19, $node12->getAttribute('rgt'), 'Node 12 right should shift from 21 to 19'); - self::assertEquals(17, $node10->getAttribute('lft'), 'Node 10 left should remain 17'); - self::assertEquals(20, $node10->getAttribute('rgt'), 'Node 10 right should shift from 22 to 20'); - self::assertEquals(21, $node13->getAttribute('lft'), 'Node 13 left should shift from 23 to 21'); - self::assertEquals(26, $node13->getAttribute('rgt'), 'Node 13 right should shift from 28 to 26'); - self::assertLessThan( - $node10->getAttribute('rgt'), - $node12->getAttribute('rgt'), - 'Child node 12 must be contained within parent node 10' - ); - self::assertGreaterThan( - $node10->getAttribute('lft'), - $node12->getAttribute('lft'), - 'Child node 12 must be contained within parent node 10' - ); - } - - public function testDeleteWithChildrenShiftsCorrectly(): void - { - $this->generateFixtureTree(); + $this->createDatabase(); - $nodeToDelete = Tree::findOne(3); + $root = new Tree(['name' => 'Root']); + $root->makeRoot(); - self::assertNotNull($nodeToDelete); - self::assertEquals(3, $nodeToDelete->getAttribute('lft')); - self::assertEquals(8, $nodeToDelete->getAttribute('rgt')); + $nodes = []; - $node6 = Tree::findOne(6); + for ($i = 1; $i <= 5; $i++) { + $nodes[$i] = new Tree(['name' => "Node$i"]); + // @phpstan-ignore-next-line + $nodes[$i]->appendTo($root); + } - self::assertNotNull($node6); - self::assertEquals(9, $node6->getAttribute('lft')); - self::assertEquals(14, $node6->getAttribute('rgt')); + foreach ($nodes as $node) { + $node->refresh(); + } - $result = $nodeToDelete->deleteWithChildren(); + $root->refresh(); - self::assertEquals(3, $result); - self::assertNull(Tree::findOne(3)); - self::assertNull(Tree::findOne(4)); - self::assertNull(Tree::findOne(5)); + // @phpstan-ignore-next-line + $nodeToDelete = $nodes[2]; + $rightValueBeforeDelete = $nodeToDelete->rgt; - $node6->refresh(); + $valuesBeforeDelete = []; - self::assertEquals(3, $node6->getAttribute('lft')); - self::assertEquals(8, $node6->getAttribute('rgt')); + foreach ($nodes as $i => $node) { + if ($i !== 2) { + $valuesBeforeDelete[$i] = [ + 'lft' => $node->lft, + 'rgt' => $node->rgt + ]; + } + } + + $nodeToDelete->delete(); + + foreach ($nodes as $i => $node) { + if ($i !== 2) { + $node->refresh(); + + // @phpstan-ignore-next-line + $oldLft = $valuesBeforeDelete[$i]['lft']; + // @phpstan-ignore-next-line + $oldRgt = $valuesBeforeDelete[$i]['rgt']; + + if ($oldLft > $rightValueBeforeDelete) { + self::assertEquals($oldLft - 2, $node->lft, + "Node $i left should be shifted when > deleted right"); + } else { + self::assertEquals($oldLft, $node->lft, + "Node $i left should NOT be shifted when <= deleted right"); + } + + if ($oldRgt > $rightValueBeforeDelete) { + self::assertEquals($oldRgt - 2, $node->rgt, + "Node $i right should be shifted when > deleted right"); + } else { + self::assertEquals($oldRgt, $node->rgt, + "Node $i right should NOT be shifted when <= deleted right"); + } + } + } } } From 139b336554caaa6a09fb9a14a505c0aaf40b53f4 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 28 Jun 2025 21:46:18 +0000 Subject: [PATCH 12/28] Apply fixes from StyleCI --- tests/NestedSetsBehaviorTest.php | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index 8fc6682..bf44eb0 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1702,7 +1702,7 @@ public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void if ($i !== 2) { $valuesBeforeDelete[$i] = [ 'lft' => $node->lft, - 'rgt' => $node->rgt + 'rgt' => $node->rgt, ]; } } @@ -1719,19 +1719,31 @@ public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void $oldRgt = $valuesBeforeDelete[$i]['rgt']; if ($oldLft > $rightValueBeforeDelete) { - self::assertEquals($oldLft - 2, $node->lft, - "Node $i left should be shifted when > deleted right"); + self::assertEquals( + $oldLft - 2, + $node->lft, + "Node $i left should be shifted when > deleted right" + ); } else { - self::assertEquals($oldLft, $node->lft, - "Node $i left should NOT be shifted when <= deleted right"); + self::assertEquals( + $oldLft, + $node->lft, + "Node $i left should NOT be shifted when <= deleted right" + ); } if ($oldRgt > $rightValueBeforeDelete) { - self::assertEquals($oldRgt - 2, $node->rgt, - "Node $i right should be shifted when > deleted right"); + self::assertEquals( + $oldRgt - 2, + $node->rgt, + "Node $i right should be shifted when > deleted right" + ); } else { - self::assertEquals($oldRgt, $node->rgt, - "Node $i right should NOT be shifted when <= deleted right"); + self::assertEquals( + $oldRgt, + $node->rgt, + "Node $i right should NOT be shifted when <= deleted right" + ); } } } From 1f7d3a217379cb930142be993dac2d565a422b00 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 17:56:19 -0400 Subject: [PATCH 13/28] fix: Correct left-right attribute shifting logic in `NestedSetsBehavior`. --- src/NestedSetsBehavior.php | 2 +- tests/NestedSetsBehaviorTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index fd25857..8536d4c 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -142,7 +142,7 @@ public function afterDelete(): void $rightValue = $this->getOwner()->getAttribute($this->rightAttribute); if ($this->operation === self::OPERATION_DELETE_WITH_CHILDREN || $this->getOwner()->isLeaf()) { - $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1); + $this->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1); } else { $condition = [ 'and', diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index bf44eb0..4904159 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1722,13 +1722,13 @@ public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void self::assertEquals( $oldLft - 2, $node->lft, - "Node $i left should be shifted when > deleted right" + "Node $i left should be shifted when > deleted right", ); } else { self::assertEquals( $oldLft, $node->lft, - "Node $i left should NOT be shifted when <= deleted right" + "Node $i left should NOT be shifted when <= deleted right", ); } @@ -1736,13 +1736,13 @@ public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void self::assertEquals( $oldRgt - 2, $node->rgt, - "Node $i right should be shifted when > deleted right" + "Node $i right should be shifted when > deleted right", ); } else { self::assertEquals( $oldRgt, $node->rgt, - "Node $i right should NOT be shifted when <= deleted right" + "Node $i right should NOT be shifted when <= deleted right", ); } } From 7e15a5601694a8d14766c1b9447a032d87037400 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 18:02:35 -0400 Subject: [PATCH 14/28] fix: Adjust left-right attribute shifting logic in `NestedSetsBehavior`. --- .github/workflows/mutation.yml | 2 +- src/NestedSetsBehavior.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 1741a2a..d08baea 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered --log-verbosity=all + command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered composer-command: composer require --dev infection/infection:dev-master && vendor/bin/phpstan --configuration=phpstan.neon analyse secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index 8536d4c..fea406f 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -174,7 +174,7 @@ public function afterDelete(): void ], $condition, ); - $this->shiftLeftRightAttribute($rightValue + 1, -2); + $this->shiftLeftRightAttribute($rightValue, -2); } $this->operation = null; From 1ce4a5bd5bfef6aed0c30a17c2b4e50958f48230 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 18:12:32 -0400 Subject: [PATCH 15/28] fix: Improve null handling for left-right attribute values in `afterUpdate` method. --- src/NestedSetsBehavior.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index fea406f..15ab213 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -247,15 +247,15 @@ public function afterInsert(): void */ public function afterUpdate(): void { - $nodeLeftValue = $this->node?->getAttribute($this->leftAttribute) ?? 0; - $nodeRightValue = $this->node?->getAttribute($this->rightAttribute) ?? 0; + $nodeLeftValue = $this->node?->getAttribute($this->leftAttribute); + $nodeRightValue = $this->node?->getAttribute($this->rightAttribute); match ($this->operation) { - self::OPERATION_APPEND_TO => $this->moveNode($nodeRightValue, 1), - self::OPERATION_INSERT_AFTER => $this->moveNode($nodeRightValue + 1, 0), - self::OPERATION_INSERT_BEFORE => $this->moveNode($nodeLeftValue, 0), + self::OPERATION_APPEND_TO => $nodeRightValue !== null ? $this->moveNode($nodeRightValue, 1) : null, + self::OPERATION_INSERT_AFTER => $nodeRightValue !== null ? $this->moveNode($nodeRightValue + 1, 0) : null, + self::OPERATION_INSERT_BEFORE => $nodeLeftValue !== null ? $this->moveNode($nodeLeftValue, 0) : null, self::OPERATION_MAKE_ROOT => $this->moveNodeAsRoot(), - self::OPERATION_PREPEND_TO => $this->moveNode($nodeLeftValue + 1, 1), + self::OPERATION_PREPEND_TO => $nodeLeftValue !== null ? $this->moveNode($nodeLeftValue + 1, 1) : null, default => null, }; From d1b8234cce15f0af90638488fdc26caad96dd807 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 19:34:30 -0400 Subject: [PATCH 16/28] refactor: Simplify node movement logic in `afterUpdate()` and `beforeInsert()` methods. --- src/NestedSetsBehavior.php | 86 +++++++++++++-------- tests/NestedSetsBehaviorTest.php | 128 ------------------------------- 2 files changed, 52 insertions(+), 162 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index 15ab213..bd55ed3 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -247,17 +247,22 @@ public function afterInsert(): void */ public function afterUpdate(): void { - $nodeLeftValue = $this->node?->getAttribute($this->leftAttribute); - $nodeRightValue = $this->node?->getAttribute($this->rightAttribute); - - match ($this->operation) { - self::OPERATION_APPEND_TO => $nodeRightValue !== null ? $this->moveNode($nodeRightValue, 1) : null, - self::OPERATION_INSERT_AFTER => $nodeRightValue !== null ? $this->moveNode($nodeRightValue + 1, 0) : null, - self::OPERATION_INSERT_BEFORE => $nodeLeftValue !== null ? $this->moveNode($nodeLeftValue, 0) : null, - self::OPERATION_MAKE_ROOT => $this->moveNodeAsRoot(), - self::OPERATION_PREPEND_TO => $nodeLeftValue !== null ? $this->moveNode($nodeLeftValue + 1, 1) : null, - default => null, - }; + switch (true) { + case $this->operation === self::OPERATION_MAKE_ROOT: + $this->moveNodeAsRoot(); + break; + case $this->operation === self::OPERATION_APPEND_TO && $this->node !== null: + $this->moveNode($this->node, $this->node->getAttribute($this->rightAttribute), 1); + break; + case $this->operation === self::OPERATION_INSERT_AFTER && $this->node !== null: + $this->moveNode($this->node, $this->node->getAttribute($this->rightAttribute) + 1, 0); + break; + case $this->operation === self::OPERATION_INSERT_BEFORE && $this->node !== null: + $this->moveNode($this->node, $this->node->getAttribute($this->leftAttribute), 0); + break; + case $this->operation === self::OPERATION_PREPEND_TO && $this->node !== null: + $this->moveNode($this->node, $this->node->getAttribute($this->leftAttribute) + 1, 1); + } $this->operation = null; $this->node = null; @@ -361,23 +366,30 @@ public function beforeDelete(): void */ public function beforeInsert(): void { - if ($this->node?->getIsNewRecord() === false) { + if ($this->node !== null && $this->node->getIsNewRecord() === false) { $this->node->refresh(); } - $nodeLeftValue = $this->node?->getAttribute($this->leftAttribute) ?? 0; - $nodeRightValue = $this->node?->getAttribute($this->rightAttribute) ?? 0; - - match ($this->operation) { - self::OPERATION_APPEND_TO => $this->beforeInsertNode($nodeRightValue, 1), - self::OPERATION_INSERT_AFTER => $this->beforeInsertNode($nodeRightValue + 1, 0), - self::OPERATION_INSERT_BEFORE => $this->beforeInsertNode($nodeLeftValue, 0), - self::OPERATION_MAKE_ROOT => $this->beforeInsertRootNode(), - self::OPERATION_PREPEND_TO => $this->beforeInsertNode($nodeLeftValue + 1, 1), - default => throw new NotSupportedException( - 'Method "' . get_class($this->getOwner()) . '::insert" is not supported for inserting new nodes.', - ), - }; + switch (true) { + case $this->operation === self::OPERATION_MAKE_ROOT: + $this->beforeInsertRootNode(); + break; + case $this->operation === self::OPERATION_PREPEND_TO && $this->node !== null: + $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute) + 1, 1); + break; + case $this->operation === self::OPERATION_INSERT_AFTER && $this->node !== null: + $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute) + 1, 0); + break; + case $this->operation === self::OPERATION_INSERT_BEFORE && $this->node !== null: + $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute), 0); + break; + case $this->operation === self::OPERATION_APPEND_TO && $this->node !== null: + $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1); + default: + throw new NotSupportedException( + 'Method "' . get_class($this->getOwner()) . '::insert" is not supported for inserting new nodes.', + ); + } } /** @@ -1053,14 +1065,19 @@ protected function beforeInsertNode(int|null $value, int $depth): void $this->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); } - $this->shiftLeftRightAttribute($value ?? 0, 2); + if ($this->treeAttribute !== false && $this->node !== null) { + $this->getOwner()->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute)); + } + + if ($value !== null) { + $this->shiftLeftRightAttribute($value, 2); + } } /** @@ -1150,23 +1167,24 @@ protected function deleteWithChildrenInternal(): bool|int * This method is called internally during node movement operations such as append, prepend, insert before/after, * and supports both root and non-root node moves. * + * @param ActiveRecord $node Node to be moved within the nested set tree. * @param int $value Left attribute value indicating the new position for the node. * @param int $depth Depth offset to apply to the node and its descendants after the move. */ - protected function moveNode(int $value, int $depth): void + protected function moveNode(ActiveRecord $node, int $value, int $depth): void { $db = $this->getOwner()::getDb(); $leftValue = $this->getOwner()->getAttribute($this->leftAttribute); $rightValue = $this->getOwner()->getAttribute($this->rightAttribute); $depthValue = $this->getOwner()->getAttribute($this->depthAttribute); $depthAttribute = $db->quoteColumnName($this->depthAttribute); - $nodeDepthValue = $this->node?->getAttribute($this->depthAttribute) ?? 0; + $nodeDepthValue = $node->getAttribute($this->depthAttribute); $depth = $nodeDepthValue - $depthValue + $depth; if ( $this->treeAttribute === false || - $this->getOwner()->getAttribute($this->treeAttribute) === $this->node?->getAttribute($this->treeAttribute) + $this->getOwner()->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute) ) { $delta = $rightValue - $leftValue + 1; @@ -1227,7 +1245,7 @@ protected function moveNode(int $value, int $depth): void } else { $leftAttribute = $db->quoteColumnName($this->leftAttribute); $rightAttribute = $db->quoteColumnName($this->rightAttribute); - $nodeRootValue = $this->node?->getAttribute($this->treeAttribute); + $nodeRootValue = $node->getAttribute($this->treeAttribute); foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) { $this->getOwner()::updateAll( diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index 4904159..d311816 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -1620,132 +1620,4 @@ public function makeRoot(): bool $node->makeRoot(); } - - public function testDeleteNodeDoesNotAffectNodeWithLeftEqualToDeletedRight(): void - { - $this->createDatabase(); - - $root = new Tree(['name' => 'Root']); - $root->makeRoot(); - - $node1 = new Tree(['name' => 'Node1']); - $node1->appendTo($root); - - $child1 = new Tree(['name' => 'Child1']); - $child1->appendTo($node1); - - $node2 = new Tree(['name' => 'Node2']); - $node2->appendTo($root); - - $child2 = new Tree(['name' => 'Child2']); - $child2->appendTo($node2); - - $root->refresh(); - $node1->refresh(); - $child1->refresh(); - $node2->refresh(); - $child2->refresh(); - - self::assertEquals(1, $root->lft); - self::assertEquals(10, $root->rgt); - self::assertEquals(2, $node1->lft); - self::assertEquals(5, $node1->rgt); - self::assertEquals(3, $child1->lft); - self::assertEquals(4, $child1->rgt); - self::assertEquals(6, $node2->lft); - self::assertEquals(9, $node2->rgt); - self::assertEquals(7, $child2->lft); - self::assertEquals(8, $child2->rgt); - - $node1->deleteWithChildren(); - - $root->refresh(); - $node2->refresh(); - $child2->refresh(); - - self::assertEquals(1, $root->lft); - self::assertEquals(6, $root->rgt); - self::assertEquals(2, $node2->lft); - self::assertEquals(5, $node2->rgt); - self::assertEquals(3, $child2->lft); - self::assertEquals(4, $child2->rgt); - } - - public function testShiftBehaviorAfterDeleteWithPreciseBoundary(): void - { - $this->createDatabase(); - - $root = new Tree(['name' => 'Root']); - $root->makeRoot(); - - $nodes = []; - - for ($i = 1; $i <= 5; $i++) { - $nodes[$i] = new Tree(['name' => "Node$i"]); - // @phpstan-ignore-next-line - $nodes[$i]->appendTo($root); - } - - foreach ($nodes as $node) { - $node->refresh(); - } - - $root->refresh(); - - // @phpstan-ignore-next-line - $nodeToDelete = $nodes[2]; - $rightValueBeforeDelete = $nodeToDelete->rgt; - - $valuesBeforeDelete = []; - - foreach ($nodes as $i => $node) { - if ($i !== 2) { - $valuesBeforeDelete[$i] = [ - 'lft' => $node->lft, - 'rgt' => $node->rgt, - ]; - } - } - - $nodeToDelete->delete(); - - foreach ($nodes as $i => $node) { - if ($i !== 2) { - $node->refresh(); - - // @phpstan-ignore-next-line - $oldLft = $valuesBeforeDelete[$i]['lft']; - // @phpstan-ignore-next-line - $oldRgt = $valuesBeforeDelete[$i]['rgt']; - - if ($oldLft > $rightValueBeforeDelete) { - self::assertEquals( - $oldLft - 2, - $node->lft, - "Node $i left should be shifted when > deleted right", - ); - } else { - self::assertEquals( - $oldLft, - $node->lft, - "Node $i left should NOT be shifted when <= deleted right", - ); - } - - if ($oldRgt > $rightValueBeforeDelete) { - self::assertEquals( - $oldRgt - 2, - $node->rgt, - "Node $i right should be shifted when > deleted right", - ); - } else { - self::assertEquals( - $oldRgt, - $node->rgt, - "Node $i right should NOT be shifted when <= deleted right", - ); - } - } - } - } } From 0f780ca89f23929e7ee0c13bccbb889d8ea3af85 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 28 Jun 2025 23:34:59 +0000 Subject: [PATCH 17/28] Apply fixes from StyleCI --- src/NestedSetsBehavior.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index bd55ed3..d3bbcd1 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -385,6 +385,7 @@ public function beforeInsert(): void break; case $this->operation === self::OPERATION_APPEND_TO && $this->node !== null: $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1); + // no break default: throw new NotSupportedException( 'Method "' . get_class($this->getOwner()) . '::insert" is not supported for inserting new nodes.', From 176fc7667276db98ba1b1737690b7a6a438c14db Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 19:45:16 -0400 Subject: [PATCH 18/28] fix: Use nullsafe operator for node check in beforeInsert method and update appendTo/insertAfter calls in tests. --- src/NestedSetsBehavior.php | 2 +- tests/NestedSetsBehaviorTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index d3bbcd1..a5d68f5 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -366,7 +366,7 @@ public function beforeDelete(): void */ public function beforeInsert(): void { - if ($this->node !== null && $this->node->getIsNewRecord() === false) { + if ($this->node?->getIsNewRecord() === false) { $this->node->refresh(); } diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index d311816..aec8081 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 From 623460c28a8309fd035a7e5383697d701007838c Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 19:57:19 -0400 Subject: [PATCH 19/28] fix: Remove unnecessary node refresh calls in beforeInsert and beforeUpdate methods. --- src/NestedSetsBehavior.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index a5d68f5..96a1259 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -331,8 +331,6 @@ public function beforeDelete(): void 'Method "' . get_class($this->getOwner()) . '::delete" is not supported for deleting root nodes.', ); } - - $this->getOwner()->refresh(); } /** @@ -366,10 +364,6 @@ public function beforeDelete(): void */ public function beforeInsert(): void { - if ($this->node?->getIsNewRecord() === false) { - $this->node->refresh(); - } - switch (true) { case $this->operation === self::OPERATION_MAKE_ROOT: $this->beforeInsertRootNode(); @@ -420,10 +414,6 @@ public function beforeInsert(): void */ public function beforeUpdate(): void { - if ($this->node?->getIsNewRecord() === false) { - $this->node->refresh(); - } - switch ($this->operation) { case self::OPERATION_MAKE_ROOT: if ($this->treeAttribute === false) { From 68a3895805b02a0d22d7df1d0724d5d48fdd6723 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 20:03:57 -0400 Subject: [PATCH 20/28] fix: Correct left-right attribute shifting logic in `NestedSetsBehavior`. --- src/NestedSetsBehavior.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index 96a1259..e9feb6a 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -1232,7 +1232,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); @@ -1285,7 +1285,7 @@ protected function moveNode(ActiveRecord $node, int $value, int $depth): void ], ], ); - $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1); + $this->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1); } } @@ -1341,7 +1341,7 @@ protected function moveNodeAsRoot(): void ], ], ); - $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1); + $this->shiftLeftRightAttribute($rightValue, $leftValue - $rightValue - 1); } /** From 7a0b9c6a9d52724b18e0afa8c4c32cb0ba7d2afb Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 20:28:29 -0400 Subject: [PATCH 21/28] fix: Correct logic for appending nodes in `NestedSetsBehavior` and add test for appending to existing child node. --- src/NestedSetsBehavior.php | 4 ++-- tests/NestedSetsQueryBehaviorTest.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index e9feb6a..0c264b8 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -378,8 +378,8 @@ public function beforeInsert(): void $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute), 0); break; case $this->operation === self::OPERATION_APPEND_TO && $this->node !== null: - $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1); - // no break + $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 0); + break; default: throw new NotSupportedException( 'Method "' . get_class($this->getOwner()) . '::insert" is not supported for inserting new nodes.', diff --git a/tests/NestedSetsQueryBehaviorTest.php b/tests/NestedSetsQueryBehaviorTest.php index abc9d84..ce986b6 100644 --- a/tests/NestedSetsQueryBehaviorTest.php +++ b/tests/NestedSetsQueryBehaviorTest.php @@ -70,4 +70,23 @@ public function testThrowLogicExceptionWhenBehaviorIsDetachedFromOwner(): void $behavior->leaves(); } + + 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\'.' + ); + } } From 053f00a0d985b9cedda9298d8738162cc6623854 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 20:44:58 -0400 Subject: [PATCH 22/28] test: Add tests for appending nodes to existing child nodes and verify depth behavior in `NestedSetsBehavior`. --- tests/NestedSetsBehaviorTest.php | 148 ++++++++++++++++++++++++++ tests/NestedSetsQueryBehaviorTest.php | 19 ---- 2 files changed, 148 insertions(+), 19 deletions(-) diff --git a/tests/NestedSetsBehaviorTest.php b/tests/NestedSetsBehaviorTest.php index aec8081..8c469bf 100644 --- a/tests/NestedSetsBehaviorTest.php +++ b/tests/NestedSetsBehaviorTest.php @@ -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); + } } diff --git a/tests/NestedSetsQueryBehaviorTest.php b/tests/NestedSetsQueryBehaviorTest.php index ce986b6..abc9d84 100644 --- a/tests/NestedSetsQueryBehaviorTest.php +++ b/tests/NestedSetsQueryBehaviorTest.php @@ -70,23 +70,4 @@ public function testThrowLogicExceptionWhenBehaviorIsDetachedFromOwner(): void $behavior->leaves(); } - - 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\'.' - ); - } } From 9c40d4bb7cc6c7b88ee3eb1dd6532b1f38210311 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 28 Jun 2025 20:48:21 -0400 Subject: [PATCH 23/28] fix: Update right attribute handling in beforeInsertNode for appending nodes. --- src/NestedSetsBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NestedSetsBehavior.php b/src/NestedSetsBehavior.php index 0c264b8..a1fdd4b 100644 --- a/src/NestedSetsBehavior.php +++ b/src/NestedSetsBehavior.php @@ -378,7 +378,7 @@ public function beforeInsert(): void $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute), 0); break; case $this->operation === self::OPERATION_APPEND_TO && $this->node !== null: - $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 0); + $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1); break; default: throw new NotSupportedException( From 0176e5e2720bb6e224bc17f6062e8bfa1ffe0e18 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 29 Jun 2025 06:01:27 -0400 Subject: [PATCH 24/28] fix: Simplify mutation job configuration by removing unnecessary command options in `mutation.yml`. --- .github/workflows/mutation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index d08baea..9c6f1ec 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,6 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - command-options: --static-analysis-tool=phpstan --threads=2 --ignore-msi-with-no-mutations --only-covered - composer-command: composer require --dev infection/infection:dev-master && vendor/bin/phpstan --configuration=phpstan.neon analyse + phpstan: true secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From 49aac8af5745f870513b06e2e3df7817371fe55c Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 29 Jun 2025 06:03:36 -0400 Subject: [PATCH 25/28] fix: Add tools configuration for infection in mutation job. --- .github/workflows/mutation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 9c6f1ec..d8fd3b8 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -22,5 +22,6 @@ jobs: uses: php-forge/actions/.github/workflows/infection.yml@main with: phpstan: true + tools: infection/infection:dev-master secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From 4786d28c9e8b1cb7863cc428eac7d406d9fe454b Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 29 Jun 2025 06:22:08 -0400 Subject: [PATCH 26/28] fix: Remove unnecessary tools configuration from mutation job. --- .github/workflows/mutation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index d8fd3b8..c95057f 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: + development: true phpstan: true - tools: infection/infection:dev-master secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From 483e39e5eae14cc05b565bd4ffcec80a23735ef9 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 29 Jun 2025 06:24:03 -0400 Subject: [PATCH 27/28] fix: Correct typo in development configuration for mutation job. --- .github/workflows/mutation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index c95057f..e2dcd58 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - development: true + devolopment: true phpstan: true secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} From e731f47c57b15120e34c4e712d1ddacd3079f957 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sun, 29 Jun 2025 06:28:43 -0400 Subject: [PATCH 28/28] fix: Correct typo in development configuration in mutation job. --- .github/workflows/mutation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index e2dcd58..c95057f 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -21,7 +21,7 @@ jobs: mutation: uses: php-forge/actions/.github/workflows/infection.yml@main with: - devolopment: true + development: true phpstan: true secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}