Skip to content

Commit ae1c5bc

Browse files
refactor: NestedSetsBehaviorTest class split into domain specific test classes. (#60)
1 parent 61948fe commit ae1c5bc

24 files changed

+3431
-3113
lines changed

tests/NestedSetsBehaviorTest.php

Lines changed: 0 additions & 3109 deletions
This file was deleted.

tests/TestCase.php

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
use RuntimeException;
88
use SimpleXMLElement;
99
use Yii;
10+
use yii\base\InvalidArgumentException;
1011
use yii\console\Application;
11-
use yii\db\{Connection, SchemaBuilderTrait};
12+
use yii\db\{ActiveQuery, ActiveRecord, Connection, SchemaBuilderTrait};
1213
use yii2\extensions\nestedsets\tests\support\model\{MultipleTree, Tree};
1314

1415
use function array_merge;
@@ -30,11 +31,15 @@
3031
* depth: int,
3132
* }
3233
* >
34+
* @phpstan-type NodeChildren array<string|array{name: string, children?: array<mixed>}>
35+
* @phpstan-type TreeStructure array<array<mixed>>
36+
* @phpstan-type UpdateData array<array{name: string, lft?: int, rgt?: int, depth?: int}>
3337
*/
3438
class TestCase extends \PHPUnit\Framework\TestCase
3539
{
3640
use SchemaBuilderTrait;
3741

42+
protected string|null $dsn = null;
3843
protected string $fixtureDirectory = __DIR__ . '/support/data/';
3944

4045
protected function setUp(): void
@@ -49,6 +54,66 @@ public function getDb(): Connection
4954
return Yii::$app->getDb();
5055
}
5156

57+
/**
58+
* Asserts that a list of tree nodes matches the expected order.
59+
*
60+
* @param array $nodesList List of tree nodes to validate
61+
* @param array $expectedOrder Expected order of node names
62+
* @param string $nodeType Type of nodes being tested (for error messages)
63+
*
64+
* @phpstan-param array<ActiveRecord> $nodesList
65+
* @phpstan-param array<string> $expectedOrder
66+
*/
67+
protected function assertNodesInCorrectOrder(array $nodesList, array $expectedOrder, string $nodeType): void
68+
{
69+
self::assertCount(
70+
count($expectedOrder),
71+
$nodesList,
72+
"{$nodeType} list should contain exactly '" . count($expectedOrder) . "' elements.",
73+
);
74+
75+
foreach ($nodesList as $index => $node) {
76+
self::assertInstanceOf(
77+
Tree::class,
78+
$node,
79+
"{$nodeType} at index {$index} should be an instance of 'Tree'.",
80+
);
81+
82+
if (isset($expectedOrder[$index])) {
83+
self::assertEquals(
84+
$expectedOrder[$index],
85+
$node->getAttribute('name'),
86+
"{$nodeType} at index {$index} should be {$expectedOrder[$index]} in correct 'lft' order.",
87+
);
88+
}
89+
}
90+
}
91+
92+
/**
93+
* Asserts that a query contains ORDER BY clause with 'lft' column.
94+
*
95+
* @param ActiveQuery $query The query to check
96+
* @param string $methodName Name of the method being tested
97+
*
98+
* @phpstan-param ActiveQuery<ActiveRecord> $query
99+
*/
100+
protected function assertQueryHasOrderBy(ActiveQuery $query, string $methodName): void
101+
{
102+
$sql = $query->createCommand()->getRawSql();
103+
104+
self::assertStringContainsString(
105+
'ORDER BY',
106+
$sql,
107+
"'{$methodName}' query should include 'ORDER BY' clause for deterministic results.",
108+
);
109+
110+
self::assertStringContainsString(
111+
'`lft`',
112+
$sql,
113+
"'{$methodName}' query should order by 'left' attribute for consistent ordering.",
114+
);
115+
}
116+
52117
/**
53118
* @phpstan-import-type DataSetType from TestCase
54119
*
@@ -133,6 +198,56 @@ protected function createDatabase(): void
133198
)->execute();
134199
}
135200

201+
/**
202+
* Creates a tree structure based on a hierarchical definition.
203+
*
204+
* @param array $structure Hierarchical tree structure definition
205+
* @param array $updates Database updates to apply after creation
206+
* @param string $modelClass The model class to use (Tree::class or MultipleTree::class)
207+
*
208+
* @throws InvalidArgumentException if the structure array is empty.
209+
*
210+
* @return MultipleTree|Tree The root node
211+
*
212+
* @phpstan-param TreeStructure $structure
213+
* @phpstan-param UpdateData $updates
214+
* @phpstan-param class-string<Tree|MultipleTree> $modelClass
215+
*/
216+
protected function createTreeStructure(
217+
array $structure,
218+
array $updates = [],
219+
string $modelClass = Tree::class,
220+
): Tree|MultipleTree {
221+
if ($structure === []) {
222+
throw new InvalidArgumentException('Tree structure cannot be empty.');
223+
}
224+
225+
$this->createDatabase();
226+
227+
$rootNode = null;
228+
229+
foreach ($structure as $rootDefinition) {
230+
$root = new $modelClass(['name' => $rootDefinition['name'] ?? 'Root']);
231+
$root->makeRoot();
232+
233+
if ($rootNode === null) {
234+
$rootNode = $root;
235+
}
236+
237+
if (isset($rootDefinition['children'])) {
238+
/** @phpstan-var NodeChildren $children */
239+
$children = $rootDefinition['children'];
240+
$this->createChildrenRecursively($root, $children);
241+
}
242+
}
243+
244+
$this->applyUpdates($updates, $modelClass === MultipleTree::class ? 'multiple_tree' : 'tree');
245+
246+
$rootNode->refresh();
247+
248+
return $rootNode;
249+
}
250+
136251
protected function generateFixtureTree(): void
137252
{
138253
$this->createDatabase();
@@ -236,10 +351,62 @@ protected function mockConsoleApplication(): void
236351
'components' => [
237352
'db' => [
238353
'class' => Connection::class,
239-
'dsn' => 'sqlite::memory:',
354+
'dsn' => $this->dsn !== null ? $this->dsn : 'sqlite::memory:',
240355
],
241356
],
242357
],
243358
);
244359
}
360+
361+
/**
362+
* Applies database updates to tree nodes.
363+
*
364+
* @param array $updates Array of updates to apply.
365+
* @param string $tableName Name of the table to apply updates to.
366+
*
367+
* @phpstan-param UpdateData $updates
368+
*/
369+
private function applyUpdates(array $updates, string $tableName): void
370+
{
371+
if ($updates === []) {
372+
return;
373+
}
374+
375+
$command = $this->getDb()->createCommand();
376+
377+
foreach ($updates as $update) {
378+
$name = $update['name'];
379+
380+
unset($update['name']);
381+
382+
$command->update($tableName, $update, ['name' => $name])->execute();
383+
}
384+
}
385+
386+
/**
387+
* Recursively creates children for a given parent node.
388+
*
389+
* @param MultipleTree|Tree $parent The parent node
390+
* @param array $nodes Children definition (can be strings or arrays)
391+
*
392+
* @phpstan-param NodeChildren $nodes
393+
*/
394+
private function createChildrenRecursively(Tree|MultipleTree $parent, array $nodes): void
395+
{
396+
foreach ($nodes as $nodeDefinition) {
397+
if (is_string($nodeDefinition)) {
398+
$node = new ($parent::class)(['name' => $nodeDefinition]);
399+
$node->appendTo($parent);
400+
} else {
401+
$node = new ($parent::class)(['name' => $nodeDefinition['name']]);
402+
$node->appendTo($parent);
403+
404+
if (isset($nodeDefinition['children'])) {
405+
/** @phpstan-var NodeChildren $children */
406+
$children = $nodeDefinition['children'];
407+
$this->createChildrenRecursively($node, $children);
408+
}
409+
}
410+
}
411+
}
245412
}

0 commit comments

Comments
 (0)