Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c3916ea
feat: Add mutation testing configuration and update `PHPUnit` schema …
terabytesoftw Jun 28, 2025
023fa50
test: Add test for shifting left and right attributes after deleting …
terabytesoftw Jun 28, 2025
1dbbb17
test: Rename test for shifting left and right attributes after deleti…
terabytesoftw Jun 28, 2025
d039c60
feat: Update mutation testing configuration and clean up JSON files.
terabytesoftw Jun 28, 2025
fd62276
feat: Update mutation testing configuration and remove minimum stabil…
terabytesoftw Jun 28, 2025
d885491
test: Enhance deletion tests for tree nodes and verify attribute shifts.
terabytesoftw Jun 28, 2025
d6d891b
feat: Update mutation testing workflow to include static analysis com…
terabytesoftw Jun 28, 2025
daf60d7
fix: Simplify composer command in mutation testing workflow.
terabytesoftw Jun 28, 2025
e6af6b2
feat: Add phpStan configuration to `infection.json5`.
terabytesoftw Jun 28, 2025
6c1c41a
feat: Add log verbosity option to mutation testing command.
terabytesoftw Jun 28, 2025
af317f9
test: Refactor deletion tests to improve clarity and accuracy of node…
terabytesoftw Jun 28, 2025
139b336
Apply fixes from StyleCI
StyleCIBot Jun 28, 2025
1f7d3a2
fix: Correct left-right attribute shifting logic in `NestedSetsBehavi…
terabytesoftw Jun 28, 2025
7e15a56
fix: Adjust left-right attribute shifting logic in `NestedSetsBehavior`.
terabytesoftw Jun 28, 2025
1ce4a5b
fix: Improve null handling for left-right attribute values in `afterU…
terabytesoftw Jun 28, 2025
d1b8234
refactor: Simplify node movement logic in `afterUpdate()` and `before…
terabytesoftw Jun 28, 2025
0f780ca
Apply fixes from StyleCI
StyleCIBot Jun 28, 2025
176fc76
fix: Use nullsafe operator for node check in beforeInsert method and …
terabytesoftw Jun 28, 2025
623460c
fix: Remove unnecessary node refresh calls in beforeInsert and before…
terabytesoftw Jun 28, 2025
68a3895
fix: Correct left-right attribute shifting logic in `NestedSetsBehavi…
terabytesoftw Jun 29, 2025
7a0b9c6
fix: Correct logic for appending nodes in `NestedSetsBehavior` and ad…
terabytesoftw Jun 29, 2025
053f00a
test: Add tests for appending nodes to existing child nodes and verif…
terabytesoftw Jun 29, 2025
9c40d4b
fix: Update right attribute handling in beforeInsertNode for appendin…
terabytesoftw Jun 29, 2025
0176e5e
fix: Simplify mutation job configuration by removing unnecessary comm…
terabytesoftw Jun 29, 2025
49aac8a
fix: Add tools configuration for infection in mutation job.
terabytesoftw Jun 29, 2025
4786d28
fix: Remove unnecessary tools configuration from mutation job.
terabytesoftw Jun 29, 2025
483e39e
fix: Correct typo in development configuration for mutation job.
terabytesoftw Jun 29, 2025
e731f47
fix: Correct typo in development configuration in mutation job.
terabytesoftw Jun 29, 2025
43ca033
Merge branch 'main' into fix-mini-8
terabytesoftw Jun 29, 2025
c4ef00b
Merge branch 'main' into fix-mini-8
terabytesoftw Jun 29, 2025
b4f9f58
Merge branch 'main' into fix-mini-8.
terabytesoftw Jun 29, 2025
e73830d
Merge branch 'main' into fix-mini-8
terabytesoftw Jun 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
@@ -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 }}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@
"config": {
"sort-packages": true,
"allow-plugins": {
"infection/extension-installer": true,
"phpstan/extension-installer": true,
"yiisoft/yii2-composer": true
}
},
"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"
Expand Down
17 changes: 9 additions & 8 deletions infection.json.dist → infection.json5
Original file line number Diff line number Diff line change
@@ -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": "."
},
}
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
cacheDirectory="runtime/.phpunit.cache"
colors="true"
Expand Down
17 changes: 11 additions & 6 deletions src/NestedSetsBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@
*
* @phpstan-param array<string, mixed>|null $attributes
*/
public function appendTo(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool

Check warning on line 294 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ * * @phpstan-param array<string, mixed>|null $attributes */ - public function appendTo(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool + public function appendTo(ActiveRecord $node, bool $runValidation = false, array|null $attributes = null): bool { $this->operation = self::OPERATION_APPEND_TO; $this->node = $node;
{
$this->operation = self::OPERATION_APPEND_TO;
$this->node = $node;
Expand Down Expand Up @@ -492,7 +492,7 @@

$this->applyTreeAttributeCondition($condition);

return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);

Check warning on line 495 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $condition[] = ['<=', $this->depthAttribute, $this->getOwner()->getAttribute($this->depthAttribute) + $depth]; } $this->applyTreeAttributeCondition($condition); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Deletes the current node and all its descendant nodes from the nested set tree.
}

/**
Expand Down Expand Up @@ -521,21 +521,21 @@
{
$this->operation = self::OPERATION_DELETE_WITH_CHILDREN;

if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) !== false) {

Check warning on line 524 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "NotIdentical": @@ @@ public function deleteWithChildren(): bool|int { $this->operation = self::OPERATION_DELETE_WITH_CHILDREN; - if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) !== false) { + if ($this->getOwner()->isTransactional(ActiveRecord::OP_DELETE) === false) { return $this->deleteWithChildrenInternal(); } $transaction = $this->getOwner()::getDb()->beginTransaction();
return $this->deleteWithChildrenInternal();
}

$transaction = $this->getOwner()::getDb()->beginTransaction();

try {
match ($result = $this->deleteWithChildrenInternal()) {

Check warning on line 531 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MatchArmRemoval": @@ @@ $transaction = $this->getOwner()::getDb()->beginTransaction(); try { match ($result = $this->deleteWithChildrenInternal()) { - false => $transaction->rollBack(), default => $transaction->commit(), }; return $result;
false => $transaction->rollBack(),
default => $transaction->commit(),
};

return $result;
} catch (Exception $e) {
$transaction->rollBack();

Check warning on line 538 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ }; return $result; } catch (Exception $e) { - $transaction->rollBack(); + throw $e; } }

throw $e;
}
Expand Down Expand Up @@ -604,7 +604,7 @@
*
* @phpstan-param array<string, mixed>|null $attributes
*/
public function insertAfter(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool

Check warning on line 607 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ * * @phpstan-param array<string, mixed>|null $attributes */ - public function insertAfter(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool + public function insertAfter(ActiveRecord $node, bool $runValidation = false, array|null $attributes = null): bool { $this->operation = self::OPERATION_INSERT_AFTER; $this->node = $node;
{
$this->operation = self::OPERATION_INSERT_AFTER;
$this->node = $node;
Expand Down Expand Up @@ -645,7 +645,7 @@
*
* @phpstan-param array<string, mixed>|null $attributes
*/
public function insertBefore(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool

Check warning on line 648 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": @@ @@ * * @phpstan-param array<string, mixed>|null $attributes */ - public function insertBefore(ActiveRecord $node, bool $runValidation = true, array|null $attributes = null): bool + public function insertBefore(ActiveRecord $node, bool $runValidation = false, array|null $attributes = null): bool { $this->operation = self::OPERATION_INSERT_BEFORE; $this->node = $node;
{
$this->operation = self::OPERATION_INSERT_BEFORE;
$this->node = $node;
Expand Down Expand Up @@ -685,7 +685,7 @@
$nodeLeft = $node->getAttribute($this->leftAttribute);
$nodeRight = $node->getAttribute($this->rightAttribute);

if ($currentLeft <= $nodeLeft || $currentRight >= $nodeRight) {

Check warning on line 688 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "GreaterThanOrEqualTo": @@ @@ $currentRight = $owner->getAttribute($this->rightAttribute); $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($currentLeft <= $nodeLeft || $currentRight >= $nodeRight) { + if ($currentLeft <= $nodeLeft || $currentRight > $nodeRight) { return false; } if ($this->treeAttribute !== false) {

Check warning on line 688 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "LessThanOrEqualTo": @@ @@ $currentRight = $owner->getAttribute($this->rightAttribute); $nodeLeft = $node->getAttribute($this->leftAttribute); $nodeRight = $node->getAttribute($this->rightAttribute); - if ($currentLeft <= $nodeLeft || $currentRight >= $nodeRight) { + if ($currentLeft < $nodeLeft || $currentRight >= $nodeRight) { return false; } if ($this->treeAttribute !== false) {
return false;
}

Expand Down Expand Up @@ -794,7 +794,7 @@

$this->applyTreeAttributeCondition($condition);

return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);

Check warning on line 797 in src/NestedSetsBehavior.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ { $condition = ['and', ['>', $this->leftAttribute, $this->getOwner()->getAttribute($this->leftAttribute)], ['<', $this->rightAttribute, $this->getOwner()->getAttribute($this->rightAttribute)], [$this->rightAttribute => new Expression($this->getOwner()::getDb()->quoteColumnName($this->leftAttribute) . '+ 1')]]; $this->applyTreeAttributeCondition($condition); - return $this->getOwner()::find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]); + return $this->getOwner()::find()->andWhere($condition)->addOrderBy([]); } /** * Creates the root node if the active record is new, or moves it as the root node in the nested set tree.
}

/**
Expand Down Expand Up @@ -1054,14 +1054,19 @@
$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);
}

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);
}
}

/**
Expand Down Expand Up @@ -1225,7 +1230,7 @@
);
}

$this->shiftLeftRightAttribute($rightValue + 1, -$delta);
$this->shiftLeftRightAttribute($rightValue, -$delta);
} else {
$leftAttribute = $db->quoteColumnName($this->leftAttribute);
$rightAttribute = $db->quoteColumnName($this->rightAttribute);
Expand Down
152 changes: 150 additions & 2 deletions tests/NestedSetsBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}