From 38a822fc7c999a70cfb77b0fc523831b4409497b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Helfensdo=CC=88rfer?= Date: Sun, 20 Oct 2019 13:29:53 +0200 Subject: [PATCH] Require php 7.2 and laravel 6 because of phpunit 8 --- .gitignore | 1 + composer.json | 12 +- src/Console/BaumCommand.php | 49 +- src/Console/InstallCommand.php | 235 +- src/Extensions/Eloquent/Collection.php | 52 +- src/Extensions/Eloquent/Model.php | 214 +- src/Extensions/Query/Builder.php | 73 +- src/Generators/Generator.php | 161 +- src/Generators/MigrationGenerator.php | 111 +- src/Generators/ModelGenerator.php | 68 +- src/Generators/stubs/migration.php | 64 +- src/Generators/stubs/model.php | 190 +- src/Move.php | 755 ++--- src/MoveNotPossibleException.php | 5 +- src/NestedSet/Columns.php | 96 +- src/Node.php | 2611 +++++++++-------- src/Providers/BaumServiceProvider.php | 121 +- src/SetBuilder.php | 346 +-- src/SetMapper.php | 300 +- src/SetValidator.php | 416 +-- tests/suite/BaumTestCase.php | 18 +- tests/suite/Category/CategoryColumnsTest.php | 193 +- .../Category/CategoryCustomEventsTest.php | 72 +- .../suite/Category/CategoryHierarchyTest.php | 1306 +++++---- tests/suite/Category/CategoryMovementTest.php | 758 ++--- .../suite/Category/CategoryRelationsTest.php | 176 +- tests/suite/Category/CategoryScopedTest.php | 536 ++-- .../Category/CategorySoftDeletesTest.php | 376 +-- .../suite/Category/CategoryTreeMapperTest.php | 410 +-- .../Category/CategoryTreeRebuildingTest.php | 138 +- .../Category/CategoryTreeValidationTest.php | 124 +- tests/suite/CategoryTestCase.php | 24 +- tests/suite/Cluster/ClusterColumnsTest.php | 25 +- tests/suite/Cluster/ClusterHierarchyTest.php | 1312 +++++---- tests/suite/Cluster/ClusterMovementTest.php | 758 ++--- tests/suite/ClusterTestCase.php | 24 +- tests/suite/NodeModelExtensionsTest.php | 291 +- tests/suite/QueryBuilderExtensionTest.php | 119 +- tests/suite/support.php | 102 +- 39 files changed, 6592 insertions(+), 6050 deletions(-) diff --git a/.gitignore b/.gitignore index a439bd57..826d2ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor composer.lock .vscode +.phpunit.result.cache diff --git a/composer.json b/composer.json index bc13002f..1be5191a 100644 --- a/composer.json +++ b/composer.json @@ -12,16 +12,16 @@ } ], "require": { - "php": ">=7.0.0", - "illuminate/database": "~5.5.0|~5.6.0|~5.7.0|~5.8.0|~5.9.0|~6.0.0", - "illuminate/events": "~5.5.0|~5.6.0|~5.7.0|~5.8.0|~5.9.0|~6.0.0", - "illuminate/support": "~5.5.0|~5.6.0|~5.7.0|~5.8.0|~5.9.0|~6.0.0" + "php": ">=7.2.0", + "illuminate/database": "^6.0", + "illuminate/events": "^6.0", + "illuminate/support": "^6.0" }, "require-dev": { - "d11wtq/boris": "~1.0.10", + "d11wtq/boris": "^1.0", "doctrine/dbal": "*", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^8.0", "squizlabs/php_codesniffer": "^3.4" }, "autoload": { diff --git a/src/Console/BaumCommand.php b/src/Console/BaumCommand.php index 56394739..3c2054ba 100644 --- a/src/Console/BaumCommand.php +++ b/src/Console/BaumCommand.php @@ -1,34 +1,37 @@ line('Baum version ' . Baum::VERSION . ''); - $this->line('A Nested Set pattern implementation for the Eloquent ORM.'); - $this->line('Copyright (c) 2013 Estanislau Trepat'); - } + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->line('Baum version ' . Baum::VERSION . ''); + $this->line('A Nested Set pattern implementation for the Eloquent ORM.'); + $this->line('Copyright (c) 2013 Estanislau Trepat'); + } } diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 8c273b7b..8ba1f305 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -1,4 +1,5 @@ migrator = $migrator; - $this->modeler = $modeler; - } - - /** - * Execute the console command. - * - * Basically, we'll write the migration and model stubs out to disk inflected - * with the name provided. Once its done, we'll `dump-autoload` for the entire - * framework to make sure that the new classes are registered by the class - * loaders. - * - * @return void - */ - public function fire() { - $name = $this->input->getArgument('name'); - - $this->writeMigration($name); - - $this->writeModel($name); - - } - - /** - * Get the command arguments - * - * @return array - */ - protected function getArguments() { - return array( - array('name', InputArgument::REQUIRED, 'Name to use for the scaffolding of the migration and model.') - ); - } - - /** - * Write the migration file to disk. - * - * @param string $name - * @return string - */ - protected function writeMigration($name) { - $output = pathinfo($this->migrator->create($name, $this->getMigrationsPath()), PATHINFO_FILENAME); - - $this->line(" create $output"); - } - - /** - * Write the model file to disk. - * - * @param string $name - * @return string - */ - protected function writeModel($name) { - $output = pathinfo($this->modeler->create($name, $this->getModelsPath()), PATHINFO_FILENAME); - - $this->line(" create $output"); - } - - /** - * Get the path to the migrations directory. - * - * @return string - */ - protected function getMigrationsPath() { - return $this->laravel['path.database'].'/migrations'; - } - - /** - * Get the path to the models directory. - * - * @return string - */ - protected function getModelsPath() { - return $this->laravel['path.base']; - } +class InstallCommand extends Command +{ + + /** + * The console command name. + * + * @var string + */ + protected $name = 'baum:install'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Scaffolds a new migration and model suitable for Baum.'; + + /** + * Migration generator instance + * + * @var Baum\Generators\MigrationGenerator + */ + protected $migrator; + + /** + * Model generator instance + * + * @var Baum\Generators\ModelGenerator + */ + protected $modeler; + + /** + * Create a new command instance + * + * @return void + */ + public function __construct(MigrationGenerator $migrator, ModelGenerator $modeler) + { + parent::__construct(); + + $this->migrator = $migrator; + $this->modeler = $modeler; + } + + /** + * Execute the console command. + * + * Basically, we'll write the migration and model stubs out to disk inflected + * with the name provided. Once its done, we'll `dump-autoload` for the entire + * framework to make sure that the new classes are registered by the class + * loaders. + * + * @return void + */ + public function fire() + { + $name = $this->input->getArgument('name'); + + $this->writeMigration($name); + + $this->writeModel($name); + + } + + /** + * Get the command arguments + * + * @return array + */ + protected function getArguments() + { + return array( + array('name', InputArgument::REQUIRED, 'Name to use for the scaffolding of the migration and model.') + ); + } + + /** + * Write the migration file to disk. + * + * @param string $name + * @return string + */ + protected function writeMigration($name) + { + $output = pathinfo($this->migrator->create($name, $this->getMigrationsPath()), PATHINFO_FILENAME); + + $this->line(" create $output"); + } + + /** + * Write the model file to disk. + * + * @param string $name + * @return string + */ + protected function writeModel($name) + { + $output = pathinfo($this->modeler->create($name, $this->getModelsPath()), PATHINFO_FILENAME); + + $this->line(" create $output"); + } + + /** + * Get the path to the migrations directory. + * + * @return string + */ + protected function getMigrationsPath() + { + return $this->laravel['path.database'] . '/migrations'; + } + + /** + * Get the path to the models directory. + * + * @return string + */ + protected function getModelsPath() + { + return $this->laravel['path.base']; + } } diff --git a/src/Extensions/Eloquent/Collection.php b/src/Extensions/Eloquent/Collection.php index e6fa41c2..fd91ea4a 100644 --- a/src/Extensions/Eloquent/Collection.php +++ b/src/Extensions/Eloquent/Collection.php @@ -1,41 +1,45 @@ getDictionary(); + public function toHierarchy() + { + $dict = $this->getDictionary(); - // Enforce sorting by $orderColumn setting in Baum\Node instance - uasort($dict, function($a, $b){ - return ($a->getOrder() >= $b->getOrder()) ? 1 : -1; - }); + // Enforce sorting by $orderColumn setting in Baum\Node instance + uasort($dict, function ($a, $b) { + return ($a->getOrder() >= $b->getOrder()) ? 1 : -1; + }); - return new BaseCollection($this->hierarchical($dict)); - } + return new BaseCollection($this->hierarchical($dict)); + } - protected function hierarchical($result) { - foreach($result as $key => $node) - $node->setRelation('children', new BaseCollection); + protected function hierarchical($result) + { + foreach ($result as $key => $node) + $node->setRelation('children', new BaseCollection); - $nestedKeys = array(); + $nestedKeys = array(); - foreach($result as $key => $node) { - $parentKey = $node->getParentId(); + foreach ($result as $key => $node) { + $parentKey = $node->getParentId(); - if ( !is_null($parentKey) && array_key_exists($parentKey, $result) ) { - $result[$parentKey]->children[] = $node; + if (!is_null($parentKey) && array_key_exists($parentKey, $result)) { + $result[$parentKey]->children[] = $node; - $nestedKeys[] = $node->getKey(); - } - } + $nestedKeys[] = $node->getKey(); + } + } - foreach($nestedKeys as $key) - unset($result[$key]); + foreach ($nestedKeys as $key) + unset($result[$key]); - return $result; - } + return $result; + } } diff --git a/src/Extensions/Eloquent/Model.php b/src/Extensions/Eloquent/Model.php index 946960cc..daa4b07d 100644 --- a/src/Extensions/Eloquent/Model.php +++ b/src/Extensions/Eloquent/Model.php @@ -1,4 +1,5 @@ exists || ($this->areSoftDeletesEnabled() && $this->trashed())) { + $fresh = $this->getFreshInstance(); - /** - * Reloads the model from the database. - * - * @return \Baum\Node - * - * @throws ModelNotFoundException - */ - public function reload() { - if ( $this->exists || ($this->areSoftDeletesEnabled() && $this->trashed()) ) { - $fresh = $this->getFreshInstance(); + if (is_null($fresh)) + throw with(new ModelNotFoundException)->setModel(get_called_class()); - if ( is_null($fresh) ) - throw with(new ModelNotFoundException)->setModel(get_called_class()); + $this->setRawAttributes($fresh->getAttributes(), true); - $this->setRawAttributes($fresh->getAttributes(), true); + $this->setRelations($fresh->getRelations()); - $this->setRelations($fresh->getRelations()); + $this->exists = $fresh->exists; + } else { + // Revert changes if model is not persisted + $this->attributes = $this->original; + } - $this->exists = $fresh->exists; - } else { - // Revert changes if model is not persisted - $this->attributes = $this->original; + return $this; } - return $this; - } - - /** - * Get the observable event names. - * - * @return array - */ - public function getObservableEvents() { - return array_merge(array('moving', 'moved'), parent::getObservableEvents()); - } - - /** - * Register a moving model event with the dispatcher. - * - * @param Closure|string $callback - * @return void - */ - public static function moving($callback) { - static::registerModelEvent('moving', $callback); - } - - /** - * Register a moved model event with the dispatcher. - * - * @param Closure|string $callback - * @return void - */ - public static function moved($callback) { - static::registerModelEvent('moved', $callback); - } - - /** - * Get a new query builder instance for the connection. - * - * @return \Baum\Extensions\Query\Builder - */ - protected function newBaseQueryBuilder() { - $conn = $this->getConnection(); - - $grammar = $conn->getQueryGrammar(); - - return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); - } - - /** - * Returns a fresh instance from the database. - * - * @return \Baum\Node - */ - protected function getFreshInstance() { - if ( $this->areSoftDeletesEnabled() ) - return static::withTrashed()->find($this->getKey()); - - return static::find($this->getKey()); - } - - /** - * Returns wether soft delete functionality is enabled on the model or not. - * - * @return boolean - */ - public function areSoftDeletesEnabled() { - // To determine if there's a global soft delete scope defined we must - // first determine if there are any, to workaround a non-existent key error. - $globalScopes = $this->getGlobalScopes(); - - if ( count($globalScopes) === 0 ) return false; - - // Now that we're sure that the calling class has some kind of global scope - // we check for the SoftDeletingScope existance - return static::hasGlobalScope(new SoftDeletingScope); - } - - /** - * Static method which returns wether soft delete functionality is enabled - * on the model. - * - * @return boolean - */ - public static function softDeletesEnabled() { - return with(new static)->areSoftDeletesEnabled(); - } + /** + * Get the observable event names. + * + * @return array + */ + public function getObservableEvents() + { + return array_merge(array('moving', 'moved'), parent::getObservableEvents()); + } + + /** + * Register a moving model event with the dispatcher. + * + * @param Closure|string $callback + * @return void + */ + public static function moving($callback) + { + static::registerModelEvent('moving', $callback); + } + + /** + * Register a moved model event with the dispatcher. + * + * @param Closure|string $callback + * @return void + */ + public static function moved($callback) + { + static::registerModelEvent('moved', $callback); + } + + /** + * Get a new query builder instance for the connection. + * + * @return \Baum\Extensions\Query\Builder + */ + protected function newBaseQueryBuilder() + { + $conn = $this->getConnection(); + + $grammar = $conn->getQueryGrammar(); + + return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); + } + + /** + * Returns a fresh instance from the database. + * + * @return \Baum\Node + */ + protected function getFreshInstance() + { + if ($this->areSoftDeletesEnabled()) + return static::withTrashed()->find($this->getKey()); + + return static::find($this->getKey()); + } + + /** + * Returns wether soft delete functionality is enabled on the model or not. + * + * @return boolean + */ + public function areSoftDeletesEnabled() + { + // To determine if there's a global soft delete scope defined we must + // first determine if there are any, to workaround a non-existent key error. + $globalScopes = $this->getGlobalScopes(); + + if (count($globalScopes) === 0) return false; + + // Now that we're sure that the calling class has some kind of global scope + // we check for the SoftDeletingScope existance + return static::hasGlobalScope(new SoftDeletingScope); + } + + /** + * Static method which returns wether soft delete functionality is enabled + * on the model. + * + * @return boolean + */ + public static function softDeletesEnabled() + { + return with(new static)->areSoftDeletesEnabled(); + } } diff --git a/src/Extensions/Query/Builder.php b/src/Extensions/Query/Builder.php index 801a9209..ec411aed 100644 --- a/src/Extensions/Query/Builder.php +++ b/src/Extensions/Query/Builder.php @@ -4,40 +4,43 @@ use Illuminate\Database\Query\Builder as BaseBuilder; -class Builder extends BaseBuilder { - - /** - * Replace the "order by" clause of the current query. - * - * @param string $column - * @param string $direction - * @return \Illuminate\Database\Query\Builder|static - */ - public function reOrderBy($column = null, $direction = 'asc') { - $this->{$this->unions ? 'unionOrders' : 'orders'} = null; - - if (!is_null($column)) { - return parent::orderBy($column, $direction); - } +class Builder extends BaseBuilder +{ + + /** + * Replace the "order by" clause of the current query. + * + * @param string $column + * @param string $direction + * @return \Illuminate\Database\Query\Builder|static + */ + public function reOrderBy($column = null, $direction = 'asc') + { + $this->{$this->unions ? 'unionOrders' : 'orders'} = null; + + if (!is_null($column)) { + return parent::orderBy($column, $direction); + } - return $this; - } - - /** - * Execute an aggregate function on the database. - * - * @param string $function - * @param array $columns - * @return mixed - */ - public function aggregate($function, $columns = array('*')) { - // Postgres doesn't like ORDER BY when there's no GROUP BY clause - if (!isset($this->groups)) { - $this->reOrderBy(null); + return $this; } - return parent::aggregate($function, $columns); - } + /** + * Execute an aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return mixed + */ + public function aggregate($function, $columns = array('*')) + { + // Postgres doesn't like ORDER BY when there's no GROUP BY clause + if (!isset($this->groups)) { + $this->reOrderBy(null); + } + + return parent::aggregate($function, $columns); + } /** * Determine if any rows exist for the current query. @@ -46,11 +49,11 @@ public function aggregate($function, $columns = array('*')) { */ public function exists() { - if (!isset($this->groups)) { - $this->reOrderBy(null); - } + if (!isset($this->groups)) { + $this->reOrderBy(null); + } - return parent::exists(); + return parent::exists(); } } diff --git a/src/Generators/Generator.php b/src/Generators/Generator.php index de02fab4..50ad18b5 100644 --- a/src/Generators/Generator.php +++ b/src/Generators/Generator.php @@ -1,93 +1,102 @@ files = $files; - } + /** + * Create a new MigrationGenerator instance. + * + * @param \Illuminate\Filesystem\Filesysmte $files + * @return void + */ + function __construct(Filesystem $files) + { + $this->files = $files; + } - /** - * Get the path to the stubs. - * - * @return string - */ - public function getStubPath() { - return __DIR__.'/stubs'; - } + /** + * Get the path to the stubs. + * + * @return string + */ + public function getStubPath() + { + return __DIR__ . '/stubs'; + } - /** - * Get the filesystem instance. - * - * @return \Illuminate\Filesystem\Filesystem - */ - public function getFilesystem() { - return $this->files; - } + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } - /** - * Get the given stub by name. - * - * @param string $table - * @return void - */ - protected function getStub($name) { - if ( stripos($name, '.php') === FALSE ) - $name = $name . '.php'; + /** + * Get the given stub by name. + * + * @param string $table + * @return void + */ + protected function getStub($name) + { + if (stripos($name, '.php') === false) + $name = $name . '.php'; - return $this->files->get($this->getStubPath() . '/' . $name); - } + return $this->files->get($this->getStubPath() . '/' . $name); + } - /** - * Parse the provided stub and replace via the array given. - * - * @param string $stub - * @param string $replacements - * @return string - */ - protected function parseStub($stub, $replacements=array()) { - $output = $stub; + /** + * Parse the provided stub and replace via the array given. + * + * @param string $stub + * @param string $replacements + * @return string + */ + protected function parseStub($stub, $replacements = array()) + { + $output = $stub; - foreach ($replacements as $key => $replacement) { - $search = '{{'.$key.'}}'; - $output = str_replace($search, $replacement, $output); - } + foreach ($replacements as $key => $replacement) { + $search = '{{' . $key . '}}'; + $output = str_replace($search, $replacement, $output); + } - return $output; - } + return $output; + } - /** - * Inflect to a class name - * - * @param string $input - * @return string - */ - protected function classify($input) { - return studly_case(str_singular($input)); - } + /** + * Inflect to a class name + * + * @param string $input + * @return string + */ + protected function classify($input) + { + return studly_case(str_singular($input)); + } - /** - * Inflect to table name - * - * @param string $input - * @return string - */ - protected function tableize($input) { - return snake_case(str_plural($input)); - } + /** + * Inflect to table name + * + * @param string $input + * @return string + */ + protected function tableize($input) + { + return snake_case(str_plural($input)); + } } diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php index c7a052e9..360872fd 100644 --- a/src/Generators/MigrationGenerator.php +++ b/src/Generators/MigrationGenerator.php @@ -1,65 +1,72 @@ getPath($name, $path); + /** + * Create a new migration at the given path. + * + * @param string $name + * @param string $path + * @return string + */ + public function create($name, $path) + { + $path = $this->getPath($name, $path); - $stub = $this->getStub('migration'); + $stub = $this->getStub('migration'); - $this->files->put($path, $this->parseStub($stub, array( - 'table' => $this->tableize($name), - 'class' => $this->getMigrationClassName($name) - ))); + $this->files->put($path, $this->parseStub($stub, array( + 'table' => $this->tableize($name), + 'class' => $this->getMigrationClassName($name) + ))); - return $path; - } + return $path; + } - /** - * Get the migration name. - * - * @param string $name - * @return string - */ - protected function getMigrationName($name) { - return 'create_' . $this->tableize($name) . '_table'; - } + /** + * Get the migration name. + * + * @param string $name + * @return string + */ + protected function getMigrationName($name) + { + return 'create_' . $this->tableize($name) . '_table'; + } - /** - * Get the name for the migration class. - * - * @param string $name - */ - protected function getMigrationClassName($name) { - return $this->classify($this->getMigrationName($name)); - } + /** + * Get the name for the migration class. + * + * @param string $name + */ + protected function getMigrationClassName($name) + { + return $this->classify($this->getMigrationName($name)); + } - /** - * Get the full path name to the migration. - * - * @param string $name - * @param string $path - * @return string - */ - protected function getPath($name, $path) { - return $path . '/' . $this->getDatePrefix() . '_' . $this->getMigrationName($name) . '.php'; - } + /** + * Get the full path name to the migration. + * + * @param string $name + * @param string $path + * @return string + */ + protected function getPath($name, $path) + { + return $path . '/' . $this->getDatePrefix() . '_' . $this->getMigrationName($name) . '.php'; + } - /** - * Get the date prefix for the migration. - * - * @return int - */ - protected function getDatePrefix() { - return date('Y_m_d_His'); - } + /** + * Get the date prefix for the migration. + * + * @return int + */ + protected function getDatePrefix() + { + return date('Y_m_d_His'); + } } diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php index b5ebf5dd..dd9b2394 100644 --- a/src/Generators/ModelGenerator.php +++ b/src/Generators/ModelGenerator.php @@ -1,37 +1,41 @@ getPath($name, $path); - - $stub = $this->getStub('model'); - - $this->files->put($path, $this->parseStub($stub, array( - 'table' => $this->tableize($name), - 'class' => $this->classify($name) - ))); - - return $path; - } - - /** - * Get the full path name to the migration. - * - * @param string $name - * @param string $path - * @return string - */ - protected function getPath($name, $path) { - return $path . '/' . $this->classify($name) . '.php'; - } +class ModelGenerator extends Generator +{ + + /** + * Create a new model at the given path. + * + * @param string $name + * @param string $path + * @return string + */ + public function create($name, $path) + { + $path = $this->getPath($name, $path); + + $stub = $this->getStub('model'); + + $this->files->put($path, $this->parseStub($stub, array( + 'table' => $this->tableize($name), + 'class' => $this->classify($name) + ))); + + return $path; + } + + /** + * Get the full path name to the migration. + * + * @param string $name + * @param string $path + * @return string + */ + protected function getPath($name, $path) + { + return $path . '/' . $this->classify($name) . '.php'; + } } diff --git a/src/Generators/stubs/migration.php b/src/Generators/stubs/migration.php index 1ad33068..77cfa5ed 100644 --- a/src/Generators/stubs/migration.php +++ b/src/Generators/stubs/migration.php @@ -5,38 +5,42 @@ class {{class}} extends Migration { - /** - * Run the migrations. - * - * @return void - */ - public function up() { - Schema::create('{{table}}', function(Blueprint $table) { - // These columns are needed for Baum's Nested Set implementation to work. - // Column names may be changed, but they *must* all exist and be modified - // in the model. - // Take a look at the model scaffold comments for details. - // We add indexes on parent_id, lft, rgt columns by default. - $table->increments('id'); - $table->integer('parent_id')->nullable()->index(); - $table->integer('lft')->nullable()->index(); - $table->integer('rgt')->nullable()->index(); - $table->integer('depth')->nullable(); + /** + * Run the migrations. + * + * @return void + */ + public + function up() + { + Schema::create('{{table}}', function (Blueprint $table) { + // These columns are needed for Baum's Nested Set implementation to work. + // Column names may be changed, but they *must* all exist and be modified + // in the model. + // Take a look at the model scaffold comments for details. + // We add indexes on parent_id, lft, rgt columns by default. + $table->increments('id'); + $table->integer('parent_id')->nullable()->index(); + $table->integer('lft')->nullable()->index(); + $table->integer('rgt')->nullable()->index(); + $table->integer('depth')->nullable(); - // Add needed columns here (f.ex: name, slug, path, etc.) - // $table->string('name', 255); + // Add needed columns here (f.ex: name, slug, path, etc.) + // $table->string('name', 255); - $table->timestamps(); - }); - } + $table->timestamps(); + }); + } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() { - Schema::drop('{{table}}'); - } + /** + * Reverse the migrations. + * + * @return void + */ + public + function down() + { + Schema::drop('{{table}}'); + } } diff --git a/src/Generators/stubs/model.php b/src/Generators/stubs/model.php index 0574e787..506c42d6 100644 --- a/src/Generators/stubs/model.php +++ b/src/Generators/stubs/model.php @@ -1,102 +1,104 @@ node = $node; - $this->target = $this->resolveNode($target); - $this->position = $position; - - $this->setEventDispatcher($node->getEventDispatcher()); - } - - /** - * Easy static accessor for performing a move operation. - * - * @param \Baum\Node $node - * @param \Baum\Node|int $target - * @param string $position - * @return \Baum\Node - */ - public static function to($node, $target, $position) { - $instance = new static($node, $target, $position); - - return $instance->perform(); - } - - /** - * Perform the move operation. - * - * @return \Baum\Node - */ - public function perform() { - $this->guardAgainstImpossibleMove(); - - if ( $this->fireMoveEvent('moving') === false ) - return $this->node; - - if ( $this->hasChange() ) { - $self = $this; - - $this->node->getConnection()->transaction(function() use ($self) { - $self->updateStructure(); - }); - - $this->target->reload(); - - $this->node->setDepthWithSubtree(); - - $this->node->reload(); + * Move + */ +class Move +{ + + /** + * Node on which the move operation will be performed + * + * @var \Baum\Node + */ + protected $node = null; + + /** + * Destination node + * + * @var \Baum\Node | int + */ + protected $target = null; + + /** + * Move target position, one of: child, left, right, root + * + * @var string + */ + protected $position = null; + + /** + * Memoized 1st boundary. + * + * @var int + */ + protected $_bound1 = null; + + /** + * Memoized 2nd boundary. + * + * @var int + */ + protected $_bound2 = null; + + /** + * Memoized boundaries array. + * + * @var array + */ + protected $_boundaries = null; + + /** + * The event dispatcher instance. + * + * @var \Illuminate\Events\Dispatcher + */ + protected static $dispatcher; + + /** + * Create a new Move class instance. + * + * @param \Baum\Node $node + * @param \Baum\Node|int $target + * @param string $position + * @return void + */ + public function __construct($node, $target, $position) + { + $this->node = $node; + $this->target = $this->resolveNode($target); + $this->position = $position; + + $this->setEventDispatcher($node->getEventDispatcher()); + } + + /** + * Easy static accessor for performing a move operation. + * + * @param \Baum\Node $node + * @param \Baum\Node|int $target + * @param string $position + * @return \Baum\Node + */ + public static function to($node, $target, $position) + { + $instance = new static($node, $target, $position); + + return $instance->perform(); } - $this->fireMoveEvent('moved', false); - - return $this->node; - } - - /** - * Runs the SQL query associated with the update of the indexes affected - * by the move operation. - * - * @return int - */ - public function updateStructure() { - list($a, $b, $c, $d) = $this->boundaries(); - - // select the rows between the leftmost & the rightmost boundaries and apply a lock - $this->applyLockBetween($a, $d); - - $connection = $this->node->getConnection(); - $grammar = $connection->getQueryGrammar(); - - $currentId = $this->quoteIdentifier($this->node->getKey()); - $parentId = $this->quoteIdentifier($this->parentId()); - $leftColumn = $this->node->getLeftColumnName(); - $rightColumn = $this->node->getRightColumnName(); - $parentColumn = $this->node->getParentColumnName(); - $wrappedLeft = $grammar->wrap($leftColumn); - $wrappedRight = $grammar->wrap($rightColumn); - $wrappedParent = $grammar->wrap($parentColumn); - $wrappedId = $grammar->wrap($this->node->getKeyName()); - - $lftSql = "CASE + /** + * Perform the move operation. + * + * @return \Baum\Node + */ + public function perform() + { + $this->guardAgainstImpossibleMove(); + + if ($this->fireMoveEvent('moving') === false) + return $this->node; + + if ($this->hasChange()) { + $self = $this; + + $this->node->getConnection()->transaction(function () use ($self) { + $self->updateStructure(); + }); + + $this->target->reload(); + + $this->node->setDepthWithSubtree(); + + $this->node->reload(); + } + + $this->fireMoveEvent('moved', false); + + return $this->node; + } + + /** + * Runs the SQL query associated with the update of the indexes affected + * by the move operation. + * + * @return int + */ + public function updateStructure() + { + list($a, $b, $c, $d) = $this->boundaries(); + + // select the rows between the leftmost & the rightmost boundaries and apply a lock + $this->applyLockBetween($a, $d); + + $connection = $this->node->getConnection(); + $grammar = $connection->getQueryGrammar(); + + $currentId = $this->quoteIdentifier($this->node->getKey()); + $parentId = $this->quoteIdentifier($this->parentId()); + $leftColumn = $this->node->getLeftColumnName(); + $rightColumn = $this->node->getRightColumnName(); + $parentColumn = $this->node->getParentColumnName(); + $wrappedLeft = $grammar->wrap($leftColumn); + $wrappedRight = $grammar->wrap($rightColumn); + $wrappedParent = $grammar->wrap($parentColumn); + $wrappedId = $grammar->wrap($this->node->getKeyName()); + + $lftSql = "CASE WHEN $wrappedLeft BETWEEN $a AND $b THEN $wrappedLeft + $d - $b WHEN $wrappedLeft BETWEEN $c AND $d THEN $wrappedLeft + $a - $c ELSE $wrappedLeft END"; - $rgtSql = "CASE + $rgtSql = "CASE WHEN $wrappedRight BETWEEN $a AND $b THEN $wrappedRight + $d - $b WHEN $wrappedRight BETWEEN $c AND $d THEN $wrappedRight + $a - $c ELSE $wrappedRight END"; - $parentSql = "CASE + $parentSql = "CASE WHEN $wrappedId = $currentId THEN $parentId ELSE $wrappedParent END"; - $updateConditions = array( - $leftColumn => $connection->raw($lftSql), - $rightColumn => $connection->raw($rgtSql), - $parentColumn => $connection->raw($parentSql) - ); - - if ( $this->node->timestamps ) - $updateConditions[$this->node->getUpdatedAtColumn()] = $this->node->freshTimestamp(); - - return $this->node - ->newNestedSetQuery() - ->where(function($query) use ($leftColumn, $rightColumn, $a, $d) { - $query->whereBetween($leftColumn, array($a, $d)) - ->orWhereBetween($rightColumn, array($a, $d)); - }) - ->update($updateConditions); - } - - /** - * Resolves suplied node. Basically returns the node unchanged if - * supplied parameter is an instance of \Baum\Node. Otherwise it will try - * to find the node in the database. - * - * @param \Baum\node|int - * @return \Baum\Node - */ - protected function resolveNode($node) { - if ( $node instanceof \Baum\Node ) return $node->reload(); - - return $this->node->newNestedSetQuery()->find($node); - } - - /** - * Check wether the current move is possible and if not, rais an exception. - * - * @return void - */ - protected function guardAgainstImpossibleMove() { - if ( !$this->node->exists ) - throw new MoveNotPossibleException('A new node cannot be moved.'); - - if ( array_search($this->position, array('child', 'left', 'right', 'root')) === FALSE ) - throw new MoveNotPossibleException("Position should be one of ['child', 'left', 'right'] but is {$this->position}."); - - if ( !$this->promotingToRoot() ) { - if ( is_null($this->target) ) { - if ( $this->position === 'left' || $this->position === 'right' ) - throw new MoveNotPossibleException("Could not resolve target node. This node cannot move any further to the {$this->position}."); - else - throw new MoveNotPossibleException('Could not resolve target node.'); - } - - if ( $this->node->equals($this->target) ) - throw new MoveNotPossibleException('A node cannot be moved to itself.'); - - if ( $this->target->insideSubtree($this->node) ) - throw new MoveNotPossibleException('A node cannot be moved to a descendant of itself (inside moved tree).'); - - if ( !$this->node->inSameScope($this->target) ) - throw new MoveNotPossibleException('A node cannot be moved to a different scope.'); + $updateConditions = array( + $leftColumn => $connection->raw($lftSql), + $rightColumn => $connection->raw($rgtSql), + $parentColumn => $connection->raw($parentSql) + ); + + if ($this->node->timestamps) + $updateConditions[$this->node->getUpdatedAtColumn()] = $this->node->freshTimestamp(); + + return $this->node + ->newNestedSetQuery() + ->where(function ($query) use ($leftColumn, $rightColumn, $a, $d) { + $query->whereBetween($leftColumn, array($a, $d)) + ->orWhereBetween($rightColumn, array($a, $d)); + }) + ->update($updateConditions); + } + + /** + * Resolves suplied node. Basically returns the node unchanged if + * supplied parameter is an instance of \Baum\Node. Otherwise it will try + * to find the node in the database. + * + * @param \Baum\node|int + * @return \Baum\Node + */ + protected function resolveNode($node) + { + if ($node instanceof \Baum\Node) return $node->reload(); + + return $this->node->newNestedSetQuery()->find($node); + } + + /** + * Check wether the current move is possible and if not, rais an exception. + * + * @return void + */ + protected function guardAgainstImpossibleMove() + { + if (!$this->node->exists) + throw new MoveNotPossibleException('A new node cannot be moved.'); + + if (array_search($this->position, array('child', 'left', 'right', 'root')) === false) + throw new MoveNotPossibleException("Position should be one of ['child', 'left', 'right'] but is {$this->position}."); + + if (!$this->promotingToRoot()) { + if (is_null($this->target)) { + if ($this->position === 'left' || $this->position === 'right') + throw new MoveNotPossibleException("Could not resolve target node. This node cannot move any further to the {$this->position}."); + else + throw new MoveNotPossibleException('Could not resolve target node.'); + } + + if ($this->node->equals($this->target)) + throw new MoveNotPossibleException('A node cannot be moved to itself.'); + + if ($this->target->insideSubtree($this->node)) + throw new MoveNotPossibleException('A node cannot be moved to a descendant of itself (inside moved tree).'); + + if (!$this->node->inSameScope($this->target)) + throw new MoveNotPossibleException('A node cannot be moved to a different scope.'); + } + } + + /** + * Computes the boundary. + * + * @return int + */ + protected function bound1() + { + if (!is_null($this->_bound1)) return $this->_bound1; + + switch ($this->position) { + case 'child': + $this->_bound1 = $this->target->getRight(); + break; + + case 'left': + $this->_bound1 = $this->target->getLeft(); + break; + + case 'right': + $this->_bound1 = $this->target->getRight() + 1; + break; + + case 'root': + $this->_bound1 = $this->node->newNestedSetQuery()->max($this->node->getRightColumnName()) + 1; + break; + } + + $this->_bound1 = (($this->_bound1 > $this->node->getRight()) ? $this->_bound1 - 1 : $this->_bound1); + return $this->_bound1; + } + + /** + * Computes the other boundary. + * TODO: Maybe find a better name for this... ¿? + * + * @return int + */ + protected function bound2() + { + if (!is_null($this->_bound2)) return $this->_bound2; + + $this->_bound2 = (($this->bound1() > $this->node->getRight()) ? $this->node->getRight() + 1 : $this->node->getLeft() - 1); + return $this->_bound2; } - } - - /** - * Computes the boundary. - * - * @return int - */ - protected function bound1() { - if ( !is_null($this->_bound1) ) return $this->_bound1; - - switch ( $this->position ) { - case 'child': - $this->_bound1 = $this->target->getRight(); - break; - - case 'left': - $this->_bound1 = $this->target->getLeft(); - break; - - case 'right': - $this->_bound1 = $this->target->getRight() + 1; - break; - - case 'root': - $this->_bound1 = $this->node->newNestedSetQuery()->max($this->node->getRightColumnName()) + 1; - break; + + /** + * Computes the boundaries array. + * + * @return array + */ + protected function boundaries() + { + if (!is_null($this->_boundaries)) return $this->_boundaries; + + // we have defined the boundaries of two non-overlapping intervals, + // so sorting puts both the intervals and their boundaries in order + $this->_boundaries = array( + $this->node->getLeft(), + $this->node->getRight(), + $this->bound1(), + $this->bound2() + ); + sort($this->_boundaries); + + return $this->_boundaries; + } + + /** + * Computes the new parent id for the node being moved. + * + * @return int + */ + protected function parentId() + { + switch ($this->position) { + case 'root': + return null; + + case 'child': + return $this->target->getKey(); + + default: + return $this->target->getParentId(); + } + } + + /** + * Check wether there should be changes in the downward tree structure. + * + * @return boolean + */ + protected function hasChange() + { + return !($this->bound1() == $this->node->getRight() || $this->bound1() == $this->node->getLeft()); + } + + /** + * Check if we are promoting the provided instance to a root node. + * + * @return boolean + */ + protected function promotingToRoot() + { + return ($this->position == 'root'); + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public static function getEventDispatcher() + { + return static::$dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Events\Dispatcher + * @return void + */ + public static function setEventDispatcher(Dispatcher $dispatcher) + { + static::$dispatcher = $dispatcher; + } + + /** + * Fire the given move event for the model. + * + * @param string $event + * @param bool $halt + * @return mixed + */ + protected function fireMoveEvent($event, $halt = true) + { + if (!isset(static::$dispatcher)) return true; + + // Basically the same as \Illuminate\Database\Eloquent\Model->fireModelEvent + // but we relay the event into the node instance. + $event = "eloquent.{$event}: " . get_class($this->node); + + $method = $halt ? 'until' : 'dispatch'; + + return static::$dispatcher->$method($event, $this->node); + } + + /** + * Quotes an identifier for being used in a database query. + * + * @param mixed $value + * @return string + */ + protected function quoteIdentifier($value) + { + if (is_null($value)) + return 'NULL'; + + $connection = $this->node->getConnection(); + + $pdo = $connection->getPdo(); + + return $pdo->quote($value); } - $this->_bound1 = (($this->_bound1 > $this->node->getRight()) ? $this->_bound1 - 1 : $this->_bound1); - return $this->_bound1; - } - - /** - * Computes the other boundary. - * TODO: Maybe find a better name for this... ¿? - * - * @return int - */ - protected function bound2() { - if ( !is_null($this->_bound2) ) return $this->_bound2; - - $this->_bound2 = (($this->bound1() > $this->node->getRight()) ? $this->node->getRight() + 1 : $this->node->getLeft() - 1); - return $this->_bound2; - } - - /** - * Computes the boundaries array. - * - * @return array - */ - protected function boundaries() { - if ( !is_null($this->_boundaries) ) return $this->_boundaries; - - // we have defined the boundaries of two non-overlapping intervals, - // so sorting puts both the intervals and their boundaries in order - $this->_boundaries = array( - $this->node->getLeft() , - $this->node->getRight() , - $this->bound1() , - $this->bound2() - ); - sort($this->_boundaries); - - return $this->_boundaries; - } - - /** - * Computes the new parent id for the node being moved. - * - * @return int - */ - protected function parentId() { - switch( $this->position ) { - case 'root': - return NULL; - - case 'child': - return $this->target->getKey(); - - default: - return $this->target->getParentId(); + /** + * Applies a lock to the rows between the supplied index boundaries. + * + * @param int $lft + * @param int $rgt + * @return void + */ + protected function applyLockBetween($lft, $rgt) + { + $this->node->newQuery() + ->where($this->node->getLeftColumnName(), '>=', $lft) + ->where($this->node->getRightColumnName(), '<=', $rgt) + ->select($this->node->getKeyName()) + ->lockForUpdate() + ->get(); } - } - - /** - * Check wether there should be changes in the downward tree structure. - * - * @return boolean - */ - protected function hasChange() { - return !( $this->bound1() == $this->node->getRight() || $this->bound1() == $this->node->getLeft() ); - } - - /** - * Check if we are promoting the provided instance to a root node. - * - * @return boolean - */ - protected function promotingToRoot() { - return ($this->position == 'root'); - } - - /** - * Get the event dispatcher instance. - * - * @return \Illuminate\Events\Dispatcher - */ - public static function getEventDispatcher() { - return static::$dispatcher; - } - - /** - * Set the event dispatcher instance. - * - * @param \Illuminate\Events\Dispatcher - * @return void - */ - public static function setEventDispatcher(Dispatcher $dispatcher) { - static::$dispatcher = $dispatcher; - } - - /** - * Fire the given move event for the model. - * - * @param string $event - * @param bool $halt - * @return mixed - */ - protected function fireMoveEvent($event, $halt = true) { - if ( !isset(static::$dispatcher) ) return true; - - // Basically the same as \Illuminate\Database\Eloquent\Model->fireModelEvent - // but we relay the event into the node instance. - $event = "eloquent.{$event}: ".get_class($this->node); - - $method = $halt ? 'until' : 'dispatch'; - - return static::$dispatcher->$method($event, $this->node); - } - - /** - * Quotes an identifier for being used in a database query. - * - * @param mixed $value - * @return string - */ - protected function quoteIdentifier($value) { - if ( is_null($value) ) - return 'NULL'; - - $connection = $this->node->getConnection(); - - $pdo = $connection->getPdo(); - - return $pdo->quote($value); - } - - /** - * Applies a lock to the rows between the supplied index boundaries. - * - * @param int $lft - * @param int $rgt - * @return void - */ - protected function applyLockBetween($lft, $rgt) { - $this->node->newQuery() - ->where($this->node->getLeftColumnName(), '>=', $lft) - ->where($this->node->getRightColumnName(), '<=', $rgt) - ->select($this->node->getKeyName()) - ->lockForUpdate() - ->get(); - } } diff --git a/src/MoveNotPossibleException.php b/src/MoveNotPossibleException.php index 4832ec2c..8ca8cf19 100644 --- a/src/MoveNotPossibleException.php +++ b/src/MoveNotPossibleException.php @@ -1,4 +1,7 @@ parentColumn; } /** * Get the table qualified parent column name. - * - * @return string - */ - public function getQualifiedParentColumnName() { + * + * @return string + */ + public function getQualifiedParentColumnName() + { $this->qualifyColumn($this->getParentColumnName()); } @@ -26,7 +29,8 @@ public function getQualifiedParentColumnName() { * * @return string */ - public function getLeftColumnName() { + public function getLeftColumnName() + { return $this->leftColumn; } @@ -35,7 +39,8 @@ public function getLeftColumnName() { * * @return string */ - public function getQualifiedLeftColumnName() { + public function getQualifiedLeftColumnName() + { return $this->qualifyColumn($this->getLeftColumnName()); } @@ -44,8 +49,9 @@ public function getQualifiedLeftColumnName() { * * @return int */ - public function getLeft() { - return $this->getAttribute($this->getLeftColumnName()); + public function getLeft() + { + return $this->getAttribute($this->getLeftColumnName()); } /** @@ -53,8 +59,9 @@ public function getLeft() { * * @return string */ - public function getRightColumnName() { - return $this->rightColumn; + public function getRightColumnName() + { + return $this->rightColumn; } /** @@ -62,8 +69,9 @@ public function getRightColumnName() { * * @return string */ - public function getQualifiedRightColumnName() { - return $this->getTable() . '.' . $this->getRightColumnName(); + public function getQualifiedRightColumnName() + { + return $this->getTable() . '.' . $this->getRightColumnName(); } /** @@ -71,8 +79,9 @@ public function getQualifiedRightColumnName() { * * @return int */ - public function getRight() { - return $this->getAttribute($this->getRightColumnName()); + public function getRight() + { + return $this->getAttribute($this->getRightColumnName()); } /** @@ -80,8 +89,9 @@ public function getRight() { * * @return string */ - public function getDepthColumnName() { - return $this->depthColumn; + public function getDepthColumnName() + { + return $this->depthColumn; } /** @@ -89,8 +99,9 @@ public function getDepthColumnName() { * * @return string */ - public function getQualifiedDepthColumnName() { - return $this->getTable() . '.' . $this->getDepthColumnName(); + public function getQualifiedDepthColumnName() + { + return $this->getTable() . '.' . $this->getDepthColumnName(); } /** @@ -98,8 +109,9 @@ public function getQualifiedDepthColumnName() { * * @return int */ - public function getDepth() { - return $this->getAttribute($this->getDepthColumnName()); + public function getDepth() + { + return $this->getAttribute($this->getDepthColumnName()); } /** @@ -107,8 +119,9 @@ public function getDepth() { * * @return string */ - public function getOrderColumnName() { - return is_null($this->orderColumn) ? $this->getLeftColumnName() : $this->orderColumn; + public function getOrderColumnName() + { + return is_null($this->orderColumn) ? $this->getLeftColumnName() : $this->orderColumn; } /** @@ -116,8 +129,9 @@ public function getOrderColumnName() { * * @return string */ - public function getQualifiedOrderColumnName() { - return $this->getTable() . '.' . $this->getOrderColumnName(); + public function getQualifiedOrderColumnName() + { + return $this->getTable() . '.' . $this->getOrderColumnName(); } /** @@ -125,8 +139,9 @@ public function getQualifiedOrderColumnName() { * * @return mixed */ - public function getOrder() { - return $this->getAttribute($this->getOrderColumnName()); + public function getOrder() + { + return $this->getAttribute($this->getOrderColumnName()); } /** @@ -134,8 +149,9 @@ public function getOrder() { * * @return array */ - public function getScopedColumns() { - return (array) $this->scoped; + public function getScopedColumns() + { + return (array)$this->scoped; } /** @@ -143,14 +159,16 @@ public function getScopedColumns() { * * @return array */ - public function getQualifiedScopedColumns() { - if ( !$this->isScoped() ) - return $this->getScopedColumns(); + public function getQualifiedScopedColumns() + { + if (!$this->isScoped()) + return $this->getScopedColumns(); - $prefix = $this->getTable() . '.'; + $prefix = $this->getTable() . '.'; - return array_map(function($c) use ($prefix) { - return $prefix . $c; }, $this->getScopedColumns()); + return array_map(function ($c) use ($prefix) { + return $prefix . $c; + }, $this->getScopedColumns()); } } diff --git a/src/Node.php b/src/Node.php index 8dcab130..fd1ae84a 100644 --- a/src/Node.php +++ b/src/Node.php @@ -1,4 +1,5 @@ setDefaultLeftAndRight(); - }); - - static::saving(function($node) { - $node->storeNewParent(); - }); - - static::saved(function($node) { - $node->moveToNewParent(); - $node->setDepth(); - }); - - static::deleting(function($node) { - $node->destroyDescendants(); - }); - - if ( static::softDeletesEnabled() ) { - static::restoring(function($node) { - $node->shiftSiblingsForRestore(); - }); - - static::restored(function($node) { - $node->restoreDescendants(); - }); - } - } - - /** - * Get the parent column name. - * - * @return string - */ - public function getParentColumnName() { - return $this->parentColumn; - } - - /** - * Get the table qualified parent column name. - * - * @return string - */ - public function getQualifiedParentColumnName() { - return $this->getTable(). '.' .$this->getParentColumnName(); - } - - /** - * Get the value of the models "parent_id" field. - * - * @return int - */ - public function getParentId() { - return $this->getAttribute($this->getparentColumnName()); - } - - /** - * Get the "left" field column name. - * - * @return string - */ - public function getLeftColumnName() { - return $this->leftColumn; - } - - /** - * Get the table qualified "left" field column name. - * - * @return string - */ - public function getQualifiedLeftColumnName() { - return $this->getTable() . '.' . $this->getLeftColumnName(); - } - - /** - * Get the value of the model's "left" field. - * - * @return int - */ - public function getLeft() { - return $this->getAttribute($this->getLeftColumnName()); - } - - /** - * Get the "right" field column name. - * - * @return string - */ - public function getRightColumnName() { - return $this->rightColumn; - } - - /** - * Get the table qualified "right" field column name. - * - * @return string - */ - public function getQualifiedRightColumnName() { - return $this->getTable() . '.' . $this->getRightColumnName(); - } - - /** - * Get the value of the model's "right" field. - * - * @return int - */ - public function getRight() { - return $this->getAttribute($this->getRightColumnName()); - } - - /** - * Get the "depth" field column name. - * - * @return string - */ - public function getDepthColumnName() { - return $this->depthColumn; - } - - /** - * Get the table qualified "depth" field column name. - * - * @return string - */ - public function getQualifiedDepthColumnName() { - return $this->getTable() . '.' . $this->getDepthColumnName(); - } - - /** - * Get the model's "depth" value. - * - * @return int - */ - public function getDepth() { - return $this->getAttribute($this->getDepthColumnName()); - } - - /** - * Get the "order" field column name. - * - * @return string - */ - public function getOrderColumnName() { - return is_null($this->orderColumn) ? $this->getLeftColumnName() : $this->orderColumn; - } - - /** - * Get the table qualified "order" field column name. - * - * @return string - */ - public function getQualifiedOrderColumnName() { - return $this->getTable() . '.' . $this->getOrderColumnName(); - } - - /** - * Get the model's "order" value. - * - * @return mixed - */ - public function getOrder() { - return $this->getAttribute($this->getOrderColumnName()); - } - - /** - * Get the column names which define our scope - * - * @return array - */ - public function getScopedColumns() { - return (array) $this->scoped; - } - - /** - * Get the qualified column names which define our scope - * - * @return array - */ - public function getQualifiedScopedColumns() { - if ( !$this->isScoped() ) - return $this->getScopedColumns(); - - $prefix = $this->getTable() . '.'; - - return array_map(function($c) use ($prefix) { - return $prefix . $c; }, $this->getScopedColumns()); - } - - /** - * Returns wether this particular node instance is scoped by certain fields - * or not. - * - * @return boolean - */ - public function isScoped() { - return !!(count($this->getScopedColumns()) > 0); - } - - /** - * Parent relation (self-referential) 1-1. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function parent() { - return $this->belongsTo(get_class($this), $this->getParentColumnName()); - } - - /** - * Children relation (self-referential) 1-N. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function children() { - return $this->hasMany(get_class($this), $this->getParentColumnName()) - ->orderBy($this->getOrderColumnName()); - } - - /** - * Get a new "scoped" query builder for the Node's model. - * - * @param bool $excludeDeleted - * @return \Illuminate\Database\Eloquent\Builder|static - */ - public function newNestedSetQuery($excludeDeleted = true) { - $builder = $this->newQuery($excludeDeleted)->orderBy($this->getQualifiedOrderColumnName()); - - if ( $this->isScoped() ) { - foreach($this->scoped as $scopeFld) - $builder->where($scopeFld, '=', $this->$scopeFld); - } - - return $builder; - } - - /** - * Overload new Collection - * - * @param array $models - * @return \Baum\Extensions\Eloquent\Collection - */ - public function newCollection(array $models = array()) { - return new Collection($models); - } - - /** - * Get all of the nodes from the database. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection|static[] - */ - public static function all($columns = array('*')) { - $instance = new static; - - return $instance->newQuery() - ->orderBy($instance->getQualifiedOrderColumnName()) - ->get(); - } - - /** - * Returns the first root node. - * - * @return NestedSet - */ - public static function root() { - return static::roots()->first(); - } - - /** - * Static query scope. Returns a query scope with all root nodes. - * - * @return \Illuminate\Database\Query\Builder - */ - public static function roots() { - $instance = new static; - - return $instance->newQuery() - ->whereNull($instance->getParentColumnName()) - ->orderBy($instance->getQualifiedOrderColumnName()); - } - - /** - * Static query scope. Returns a query scope with all nodes which are at - * the end of a branch. - * - * @return \Illuminate\Database\Query\Builder - */ - public static function allLeaves() { - $instance = new static; - - $grammar = $instance->getConnection()->getQueryGrammar(); - - $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName()); - $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName()); - - return $instance->newQuery() - ->whereRaw($rgtCol . ' - ' . $lftCol . ' = 1') - ->orderBy($instance->getQualifiedOrderColumnName()); - } - - /** - * Static query scope. Returns a query scope with all nodes which are at - * the middle of a branch (not root and not leaves). - * - * @return \Illuminate\Database\Query\Builder - */ - public static function allTrunks() { - $instance = new static; - - $grammar = $instance->getConnection()->getQueryGrammar(); - - $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName()); - $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName()); - - return $instance->newQuery() - ->whereNotNull($instance->getParentColumnName()) - ->whereRaw($rgtCol . ' - ' . $lftCol . ' != 1') - ->orderBy($instance->getQualifiedOrderColumnName()); - } - - /** - * Checks wether the underlying Nested Set structure is valid. - * - * @return boolean - */ - public static function isValidNestedSet() { - $validator = new SetValidator(new static); - - return $validator->passes(); - } - - /** - * Rebuilds the structure of the current Nested Set. - * - * @param bool $force - * @return void - */ - public static function rebuild($force = false) { - $builder = new SetBuilder(new static); - - $builder->rebuild($force); - } - - /** - * Maps the provided tree structure into the database. - * - * @param array|\Illuminate\Support\Contracts\ArrayableInterface - * @return boolean - */ - public static function buildTree($nodeList) { - return with(new static)->makeTree($nodeList); - } - - /** - * Query scope which extracts a certain node object from the current query - * expression. - * - * @return \Illuminate\Database\Query\Builder - */ - public function scopeWithoutNode($query, $node) { - return $query->where($node->getKeyName(), '!=', $node->getKey()); - } - - /** - * Extracts current node (self) from current query expression. - * - * @return \Illuminate\Database\Query\Builder - */ - public function scopeWithoutSelf($query) { - return $this->scopeWithoutNode($query, $this); - } - - /** - * Extracts first root (from the current node p-o-v) from current query - * expression. - * - * @return \Illuminate\Database\Query\Builder - */ - public function scopeWithoutRoot($query) { - return $this->scopeWithoutNode($query, $this->getRoot()); - } - - /** - * Provides a depth level limit for the query. - * - * @param query \Illuminate\Database\Query\Builder - * @param limit integer - * @return \Illuminate\Database\Query\Builder - */ - public function scopeLimitDepth($query, $limit) { - $depth = $this->exists ? $this->getDepth() : $this->getLevel(); - $max = $depth + $limit; - $scopes = array($depth, $max); - - return $query->whereBetween($this->getDepthColumnName(), array(min($scopes), max($scopes))); - } - - /** - * Returns true if this is a root node. - * - * @return boolean - */ - public function isRoot() { - return is_null($this->getParentId()); - } - - /** - * Returns true if this is a leaf node (end of a branch). - * - * @return boolean - */ - public function isLeaf() { - return $this->exists && ($this->getRight() - $this->getLeft() == 1); - } - - /** - * Returns true if this is a trunk node (not root or leaf). - * - * @return boolean - */ - public function isTrunk() { - return !$this->isRoot() && !$this->isLeaf(); - } - - /** - * Returns true if this is a child node. - * - * @return boolean - */ - public function isChild() { - return !$this->isRoot(); - } - - /** - * Returns the root node starting at the current node. - * - * @return NestedSet - */ - public function getRoot() { - if ( $this->exists ) { - return $this->ancestorsAndSelf()->whereNull($this->getParentColumnName())->first(); - } else { - $parentId = $this->getParentId(); - - if ( !is_null($parentId) && $currentParent = static::find($parentId) ) { - return $currentParent->getRoot(); - } else { +abstract class Node extends Model +{ + + /** + * Column name to store the reference to parent's node. + * + * @var string + */ + protected $parentColumn = 'parent_id'; + + /** + * Column name for left index. + * + * @var string + */ + protected $leftColumn = 'lft'; + + /** + * Column name for right index. + * + * @var string + */ + protected $rightColumn = 'rgt'; + + /** + * Column name for depth field. + * + * @var string + */ + protected $depthColumn = 'depth'; + + /** + * Column to perform the default sorting + * + * @var string + */ + protected $orderColumn = null; + + /** + * Guard NestedSet fields from mass-assignment. + * + * @var array + */ + protected $guarded = array('id', 'parent_id', 'lft', 'rgt', 'depth'); + + /** + * Indicates whether we should move to a new parent. + * + * @var int + */ + protected static $moveToNewParentId = null; + + /** + * Columns which restrict what we consider our Nested Set list + * + * @var array + */ + protected $scoped = array(); + + /** + * The "booting" method of the model. + * + * We'll use this method to register event listeners on a Node instance as + * suggested in the beta documentation... + * + * TODO: + * + * - Find a way to avoid needing to declare the called methods "public" + * as registering the event listeners *inside* this methods does not give + * us an object context. + * + * Events: + * + * 1. "creating": Before creating a new Node we'll assign a default value + * for the left and right indexes. + * + * 2. "saving": Before saving, we'll perform a check to see if we have to + * move to another parent. + * + * 3. "saved": Move to the new parent after saving if needed and re-set + * depth. + * + * 4. "deleting": Before delete we should prune all children and update + * the left and right indexes for the remaining nodes. + * + * 5. (optional) "restoring": Before a soft-delete node restore operation, + * shift its siblings. + * + * 6. (optional) "restore": After having restored a soft-deleted node, + * restore all of its descendants. + * + * @return void + */ + protected static function boot() + { + parent::boot(); + + static::creating(function ($node) { + $node->setDefaultLeftAndRight(); + }); + + static::saving(function ($node) { + $node->storeNewParent(); + }); + + static::saved(function ($node) { + $node->moveToNewParent(); + $node->setDepth(); + }); + + static::deleting(function ($node) { + $node->destroyDescendants(); + }); + + if (static::softDeletesEnabled()) { + static::restoring(function ($node) { + $node->shiftSiblingsForRestore(); + }); + + static::restored(function ($node) { + $node->restoreDescendants(); + }); + } + } + + /** + * Get the parent column name. + * + * @return string + */ + public function getParentColumnName() + { + return $this->parentColumn; + } + + /** + * Get the table qualified parent column name. + * + * @return string + */ + public function getQualifiedParentColumnName() + { + return $this->getTable() . '.' . $this->getParentColumnName(); + } + + /** + * Get the value of the models "parent_id" field. + * + * @return int + */ + public function getParentId() + { + return $this->getAttribute($this->getparentColumnName()); + } + + /** + * Get the "left" field column name. + * + * @return string + */ + public function getLeftColumnName() + { + return $this->leftColumn; + } + + /** + * Get the table qualified "left" field column name. + * + * @return string + */ + public function getQualifiedLeftColumnName() + { + return $this->getTable() . '.' . $this->getLeftColumnName(); + } + + /** + * Get the value of the model's "left" field. + * + * @return int + */ + public function getLeft() + { + return $this->getAttribute($this->getLeftColumnName()); + } + + /** + * Get the "right" field column name. + * + * @return string + */ + public function getRightColumnName() + { + return $this->rightColumn; + } + + /** + * Get the table qualified "right" field column name. + * + * @return string + */ + public function getQualifiedRightColumnName() + { + return $this->getTable() . '.' . $this->getRightColumnName(); + } + + /** + * Get the value of the model's "right" field. + * + * @return int + */ + public function getRight() + { + return $this->getAttribute($this->getRightColumnName()); + } + + /** + * Get the "depth" field column name. + * + * @return string + */ + public function getDepthColumnName() + { + return $this->depthColumn; + } + + /** + * Get the table qualified "depth" field column name. + * + * @return string + */ + public function getQualifiedDepthColumnName() + { + return $this->getTable() . '.' . $this->getDepthColumnName(); + } + + /** + * Get the model's "depth" value. + * + * @return int + */ + public function getDepth() + { + return $this->getAttribute($this->getDepthColumnName()); + } + + /** + * Get the "order" field column name. + * + * @return string + */ + public function getOrderColumnName() + { + return is_null($this->orderColumn) ? $this->getLeftColumnName() : $this->orderColumn; + } + + /** + * Get the table qualified "order" field column name. + * + * @return string + */ + public function getQualifiedOrderColumnName() + { + return $this->getTable() . '.' . $this->getOrderColumnName(); + } + + /** + * Get the model's "order" value. + * + * @return mixed + */ + public function getOrder() + { + return $this->getAttribute($this->getOrderColumnName()); + } + + /** + * Get the column names which define our scope + * + * @return array + */ + public function getScopedColumns() + { + return (array)$this->scoped; + } + + /** + * Get the qualified column names which define our scope + * + * @return array + */ + public function getQualifiedScopedColumns() + { + if (!$this->isScoped()) + return $this->getScopedColumns(); + + $prefix = $this->getTable() . '.'; + + return array_map(function ($c) use ($prefix) { + return $prefix . $c; + }, $this->getScopedColumns()); + } + + /** + * Returns wether this particular node instance is scoped by certain fields + * or not. + * + * @return boolean + */ + public function isScoped() + { + return !!(count($this->getScopedColumns()) > 0); + } + + /** + * Parent relation (self-referential) 1-1. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function parent() + { + return $this->belongsTo(get_class($this), $this->getParentColumnName()); + } + + /** + * Children relation (self-referential) 1-N. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function children() + { + return $this->hasMany(get_class($this), $this->getParentColumnName()) + ->orderBy($this->getOrderColumnName()); + } + + /** + * Get a new "scoped" query builder for the Node's model. + * + * @param bool $excludeDeleted + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newNestedSetQuery($excludeDeleted = true) + { + $builder = $this->newQuery($excludeDeleted)->orderBy($this->getQualifiedOrderColumnName()); + + if ($this->isScoped()) { + foreach ($this->scoped as $scopeFld) + $builder->where($scopeFld, '=', $this->$scopeFld); + } + + return $builder; + } + + /** + * Overload new Collection + * + * @param array $models + * @return \Baum\Extensions\Eloquent\Collection + */ + public function newCollection(array $models = array()) + { + return new Collection($models); + } + + /** + * Get all of the nodes from the database. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public static function all($columns = array('*')) + { + $instance = new static; + + return $instance->newQuery() + ->orderBy($instance->getQualifiedOrderColumnName()) + ->get(); + } + + /** + * Returns the first root node. + * + * @return NestedSet + */ + public static function root() + { + return static::roots()->first(); + } + + /** + * Static query scope. Returns a query scope with all root nodes. + * + * @return \Illuminate\Database\Query\Builder + */ + public static function roots() + { + $instance = new static; + + return $instance->newQuery() + ->whereNull($instance->getParentColumnName()) + ->orderBy($instance->getQualifiedOrderColumnName()); + } + + /** + * Static query scope. Returns a query scope with all nodes which are at + * the end of a branch. + * + * @return \Illuminate\Database\Query\Builder + */ + public static function allLeaves() + { + $instance = new static; + + $grammar = $instance->getConnection()->getQueryGrammar(); + + $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName()); + $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName()); + + return $instance->newQuery() + ->whereRaw($rgtCol . ' - ' . $lftCol . ' = 1') + ->orderBy($instance->getQualifiedOrderColumnName()); + } + + /** + * Static query scope. Returns a query scope with all nodes which are at + * the middle of a branch (not root and not leaves). + * + * @return \Illuminate\Database\Query\Builder + */ + public static function allTrunks() + { + $instance = new static; + + $grammar = $instance->getConnection()->getQueryGrammar(); + + $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName()); + $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName()); + + return $instance->newQuery() + ->whereNotNull($instance->getParentColumnName()) + ->whereRaw($rgtCol . ' - ' . $lftCol . ' != 1') + ->orderBy($instance->getQualifiedOrderColumnName()); + } + + /** + * Checks wether the underlying Nested Set structure is valid. + * + * @return boolean + */ + public static function isValidNestedSet() + { + $validator = new SetValidator(new static); + + return $validator->passes(); + } + + /** + * Rebuilds the structure of the current Nested Set. + * + * @param bool $force + * @return void + */ + public static function rebuild($force = false) + { + $builder = new SetBuilder(new static); + + $builder->rebuild($force); + } + + /** + * Maps the provided tree structure into the database. + * + * @param array|\Illuminate\Contracts\Support\Arrayable + * @return boolean + */ + public static function buildTree($nodeList) + { + return with(new static)->makeTree($nodeList); + } + + /** + * Query scope which extracts a certain node object from the current query + * expression. + * + * @return \Illuminate\Database\Query\Builder + */ + public function scopeWithoutNode($query, $node) + { + return $query->where($node->getKeyName(), '!=', $node->getKey()); + } + + /** + * Extracts current node (self) from current query expression. + * + * @return \Illuminate\Database\Query\Builder + */ + public function scopeWithoutSelf($query) + { + return $this->scopeWithoutNode($query, $this); + } + + /** + * Extracts first root (from the current node p-o-v) from current query + * expression. + * + * @return \Illuminate\Database\Query\Builder + */ + public function scopeWithoutRoot($query) + { + return $this->scopeWithoutNode($query, $this->getRoot()); + } + + /** + * Provides a depth level limit for the query. + * + * @param query \Illuminate\Database\Query\Builder + * @param limit integer + * @return \Illuminate\Database\Query\Builder + */ + public function scopeLimitDepth($query, $limit) + { + $depth = $this->exists ? $this->getDepth() : $this->getLevel(); + $max = $depth + $limit; + $scopes = array($depth, $max); + + return $query->whereBetween($this->getDepthColumnName(), array(min($scopes), max($scopes))); + } + + /** + * Returns true if this is a root node. + * + * @return boolean + */ + public function isRoot() + { + return is_null($this->getParentId()); + } + + /** + * Returns true if this is a leaf node (end of a branch). + * + * @return boolean + */ + public function isLeaf() + { + return $this->exists && ($this->getRight() - $this->getLeft() == 1); + } + + /** + * Returns true if this is a trunk node (not root or leaf). + * + * @return boolean + */ + public function isTrunk() + { + return !$this->isRoot() && !$this->isLeaf(); + } + + /** + * Returns true if this is a child node. + * + * @return boolean + */ + public function isChild() + { + return !$this->isRoot(); + } + + /** + * Returns the root node starting at the current node. + * + * @return NestedSet + */ + public function getRoot() + { + if ($this->exists) { + return $this->ancestorsAndSelf()->whereNull($this->getParentColumnName())->first(); + } else { + $parentId = $this->getParentId(); + + if (!is_null($parentId) && $currentParent = static::find($parentId)) { + return $currentParent->getRoot(); + } else { + return $this; + } + } + } + + /** + * Instance scope which targes all the ancestor chain nodes including + * the current one. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function ancestorsAndSelf() + { + return $this->newNestedSetQuery() + ->where($this->getLeftColumnName(), '<=', $this->getLeft()) + ->where($this->getRightColumnName(), '>=', $this->getRight()); + } + + /** + * Get all the ancestor chain from the database including the current node. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAncestorsAndSelf($columns = array('*')) + { + return $this->ancestorsAndSelf()->get($columns); + } + + /** + * Get all the ancestor chain from the database including the current node + * but without the root node. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAncestorsAndSelfWithoutRoot($columns = array('*')) + { + return $this->ancestorsAndSelf()->withoutRoot()->get($columns); + } + + /** + * Instance scope which targets all the ancestor chain nodes excluding + * the current one. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function ancestors() + { + return $this->ancestorsAndSelf()->withoutSelf(); + } + + /** + * Get all the ancestor chain from the database excluding the current node. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAncestors($columns = array('*')) + { + return $this->ancestors()->get($columns); + } + + /** + * Get all the ancestor chain from the database excluding the current node + * and the root node (from the current node's perspective). + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAncestorsWithoutRoot($columns = array('*')) + { + return $this->ancestors()->withoutRoot()->get($columns); + } + + /** + * Instance scope which targets all children of the parent, including self. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function siblingsAndSelf() + { + return $this->newNestedSetQuery() + ->where($this->getParentColumnName(), $this->getParentId()); + } + + /** + * Get all children of the parent, including self. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getSiblingsAndSelf($columns = array('*')) + { + return $this->siblingsAndSelf()->get($columns); + } + + /** + * Instance scope targeting all children of the parent, except self. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function siblings() + { + return $this->siblingsAndSelf()->withoutSelf(); + } + + /** + * Return all children of the parent, except self. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getSiblings($columns = array('*')) + { + return $this->siblings()->get($columns); + } + + /** + * Instance scope targeting all of its nested children which do not have + * children. + * + * @return \Illuminate\Database\Query\Builder + */ + public function leaves() + { + $grammar = $this->getConnection()->getQueryGrammar(); + + $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName()); + $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName()); + + return $this->descendants() + ->whereRaw($rgtCol . ' - ' . $lftCol . ' = 1'); + } + + /** + * Return all of its nested children which do not have children. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getLeaves($columns = array('*')) + { + return $this->leaves()->get($columns); + } + + /** + * Instance scope targeting all of its nested children which are between the + * root and the leaf nodes (middle branch). + * + * @return \Illuminate\Database\Query\Builder + */ + public function trunks() + { + $grammar = $this->getConnection()->getQueryGrammar(); + + $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName()); + $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName()); + + return $this->descendants() + ->whereNotNull($this->getQualifiedParentColumnName()) + ->whereRaw($rgtCol . ' - ' . $lftCol . ' != 1'); + } + + /** + * Return all of its nested children which are trunks. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getTrunks($columns = array('*')) + { + return $this->trunks()->get($columns); + } + + /** + * Scope targeting itself and all of its nested children. + * + * @return \Illuminate\Database\Query\Builder + */ + public function descendantsAndSelf() + { + return $this->newNestedSetQuery() + ->where($this->getLeftColumnName(), '>=', $this->getLeft()) + ->where($this->getLeftColumnName(), '<', $this->getRight()); + } + + /** + * Retrieve all nested children an self. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getDescendantsAndSelf($columns = array('*')) + { + if (is_array($columns)) + return $this->descendantsAndSelf()->get($columns); + + $arguments = func_get_args(); + + $limit = intval(array_shift($arguments)); + $columns = array_shift($arguments) ?: array('*'); + + return $this->descendantsAndSelf()->limitDepth($limit)->get($columns); + } + + /** + * Set of all children & nested children. + * + * @return \Illuminate\Database\Query\Builder + */ + public function descendants() + { + return $this->descendantsAndSelf()->withoutSelf(); + } + + /** + * Retrieve all of its children & nested children. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getDescendants($columns = array('*')) + { + if (is_array($columns)) + return $this->descendants()->get($columns); + + $arguments = func_get_args(); + + $limit = intval(array_shift($arguments)); + $columns = array_shift($arguments) ?: array('*'); + + return $this->descendants()->limitDepth($limit)->get($columns); + } + + /** + * Set of "immediate" descendants (aka children), alias for the children relation. + * + * @return \Illuminate\Database\Query\Builder + */ + public function immediateDescendants() + { + return $this->children(); + } + + /** + * Retrive all of its "immediate" descendants. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getImmediateDescendants($columns = array('*')) + { + return $this->children()->get($columns); + } + + /** + * Returns the level of this node in the tree. + * Root level is 0. + * + * @return int + */ + public function getLevel() + { + if (is_null($this->getParentId())) + return 0; + + return $this->computeLevel(); + } + + /** + * Returns true if node is a descendant. + * + * @param NestedSet + * @return boolean + */ + public function isDescendantOf($other) + { + return ( + $this->getLeft() > $other->getLeft() && + $this->getLeft() < $other->getRight() && + $this->inSameScope($other) + ); + } + + /** + * Returns true if node is self or a descendant. + * + * @param NestedSet + * @return boolean + */ + public function isSelfOrDescendantOf($other) + { + return ( + $this->getLeft() >= $other->getLeft() && + $this->getLeft() < $other->getRight() && + $this->inSameScope($other) + ); + } + + /** + * Returns true if node is an ancestor. + * + * @param NestedSet + * @return boolean + */ + public function isAncestorOf($other) + { + return ( + $this->getLeft() < $other->getLeft() && + $this->getRight() > $other->getLeft() && + $this->inSameScope($other) + ); + } + + /** + * Returns true if node is self or an ancestor. + * + * @param NestedSet + * @return boolean + */ + public function isSelfOrAncestorOf($other) + { + return ( + $this->getLeft() <= $other->getLeft() && + $this->getRight() > $other->getLeft() && + $this->inSameScope($other) + ); + } + + /** + * Returns the first sibling to the left. + * + * @return NestedSet + */ + public function getLeftSibling() + { + return $this->siblings() + ->where($this->getLeftColumnName(), '<', $this->getLeft()) + ->orderBy($this->getOrderColumnName(), 'desc') + ->get() + ->last(); + } + + /** + * Returns the first sibling to the right. + * + * @return NestedSet + */ + public function getRightSibling() + { + return $this->siblings() + ->where($this->getLeftColumnName(), '>', $this->getLeft()) + ->first(); + } + + /** + * Find the left sibling and move to left of it. + * + * @return \Baum\Node + */ + public function moveLeft() + { + return $this->moveToLeftOf($this->getLeftSibling()); + } + + /** + * Find the right sibling and move to the right of it. + * + * @return \Baum\Node + */ + public function moveRight() + { + return $this->moveToRightOf($this->getRightSibling()); + } + + /** + * Move to the node to the left of ... + * + * @return \Baum\Node + */ + public function moveToLeftOf($node) + { + return $this->moveTo($node, 'left'); + } + + /** + * Move to the node to the right of ... + * + * @return \Baum\Node + */ + public function moveToRightOf($node) + { + return $this->moveTo($node, 'right'); + } + + /** + * Alias for moveToRightOf + * + * @return \Baum\Node + */ + public function makeNextSiblingOf($node) + { + return $this->moveToRightOf($node); + } + + /** + * Alias for moveToRightOf + * + * @return \Baum\Node + */ + public function makeSiblingOf($node) + { + return $this->moveToRightOf($node); + } + + /** + * Alias for moveToLeftOf + * + * @return \Baum\Node + */ + public function makePreviousSiblingOf($node) + { + return $this->moveToLeftOf($node); + } + + /** + * Make the node a child of ... + * + * @return \Baum\Node + */ + public function makeChildOf($node) + { + return $this->moveTo($node, 'child'); + } + + /** + * Make the node the first child of ... + * + * @return \Baum\Node + */ + public function makeFirstChildOf($node) + { + if ($node->children()->count() == 0) + return $this->makeChildOf($node); + + return $this->moveToLeftOf($node->children()->first()); + } + + /** + * Make the node the last child of ... + * + * @return \Baum\Node + */ + public function makeLastChildOf($node) + { + return $this->makeChildOf($node); + } + + /** + * Make current node a root node. + * + * @return \Baum\Node + */ + public function makeRoot() + { + return $this->moveTo($this, 'root'); + } + + /** + * Equals? + * + * @param \Baum\Node + * @return boolean + */ + public function equals($node) + { + return ($this == $node); + } + + /** + * Checkes if the given node is in the same scope as the current one. + * + * @param \Baum\Node + * @return boolean + */ + public function inSameScope($other) + { + foreach ($this->getScopedColumns() as $fld) { + if ($this->$fld != $other->$fld) return false; + } + + return true; + } + + /** + * Checks wether the given node is a descendant of itself. Basically, whether + * its in the subtree defined by the left and right indices. + * + * @param \Baum\Node + * @return boolean + */ + public function insideSubtree($node) + { + return ( + $this->getLeft() >= $node->getLeft() && + $this->getLeft() <= $node->getRight() && + $this->getRight() >= $node->getLeft() && + $this->getRight() <= $node->getRight() + ); + } + + /** + * Sets default values for left and right fields. + * + * @return void + */ + public function setDefaultLeftAndRight() + { + $withHighestRight = $this->newNestedSetQuery()->reOrderBy($this->getRightColumnName(), 'desc')->take(1)->sharedLock()->first(); + + $maxRgt = 0; + if (!is_null($withHighestRight)) $maxRgt = $withHighestRight->getRight(); + + $this->setAttribute($this->getLeftColumnName(), $maxRgt + 1); + $this->setAttribute($this->getRightColumnName(), $maxRgt + 2); + } + + /** + * Store the parent_id if the attribute is modified so as we are able to move + * the node to this new parent after saving. + * + * @return void + */ + public function storeNewParent() + { + if ($this->isDirty($this->getParentColumnName()) && ($this->exists || !$this->isRoot())) + static::$moveToNewParentId = $this->getParentId(); + else + static::$moveToNewParentId = false; + } + + /** + * Move to the new parent if appropiate. + * + * @return void + */ + public function moveToNewParent() + { + $pid = static::$moveToNewParentId; + + if (is_null($pid)) + $this->makeRoot(); + else if ($pid !== false) + $this->makeChildOf($pid); + } + + /** + * Sets the depth attribute + * + * @return \Baum\Node + */ + public function setDepth() + { + $self = $this; + + $this->getConnection()->transaction(function () use ($self) { + $self->reload(); + + $level = $self->getLevel(); + + $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update(array($self->getDepthColumnName() => $level)); + $self->setAttribute($self->getDepthColumnName(), $level); + }); + + return $this; + } + + /** + * Sets the depth attribute for the current node and all of its descendants. + * + * @return \Baum\Node + */ + public function setDepthWithSubtree() + { + $self = $this; + + $this->getConnection()->transaction(function () use ($self) { + $self->reload(); + + $self->descendantsAndSelf()->select($self->getKeyName())->lockForUpdate()->get(); + + $oldDepth = !is_null($self->getDepth()) ? $self->getDepth() : 0; + + $newDepth = $self->getLevel(); + + $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update(array($self->getDepthColumnName() => $newDepth)); + $self->setAttribute($self->getDepthColumnName(), $newDepth); + + $diff = $newDepth - $oldDepth; + if (!$self->isLeaf() && $diff != 0) + $self->descendants()->increment($self->getDepthColumnName(), $diff); + }); + return $this; - } - } - } - - /** - * Instance scope which targes all the ancestor chain nodes including - * the current one. - * - * @return \Illuminate\Database\Eloquent\Builder - */ - public function ancestorsAndSelf() { - return $this->newNestedSetQuery() - ->where($this->getLeftColumnName(), '<=', $this->getLeft()) - ->where($this->getRightColumnName(), '>=', $this->getRight()); - } - - /** - * Get all the ancestor chain from the database including the current node. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getAncestorsAndSelf($columns = array('*')) { - return $this->ancestorsAndSelf()->get($columns); - } - - /** - * Get all the ancestor chain from the database including the current node - * but without the root node. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getAncestorsAndSelfWithoutRoot($columns = array('*')) { - return $this->ancestorsAndSelf()->withoutRoot()->get($columns); - } - - /** - * Instance scope which targets all the ancestor chain nodes excluding - * the current one. - * - * @return \Illuminate\Database\Eloquent\Builder - */ - public function ancestors() { - return $this->ancestorsAndSelf()->withoutSelf(); - } - - /** - * Get all the ancestor chain from the database excluding the current node. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getAncestors($columns = array('*')) { - return $this->ancestors()->get($columns); - } - - /** - * Get all the ancestor chain from the database excluding the current node - * and the root node (from the current node's perspective). - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getAncestorsWithoutRoot($columns = array('*')) { - return $this->ancestors()->withoutRoot()->get($columns); - } - - /** - * Instance scope which targets all children of the parent, including self. - * - * @return \Illuminate\Database\Eloquent\Builder - */ - public function siblingsAndSelf() { - return $this->newNestedSetQuery() - ->where($this->getParentColumnName(), $this->getParentId()); - } - - /** - * Get all children of the parent, including self. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getSiblingsAndSelf($columns = array('*')) { - return $this->siblingsAndSelf()->get($columns); - } - - /** - * Instance scope targeting all children of the parent, except self. - * - * @return \Illuminate\Database\Eloquent\Builder - */ - public function siblings() { - return $this->siblingsAndSelf()->withoutSelf(); - } - - /** - * Return all children of the parent, except self. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getSiblings($columns = array('*')) { - return $this->siblings()->get($columns); - } - - /** - * Instance scope targeting all of its nested children which do not have - * children. - * - * @return \Illuminate\Database\Query\Builder - */ - public function leaves() { - $grammar = $this->getConnection()->getQueryGrammar(); - - $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName()); - $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName()); - - return $this->descendants() - ->whereRaw($rgtCol . ' - ' . $lftCol . ' = 1'); - } - - /** - * Return all of its nested children which do not have children. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getLeaves($columns = array('*')) { - return $this->leaves()->get($columns); - } - - /** - * Instance scope targeting all of its nested children which are between the - * root and the leaf nodes (middle branch). - * - * @return \Illuminate\Database\Query\Builder - */ - public function trunks() { - $grammar = $this->getConnection()->getQueryGrammar(); - - $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName()); - $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName()); - - return $this->descendants() - ->whereNotNull($this->getQualifiedParentColumnName()) - ->whereRaw($rgtCol . ' - ' . $lftCol . ' != 1'); - } - - /** - * Return all of its nested children which are trunks. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getTrunks($columns = array('*')) { - return $this->trunks()->get($columns); - } - - /** - * Scope targeting itself and all of its nested children. - * - * @return \Illuminate\Database\Query\Builder - */ - public function descendantsAndSelf() { - return $this->newNestedSetQuery() - ->where($this->getLeftColumnName(), '>=', $this->getLeft()) - ->where($this->getLeftColumnName(), '<', $this->getRight()); - } - - /** - * Retrieve all nested children an self. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getDescendantsAndSelf($columns = array('*')) { - if ( is_array($columns) ) - return $this->descendantsAndSelf()->get($columns); - - $arguments = func_get_args(); - - $limit = intval(array_shift($arguments)); - $columns = array_shift($arguments) ?: array('*'); - - return $this->descendantsAndSelf()->limitDepth($limit)->get($columns); - } - - /** - * Set of all children & nested children. - * - * @return \Illuminate\Database\Query\Builder - */ - public function descendants() { - return $this->descendantsAndSelf()->withoutSelf(); - } - - /** - * Retrieve all of its children & nested children. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getDescendants($columns = array('*')) { - if ( is_array($columns) ) - return $this->descendants()->get($columns); - - $arguments = func_get_args(); - - $limit = intval(array_shift($arguments)); - $columns = array_shift($arguments) ?: array('*'); - - return $this->descendants()->limitDepth($limit)->get($columns); - } - - /** - * Set of "immediate" descendants (aka children), alias for the children relation. - * - * @return \Illuminate\Database\Query\Builder - */ - public function immediateDescendants() { - return $this->children(); - } - - /** - * Retrive all of its "immediate" descendants. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getImmediateDescendants($columns = array('*')) { - return $this->children()->get($columns); - } - - /** - * Returns the level of this node in the tree. - * Root level is 0. - * - * @return int - */ - public function getLevel() { - if ( is_null($this->getParentId()) ) - return 0; - - return $this->computeLevel(); - } - - /** - * Returns true if node is a descendant. - * - * @param NestedSet - * @return boolean - */ - public function isDescendantOf($other) { - return ( - $this->getLeft() > $other->getLeft() && - $this->getLeft() < $other->getRight() && - $this->inSameScope($other) - ); - } - - /** - * Returns true if node is self or a descendant. - * - * @param NestedSet - * @return boolean - */ - public function isSelfOrDescendantOf($other) { - return ( - $this->getLeft() >= $other->getLeft() && - $this->getLeft() < $other->getRight() && - $this->inSameScope($other) - ); - } - - /** - * Returns true if node is an ancestor. - * - * @param NestedSet - * @return boolean - */ - public function isAncestorOf($other) { - return ( - $this->getLeft() < $other->getLeft() && - $this->getRight() > $other->getLeft() && - $this->inSameScope($other) - ); - } - - /** - * Returns true if node is self or an ancestor. - * - * @param NestedSet - * @return boolean - */ - public function isSelfOrAncestorOf($other) { - return ( - $this->getLeft() <= $other->getLeft() && - $this->getRight() > $other->getLeft() && - $this->inSameScope($other) - ); - } - - /** - * Returns the first sibling to the left. - * - * @return NestedSet - */ - public function getLeftSibling() { - return $this->siblings() - ->where($this->getLeftColumnName(), '<', $this->getLeft()) - ->orderBy($this->getOrderColumnName(), 'desc') - ->get() - ->last(); - } - - /** - * Returns the first sibling to the right. - * - * @return NestedSet - */ - public function getRightSibling() { - return $this->siblings() - ->where($this->getLeftColumnName(), '>', $this->getLeft()) - ->first(); - } - - /** - * Find the left sibling and move to left of it. - * - * @return \Baum\Node - */ - public function moveLeft() { - return $this->moveToLeftOf($this->getLeftSibling()); - } - - /** - * Find the right sibling and move to the right of it. - * - * @return \Baum\Node - */ - public function moveRight() { - return $this->moveToRightOf($this->getRightSibling()); - } - - /** - * Move to the node to the left of ... - * - * @return \Baum\Node - */ - public function moveToLeftOf($node) { - return $this->moveTo($node, 'left'); - } - - /** - * Move to the node to the right of ... - * - * @return \Baum\Node - */ - public function moveToRightOf($node) { - return $this->moveTo($node, 'right'); - } - - /** - * Alias for moveToRightOf - * - * @return \Baum\Node - */ - public function makeNextSiblingOf($node) { - return $this->moveToRightOf($node); - } - - /** - * Alias for moveToRightOf - * - * @return \Baum\Node - */ - public function makeSiblingOf($node) { - return $this->moveToRightOf($node); - } - - /** - * Alias for moveToLeftOf - * - * @return \Baum\Node - */ - public function makePreviousSiblingOf($node) { - return $this->moveToLeftOf($node); - } - - /** - * Make the node a child of ... - * - * @return \Baum\Node - */ - public function makeChildOf($node) { - return $this->moveTo($node, 'child'); - } - - /** - * Make the node the first child of ... - * - * @return \Baum\Node - */ - public function makeFirstChildOf($node) { - if ( $node->children()->count() == 0 ) - return $this->makeChildOf($node); - - return $this->moveToLeftOf($node->children()->first()); - } - - /** - * Make the node the last child of ... - * - * @return \Baum\Node - */ - public function makeLastChildOf($node) { - return $this->makeChildOf($node); - } - - /** - * Make current node a root node. - * - * @return \Baum\Node - */ - public function makeRoot() { - return $this->moveTo($this, 'root'); - } - - /** - * Equals? - * - * @param \Baum\Node - * @return boolean - */ - public function equals($node) { - return ($this == $node); - } - - /** - * Checkes if the given node is in the same scope as the current one. - * - * @param \Baum\Node - * @return boolean - */ - public function inSameScope($other) { - foreach($this->getScopedColumns() as $fld) { - if ( $this->$fld != $other->$fld ) return false; - } - - return true; - } - - /** - * Checks wether the given node is a descendant of itself. Basically, whether - * its in the subtree defined by the left and right indices. - * - * @param \Baum\Node - * @return boolean - */ - public function insideSubtree($node) { - return ( - $this->getLeft() >= $node->getLeft() && - $this->getLeft() <= $node->getRight() && - $this->getRight() >= $node->getLeft() && - $this->getRight() <= $node->getRight() - ); - } - - /** - * Sets default values for left and right fields. - * - * @return void - */ - public function setDefaultLeftAndRight() { - $withHighestRight = $this->newNestedSetQuery()->reOrderBy($this->getRightColumnName(), 'desc')->take(1)->sharedLock()->first(); - - $maxRgt = 0; - if ( !is_null($withHighestRight) ) $maxRgt = $withHighestRight->getRight(); - - $this->setAttribute($this->getLeftColumnName() , $maxRgt + 1); - $this->setAttribute($this->getRightColumnName() , $maxRgt + 2); - } - - /** - * Store the parent_id if the attribute is modified so as we are able to move - * the node to this new parent after saving. - * - * @return void - */ - public function storeNewParent() { - if ( $this->isDirty($this->getParentColumnName()) && ($this->exists || !$this->isRoot()) ) - static::$moveToNewParentId = $this->getParentId(); - else - static::$moveToNewParentId = FALSE; - } - - /** - * Move to the new parent if appropiate. - * - * @return void - */ - public function moveToNewParent() { - $pid = static::$moveToNewParentId; - - if ( is_null($pid) ) - $this->makeRoot(); - else if ( $pid !== FALSE ) - $this->makeChildOf($pid); - } - - /** - * Sets the depth attribute - * - * @return \Baum\Node - */ - public function setDepth() { - $self = $this; - - $this->getConnection()->transaction(function() use ($self) { - $self->reload(); - - $level = $self->getLevel(); - - $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update(array($self->getDepthColumnName() => $level)); - $self->setAttribute($self->getDepthColumnName(), $level); - }); - - return $this; - } - - /** - * Sets the depth attribute for the current node and all of its descendants. - * - * @return \Baum\Node - */ - public function setDepthWithSubtree() { - $self = $this; - - $this->getConnection()->transaction(function() use ($self) { - $self->reload(); - - $self->descendantsAndSelf()->select($self->getKeyName())->lockForUpdate()->get(); - - $oldDepth = !is_null($self->getDepth()) ? $self->getDepth() : 0; - - $newDepth = $self->getLevel(); - - $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update(array($self->getDepthColumnName() => $newDepth)); - $self->setAttribute($self->getDepthColumnName(), $newDepth); - - $diff = $newDepth - $oldDepth; - if ( !$self->isLeaf() && $diff != 0 ) - $self->descendants()->increment($self->getDepthColumnName(), $diff); - }); - - return $this; - } - - /** - * Prunes a branch off the tree, shifting all the elements on the right - * back to the left so the counts work. - * - * @return void; - */ - public function destroyDescendants() { - if ( is_null($this->getRight()) || is_null($this->getLeft()) ) return; - - $self = $this; - - $this->getConnection()->transaction(function() use ($self) { - $self->reload(); - - $lftCol = $self->getLeftColumnName(); - $rgtCol = $self->getRightColumnName(); - $lft = $self->getLeft(); - $rgt = $self->getRight(); - - // Apply a lock to the rows which fall past the deletion point - $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->select($self->getKeyName())->lockForUpdate()->get(); - - // Prune children - $self->newNestedSetQuery()->where($lftCol, '>', $lft)->where($rgtCol, '<', $rgt)->delete(); - - // Update left and right indexes for the remaining nodes - $diff = $rgt - $lft + 1; - - $self->newNestedSetQuery()->where($lftCol, '>', $rgt)->decrement($lftCol, $diff); - $self->newNestedSetQuery()->where($rgtCol, '>', $rgt)->decrement($rgtCol, $diff); - }); - } - - /** - * "Makes room" for the the current node between its siblings. - * - * @return void - */ - public function shiftSiblingsForRestore() { - if ( is_null($this->getRight()) || is_null($this->getLeft()) ) return; - - $self = $this; - - $this->getConnection()->transaction(function() use ($self) { - $lftCol = $self->getLeftColumnName(); - $rgtCol = $self->getRightColumnName(); - $lft = $self->getLeft(); - $rgt = $self->getRight(); - - $diff = $rgt - $lft + 1; - - $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->increment($lftCol, $diff); - $self->newNestedSetQuery()->where($rgtCol, '>=', $lft)->increment($rgtCol, $diff); - }); - } - - /** - * Restores all of the current node's descendants. - * - * @return void - */ - public function restoreDescendants() { - if ( is_null($this->getRight()) || is_null($this->getLeft()) ) return; - - $self = $this; - - $this->getConnection()->transaction(function() use ($self) { - $self->newNestedSetQuery() - ->withTrashed() - ->where($self->getLeftColumnName(), '>', $self->getLeft()) - ->where($self->getRightColumnName(), '<', $self->getRight()) - ->update(array( - $self->getDeletedAtColumn() => null, - $self->getUpdatedAtColumn() => $self->{$self->getUpdatedAtColumn()} - )); - }); - } - - /** - * Return an key-value array indicating the node's depth with $seperator - * - * @return Array - */ - public static function getNestedList($column, $key = null, $seperator = ' ') { - $instance = new static; - - $key = $key ?: $instance->getKeyName(); - $depthColumn = $instance->getDepthColumnName(); - - $nodes = $instance->newNestedSetQuery()->get()->toArray(); - - return array_combine(array_map(function($node) use($key) { - return $node[$key]; - }, $nodes), array_map(function($node) use($seperator, $depthColumn, $column) { - return str_repeat($seperator, $node[$depthColumn]) . $node[$column]; - }, $nodes)); - } - - /** - * Maps the provided tree structure into the database using the current node - * as the parent. The provided tree structure will be inserted/updated as the - * descendancy subtree of the current node instance. - * - * @param array|\Illuminate\Support\Contracts\ArrayableInterface - * @return boolean - */ - public function makeTree($nodeList) { - $mapper = new SetMapper($this); - - return $mapper->map($nodeList); - } - - /** - * Main move method. Here we handle all node movements with the corresponding - * lft/rgt index updates. - * - * @param Baum\Node|int $target - * @param string $position - * @return \Baum\Node - */ - protected function moveTo($target, $position) { - return Move::to($this, $target, $position); - } - - /** - * Compute current node level. If could not move past ourseleves return - * our ancestor count, otherwhise get the first parent level + the computed - * nesting. - * - * @return integer - */ - protected function computeLevel() { - list($node, $nesting) = $this->determineDepth($this); - - if ( $node->equals($this) ) - return $this->ancestors()->count(); - - return $node->getLevel() + $nesting; - } - - /** - * Return an array with the last node we could reach and its nesting level - * - * @param Baum\Node $node - * @param integer $nesting - * @return array - */ - protected function determineDepth($node, $nesting = 0) { - // Traverse back up the ancestry chain and add to the nesting level count - while( $parent = $node->parent()->first() ) { - $nesting = $nesting + 1; - - $node = $parent; - } - - return array($node, $nesting); - } + } + + /** + * Prunes a branch off the tree, shifting all the elements on the right + * back to the left so the counts work. + * + * @return void; + */ + public function destroyDescendants() + { + if (is_null($this->getRight()) || is_null($this->getLeft())) return; + + $self = $this; + + $this->getConnection()->transaction(function () use ($self) { + $self->reload(); + + $lftCol = $self->getLeftColumnName(); + $rgtCol = $self->getRightColumnName(); + $lft = $self->getLeft(); + $rgt = $self->getRight(); + + // Apply a lock to the rows which fall past the deletion point + $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->select($self->getKeyName())->lockForUpdate()->get(); + + // Prune children + $self->newNestedSetQuery()->where($lftCol, '>', $lft)->where($rgtCol, '<', $rgt)->delete(); + + // Update left and right indexes for the remaining nodes + $diff = $rgt - $lft + 1; + + $self->newNestedSetQuery()->where($lftCol, '>', $rgt)->decrement($lftCol, $diff); + $self->newNestedSetQuery()->where($rgtCol, '>', $rgt)->decrement($rgtCol, $diff); + }); + } + + /** + * "Makes room" for the the current node between its siblings. + * + * @return void + */ + public function shiftSiblingsForRestore() + { + if (is_null($this->getRight()) || is_null($this->getLeft())) return; + + $self = $this; + + $this->getConnection()->transaction(function () use ($self) { + $lftCol = $self->getLeftColumnName(); + $rgtCol = $self->getRightColumnName(); + $lft = $self->getLeft(); + $rgt = $self->getRight(); + + $diff = $rgt - $lft + 1; + + $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->increment($lftCol, $diff); + $self->newNestedSetQuery()->where($rgtCol, '>=', $lft)->increment($rgtCol, $diff); + }); + } + + /** + * Restores all of the current node's descendants. + * + * @return void + */ + public function restoreDescendants() + { + if (is_null($this->getRight()) || is_null($this->getLeft())) return; + + $self = $this; + + $this->getConnection()->transaction(function () use ($self) { + $self->newNestedSetQuery() + ->withTrashed() + ->where($self->getLeftColumnName(), '>', $self->getLeft()) + ->where($self->getRightColumnName(), '<', $self->getRight()) + ->update(array( + $self->getDeletedAtColumn() => null, + $self->getUpdatedAtColumn() => $self->{$self->getUpdatedAtColumn()} + )); + }); + } + + /** + * Return an key-value array indicating the node's depth with $seperator + * + * @return Array + */ + public static function getNestedList($column, $key = null, $seperator = ' ') + { + $instance = new static; + + $key = $key ?: $instance->getKeyName(); + $depthColumn = $instance->getDepthColumnName(); + + $nodes = $instance->newNestedSetQuery()->get()->toArray(); + + return array_combine(array_map(function ($node) use ($key) { + return $node[$key]; + }, $nodes), array_map(function ($node) use ($seperator, $depthColumn, $column) { + return str_repeat($seperator, $node[$depthColumn]) . $node[$column]; + }, $nodes)); + } + + /** + * Maps the provided tree structure into the database using the current node + * as the parent. The provided tree structure will be inserted/updated as the + * descendancy subtree of the current node instance. + * + * @param array|\Illuminate\Contracts\Support\Arrayable + * @return boolean + */ + public function makeTree($nodeList) + { + $mapper = new SetMapper($this); + + return $mapper->map($nodeList); + } + + /** + * Main move method. Here we handle all node movements with the corresponding + * lft/rgt index updates. + * + * @param Baum\Node|int $target + * @param string $position + * @return \Baum\Node + */ + protected function moveTo($target, $position) + { + return Move::to($this, $target, $position); + } + + /** + * Compute current node level. If could not move past ourseleves return + * our ancestor count, otherwhise get the first parent level + the computed + * nesting. + * + * @return integer + */ + protected function computeLevel() + { + list($node, $nesting) = $this->determineDepth($this); + + if ($node->equals($this)) + return $this->ancestors()->count(); + + return $node->getLevel() + $nesting; + } + + /** + * Return an array with the last node we could reach and its nesting level + * + * @param Baum\Node $node + * @param integer $nesting + * @return array + */ + protected function determineDepth($node, $nesting = 0) + { + // Traverse back up the ancestry chain and add to the nesting level count + while ($parent = $node->parent()->first()) { + $nesting = $nesting + 1; + + $node = $parent; + } + + return array($node, $nesting); + } } diff --git a/src/Providers/BaumServiceProvider.php b/src/Providers/BaumServiceProvider.php index 5c6f18c5..4061873b 100644 --- a/src/Providers/BaumServiceProvider.php +++ b/src/Providers/BaumServiceProvider.php @@ -1,4 +1,5 @@ registerCommands(); - } + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerCommands(); + } - /** - * Register the commands. - * - * @return void - */ - public function registerCommands() { - $this->registerBaumCommand(); - $this->registerInstallCommand(); + /** + * Register the commands. + * + * @return void + */ + public function registerCommands() + { + $this->registerBaumCommand(); + $this->registerInstallCommand(); - // Resolve the commands with Artisan by attaching the event listener to Artisan's - // startup. This allows us to use the commands from our terminal. - $this->commands('command.baum', 'command.baum.install'); - } + // Resolve the commands with Artisan by attaching the event listener to Artisan's + // startup. This allows us to use the commands from our terminal. + $this->commands('command.baum', 'command.baum.install'); + } - /** - * Register the 'baum' command. - * - * @return void - */ - protected function registerBaumCommand() { - $this->app->singleton('command.baum', function($app) { - return new BaumCommand; - }); - } + /** + * Register the 'baum' command. + * + * @return void + */ + protected function registerBaumCommand() + { + $this->app->singleton('command.baum', function ($app) { + return new BaumCommand; + }); + } - /** - * Register the 'baum:install' command. - * - * @return void - */ - protected function registerInstallCommand() { - $this->app->singleton('command.baum.install', function($app) { - $migrator = new MigrationGenerator($app['files']); - $modeler = new ModelGenerator($app['files']); + /** + * Register the 'baum:install' command. + * + * @return void + */ + protected function registerInstallCommand() + { + $this->app->singleton('command.baum.install', function ($app) { + $migrator = new MigrationGenerator($app['files']); + $modeler = new ModelGenerator($app['files']); - return new InstallCommand($migrator, $modeler); - }); - } + return new InstallCommand($migrator, $modeler); + }); + } - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() { - return array('command.baum', 'command.baum.install'); - } + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.baum', 'command.baum.install'); + } } diff --git a/src/SetBuilder.php b/src/SetBuilder.php index 873c238e..05b1ab67 100644 --- a/src/SetBuilder.php +++ b/src/SetBuilder.php @@ -1,175 +1,187 @@ node = $node; - } - - /** - * Perform the re-calculation of the left and right indexes of the whole - * nested set tree structure. - * - * @param bool $force - * @return void - */ - public function rebuild($force = false) { - $alreadyValid = forward_static_call(array(get_class($this->node), 'isValidNestedSet')); - - // Do not rebuild a valid Nested Set tree structure - if ( !$force && $alreadyValid ) return true; - - // Rebuild lefts and rights for each root node and its children (recursively). - // We go by setting left (and keep track of the current left bound), then - // search for each children and recursively set the left index (while - // incrementing that index). When going back up the recursive chain we start - // setting the right indexes and saving the nodes... - $self = $this; - - $this->node->getConnection()->transaction(function() use ($self) { - foreach($self->roots() as $root) - $self->rebuildBounds($root, 0); - }); - } - - /** - * Return all root nodes for the current database table appropiately sorted. - * - * @return Illuminate\Database\Eloquent\Collection - */ - public function roots() { - return $this->node->newQuery() - ->whereNull($this->node->getQualifiedParentColumnName()) - ->orderBy($this->node->getQualifiedLeftColumnName()) - ->orderBy($this->node->getQualifiedRightColumnName()) - ->orderBy($this->node->getQualifiedKeyName()) - ->get(); - } - - /** - * Recompute left and right index bounds for the specified node and its - * children (recursive call). Fill the depth column too. - */ - public function rebuildBounds($node, $depth = 0) { - $k = $this->scopedKey($node); - - $node->setAttribute($node->getLeftColumnName(), $this->getNextBound($k)); - $node->setAttribute($node->getDepthColumnName(), $depth); - - foreach($this->children($node) as $child) - $this->rebuildBounds($child, $depth + 1); - - $node->setAttribute($node->getRightColumnName(), $this->getNextBound($k)); - - $node->save(); - } - - /** - * Return all children for the specified node. - * - * @param Baum\Node $node - * @return Illuminate\Database\Eloquent\Collection - */ - public function children($node) { - $query = $this->node->newQuery(); - - $query->where($this->node->getQualifiedParentColumnName(), '=', $node->getKey()); - - // We must also add the scoped column values to the query to compute valid - // left and right indexes. - foreach($this->scopedAttributes($node) as $fld => $value) - $query->where($this->qualify($fld), '=', $value); - - $query->orderBy($this->node->getQualifiedLeftColumnName()); - $query->orderBy($this->node->getQualifiedRightColumnName()); - $query->orderBy($this->node->getQualifiedKeyName()); - - return $query->get(); - } - - /** - * Return an array of the scoped attributes of the supplied node. - * - * @param Baum\Node $node - * @return array - */ - protected function scopedAttributes($node) { - $keys = $this->node->getScopedColumns(); - - if ( count($keys) == 0 ) - return array(); - - $values = array_map(function($column) use ($node) { - return $node->getAttribute($column); }, $keys); - - return array_combine($keys, $values); - } - - /** - * Return a string-key for the current scoped attributes. Used for index - * computing when a scope is defined (acsts as an scope identifier). - * - * @param Baum\Node $node - * @return string - */ - protected function scopedKey($node) { - $attributes = $this->scopedAttributes($node); - - $output = array(); - - foreach($attributes as $fld => $value) - $output[] = $this->qualify($fld).'='.(is_null($value) ? 'NULL' : $value); - - // NOTE: Maybe an md5 or something would be better. Should be unique though. - return implode(',', $output); - } - - /** - * Return next index bound value for the given key (current scope identifier) - * - * @param string $key - * @return integer - */ - protected function getNextBound($key) { - if ( false === array_key_exists($key, $this->bounds) ) - $this->bounds[$key] = 0; - - $this->bounds[$key] = $this->bounds[$key] + 1; - - return $this->bounds[$key]; - } - - /** - * Get the fully qualified value for the specified column. - * - * @return string - */ - protected function qualify($column) { - return $this->node->getTable() . '.' . $column; - } +class SetBuilder +{ + + /** + * Node instance for reference + * + * @var \Baum\Node + */ + protected $node = null; + + /** + * Array which will hold temporary lft, rgt index values for each scope. + * + * @var array + */ + protected $bounds = array(); + + /** + * Create a new \Baum\SetBuilder class instance. + * + * @param \Baum\Node $node + * @return void + */ + public function __construct($node) + { + $this->node = $node; + } + + /** + * Perform the re-calculation of the left and right indexes of the whole + * nested set tree structure. + * + * @param bool $force + * @return void + */ + public function rebuild($force = false) + { + $alreadyValid = forward_static_call(array(get_class($this->node), 'isValidNestedSet')); + + // Do not rebuild a valid Nested Set tree structure + if (!$force && $alreadyValid) return true; + + // Rebuild lefts and rights for each root node and its children (recursively). + // We go by setting left (and keep track of the current left bound), then + // search for each children and recursively set the left index (while + // incrementing that index). When going back up the recursive chain we start + // setting the right indexes and saving the nodes... + $self = $this; + + $this->node->getConnection()->transaction(function () use ($self) { + foreach ($self->roots() as $root) + $self->rebuildBounds($root, 0); + }); + } + + /** + * Return all root nodes for the current database table appropiately sorted. + * + * @return Illuminate\Database\Eloquent\Collection + */ + public function roots() + { + return $this->node->newQuery() + ->whereNull($this->node->getQualifiedParentColumnName()) + ->orderBy($this->node->getQualifiedLeftColumnName()) + ->orderBy($this->node->getQualifiedRightColumnName()) + ->orderBy($this->node->getQualifiedKeyName()) + ->get(); + } + + /** + * Recompute left and right index bounds for the specified node and its + * children (recursive call). Fill the depth column too. + */ + public function rebuildBounds($node, $depth = 0) + { + $k = $this->scopedKey($node); + + $node->setAttribute($node->getLeftColumnName(), $this->getNextBound($k)); + $node->setAttribute($node->getDepthColumnName(), $depth); + + foreach ($this->children($node) as $child) + $this->rebuildBounds($child, $depth + 1); + + $node->setAttribute($node->getRightColumnName(), $this->getNextBound($k)); + + $node->save(); + } + + /** + * Return all children for the specified node. + * + * @param Baum\Node $node + * @return Illuminate\Database\Eloquent\Collection + */ + public function children($node) + { + $query = $this->node->newQuery(); + + $query->where($this->node->getQualifiedParentColumnName(), '=', $node->getKey()); + + // We must also add the scoped column values to the query to compute valid + // left and right indexes. + foreach ($this->scopedAttributes($node) as $fld => $value) + $query->where($this->qualify($fld), '=', $value); + + $query->orderBy($this->node->getQualifiedLeftColumnName()); + $query->orderBy($this->node->getQualifiedRightColumnName()); + $query->orderBy($this->node->getQualifiedKeyName()); + + return $query->get(); + } + + /** + * Return an array of the scoped attributes of the supplied node. + * + * @param Baum\Node $node + * @return array + */ + protected function scopedAttributes($node) + { + $keys = $this->node->getScopedColumns(); + + if (count($keys) == 0) + return array(); + + $values = array_map(function ($column) use ($node) { + return $node->getAttribute($column); + }, $keys); + + return array_combine($keys, $values); + } + + /** + * Return a string-key for the current scoped attributes. Used for index + * computing when a scope is defined (acsts as an scope identifier). + * + * @param Baum\Node $node + * @return string + */ + protected function scopedKey($node) + { + $attributes = $this->scopedAttributes($node); + + $output = array(); + + foreach ($attributes as $fld => $value) + $output[] = $this->qualify($fld) . '=' . (is_null($value) ? 'NULL' : $value); + + // NOTE: Maybe an md5 or something would be better. Should be unique though. + return implode(',', $output); + } + + /** + * Return next index bound value for the given key (current scope identifier) + * + * @param string $key + * @return integer + */ + protected function getNextBound($key) + { + if (false === array_key_exists($key, $this->bounds)) + $this->bounds[$key] = 0; + + $this->bounds[$key] = $this->bounds[$key] + 1; + + return $this->bounds[$key]; + } + + /** + * Get the fully qualified value for the specified column. + * + * @return string + */ + protected function qualify($column) + { + return $this->node->getTable() . '.' . $column; + } } diff --git a/src/SetMapper.php b/src/SetMapper.php index a44dabab..43bee475 100644 --- a/src/SetMapper.php +++ b/src/SetMapper.php @@ -1,164 +1,178 @@ node = $node; - - $this->childrenKeyName = $childrenKeyName; - } - - /** - * Maps a tree structure into the database. Unguards & wraps in transaction. - * - * @param array|\Illuminate\Support\Contracts\ArrayableInterface - * @return boolean - */ - public function map($nodeList) { - $self = $this; - - return $this->wrapInTransaction(function() use ($self, $nodeList) { - forward_static_call(array(get_class($self->node), 'unguard')); - - $result = $self->mapTree($nodeList); - - forward_static_call(array(get_class($self->node), 'reguard')); - - return $result; - }); - } - - /** - * Maps a tree structure into the database without unguarding nor wrapping - * inside a transaction. - * - * @param array|\Illuminate\Support\Contracts\ArrayableInterface - * @return boolean - */ - public function mapTree($nodeList) { - $tree = $nodeList instanceof ArrayableInterface ? $nodeList->toArray() : $nodeList; - - $affectedKeys = array(); - - $result = $this->mapTreeRecursive($tree, $this->node->getKey(), $affectedKeys); - - if ( $result && count($affectedKeys) > 0 ) - $this->deleteUnaffected($affectedKeys); - - return $result; - } - - /** - * Returns the children key name to use on the mapping array - * - * @return string - */ - public function getChildrenKeyName() { - return $this->childrenKeyName; - } - - /** - * Maps a tree structure into the database - * - * @param array $tree - * @param mixed $parent - * @return boolean - */ - protected function mapTreeRecursive(array $tree, $parentKey = null, &$affectedKeys = array()) { - // For every attribute entry: We'll need to instantiate a new node either - // from the database (if the primary key was supplied) or a new instance. Then, - // append all the remaining data attributes (including the `parent_id` if - // present) and save it. Finally, tail-recurse performing the same - // operations for any child node present. Setting the `parent_id` property at - // each level will take care of the nesting work for us. - foreach($tree as $attributes) { - $node = $this->firstOrNew($this->getSearchAttributes($attributes)); - - $data = $this->getDataAttributes($attributes); - if ( !is_null($parentKey) ) - $data[$node->getParentColumnName()] = $parentKey; - - $node->fill($data); - - $result = $node->save(); - - if ( ! $result ) return false; - - $affectedKeys[] = $node->getKey(); - - if ( array_key_exists($this->getChildrenKeyName(), $attributes) ) { - $children = $attributes[$this->getChildrenKeyName()]; - - if ( count($children) > 0 ) { - $result = $this->mapTreeRecursive($children, $node->getKey(), $affectedKeys); - - if ( ! $result ) return false; - } - } +class SetMapper +{ + + /** + * Node instance for reference + * + * @var \Baum\Node + */ + protected $node = null; + + /** + * Children key name + * + * @var string + */ + protected $childrenKeyName = 'children'; + + /** + * Create a new \Baum\SetBuilder class instance. + * + * @param \Baum\Node $node + * @return void + */ + public function __construct($node, $childrenKeyName = 'children') + { + $this->node = $node; + + $this->childrenKeyName = $childrenKeyName; } - return true; - } + /** + * Maps a tree structure into the database. Unguards & wraps in transaction. + * + * @param array|\Illuminate\Support\Contracts\ArrayableInterface + * @return boolean + */ + public function map($nodeList) + { + $self = $this; - protected function getSearchAttributes($attributes) { - $searchable = array($this->node->getKeyName()); + return $this->wrapInTransaction(function () use ($self, $nodeList) { + forward_static_call(array(get_class($self->node), 'unguard')); - return array_only($attributes, $searchable); - } + $result = $self->mapTree($nodeList); - protected function getDataAttributes($attributes) { - $exceptions = array($this->node->getKeyName(), $this->getChildrenKeyName()); + forward_static_call(array(get_class($self->node), 'reguard')); + + return $result; + }); + } - return array_except($attributes, $exceptions); - } + /** + * Maps a tree structure into the database without unguarding nor wrapping + * inside a transaction. + * + * @param array|\Illuminate\Support\Contracts\ArrayableInterface + * @return boolean + */ + public function mapTree($nodeList) + { + $tree = $nodeList instanceof ArrayableInterface ? $nodeList->toArray() : $nodeList; - protected function firstOrNew($attributes) { - $className = get_class($this->node); + $affectedKeys = array(); - if ( count($attributes) === 0 ) - return new $className; + $result = $this->mapTreeRecursive($tree, $this->node->getKey(), $affectedKeys); - return forward_static_call(array($className, 'firstOrNew'), $attributes); - } + if ($result && count($affectedKeys) > 0) + $this->deleteUnaffected($affectedKeys); - protected function pruneScope() { - if ( $this->node->exists ) - return $this->node->descendants(); + return $result; + } + + /** + * Returns the children key name to use on the mapping array + * + * @return string + */ + public function getChildrenKeyName() + { + return $this->childrenKeyName; + } - return $this->node->newNestedSetQuery(); - } + /** + * Maps a tree structure into the database + * + * @param array $tree + * @param mixed $parent + * @return boolean + */ + protected function mapTreeRecursive(array $tree, $parentKey = null, &$affectedKeys = array()) + { + // For every attribute entry: We'll need to instantiate a new node either + // from the database (if the primary key was supplied) or a new instance. Then, + // append all the remaining data attributes (including the `parent_id` if + // present) and save it. Finally, tail-recurse performing the same + // operations for any child node present. Setting the `parent_id` property at + // each level will take care of the nesting work for us. + foreach ($tree as $attributes) { + $node = $this->firstOrNew($this->getSearchAttributes($attributes)); + + $data = $this->getDataAttributes($attributes); + if (!is_null($parentKey)) + $data[$node->getParentColumnName()] = $parentKey; + + $node->fill($data); + + $result = $node->save(); + + if (!$result) return false; + + $affectedKeys[] = $node->getKey(); + + if (array_key_exists($this->getChildrenKeyName(), $attributes)) { + $children = $attributes[$this->getChildrenKeyName()]; + + if (count($children) > 0) { + $result = $this->mapTreeRecursive($children, $node->getKey(), $affectedKeys); + + if (!$result) return false; + } + } + } + + return true; + } + + protected function getSearchAttributes($attributes) + { + $searchable = array($this->node->getKeyName()); + + return Arr::only($attributes, $searchable); + } - protected function deleteUnaffected($keys = array()) { - return $this->pruneScope()->whereNotIn($this->node->getKeyName(), $keys)->delete(); - } + protected function getDataAttributes($attributes) + { + $exceptions = array($this->node->getKeyName(), $this->getChildrenKeyName()); - protected function wrapInTransaction(Closure $callback) { - return $this->node->getConnection()->transaction($callback); - } + return Arr::except($attributes, $exceptions); + } + + protected function firstOrNew($attributes) + { + $className = get_class($this->node); + + if (count($attributes) === 0) + return new $className; + + return forward_static_call(array($className, 'firstOrNew'), $attributes); + } + + protected function pruneScope() + { + if ($this->node->exists) + return $this->node->descendants(); + + return $this->node->newNestedSetQuery(); + } + + protected function deleteUnaffected($keys = array()) + { + return $this->pruneScope()->whereNotIn($this->node->getKeyName(), $keys)->delete(); + } + + protected function wrapInTransaction(Closure $callback) + { + return $this->node->getConnection()->transaction($callback); + } } diff --git a/src/SetValidator.php b/src/SetValidator.php index 10d87640..9cec5472 100644 --- a/src/SetValidator.php +++ b/src/SetValidator.php @@ -1,224 +1,238 @@ node = $node; - } - - /** - * Determine if the validation passes. - * - * @return boolean - */ - public function passes() { - return $this->validateBounds() && $this->validateDuplicates() && - $this->validateRoots(); - } - - /** - * Determine if validation fails. - * - * @return boolean - */ - public function fails() { - return !$this->passes(); - } - - /** - * Validates bounds of the nested tree structure. It will perform checks on - * the `lft`, `rgt` and `parent_id` columns. Mainly that they're not null, - * rights greater than lefts, and that they're within the bounds of the parent. - * - * @return boolean - */ - protected function validateBounds() { - $connection = $this->node->getConnection(); - $grammar = $connection->getQueryGrammar(); - - $tableName = $this->node->getTable(); - $primaryKeyName = $this->node->getKeyName(); - $parentColumn = $this->node->getQualifiedParentColumnName(); - - $lftCol = $grammar->wrap($this->node->getLeftColumnName()); - $rgtCol = $grammar->wrap($this->node->getRightColumnName()); - - $qualifiedLftCol = $grammar->wrap($this->node->getQualifiedLeftColumnName()); - $qualifiedRgtCol = $grammar->wrap($this->node->getQualifiedRightColumnName()); - $qualifiedParentCol = $grammar->wrap($this->node->getQualifiedParentColumnName()); - - $whereStm = "($qualifiedLftCol IS NULL OR +class SetValidator +{ + + /** + * Node instance for reference + * + * @var \Baum\Node + */ + protected $node = null; + + /** + * Create a new \Baum\SetValidator class instance. + * + * @param \Baum\Node $node + * @return void + */ + public function __construct($node) + { + $this->node = $node; + } + + /** + * Determine if the validation passes. + * + * @return boolean + */ + public function passes() + { + return $this->validateBounds() && $this->validateDuplicates() && + $this->validateRoots(); + } + + /** + * Determine if validation fails. + * + * @return boolean + */ + public function fails() + { + return !$this->passes(); + } + + /** + * Validates bounds of the nested tree structure. It will perform checks on + * the `lft`, `rgt` and `parent_id` columns. Mainly that they're not null, + * rights greater than lefts, and that they're within the bounds of the parent. + * + * @return boolean + */ + protected function validateBounds() + { + $connection = $this->node->getConnection(); + $grammar = $connection->getQueryGrammar(); + + $tableName = $this->node->getTable(); + $primaryKeyName = $this->node->getKeyName(); + $parentColumn = $this->node->getQualifiedParentColumnName(); + + $lftCol = $grammar->wrap($this->node->getLeftColumnName()); + $rgtCol = $grammar->wrap($this->node->getRightColumnName()); + + $qualifiedLftCol = $grammar->wrap($this->node->getQualifiedLeftColumnName()); + $qualifiedRgtCol = $grammar->wrap($this->node->getQualifiedRightColumnName()); + $qualifiedParentCol = $grammar->wrap($this->node->getQualifiedParentColumnName()); + + $whereStm = "($qualifiedLftCol IS NULL OR $qualifiedRgtCol IS NULL OR $qualifiedLftCol >= $qualifiedRgtCol OR ($qualifiedParentCol IS NOT NULL AND ($qualifiedLftCol <= parent.$lftCol OR $qualifiedRgtCol >= parent.$rgtCol)))"; - $query = $this->node->newQuery() - ->join($connection->raw($grammar->wrapTable($tableName).' AS parent'), - $parentColumn, '=', $connection->raw('parent.'.$grammar->wrap($primaryKeyName)), - 'left outer') - ->whereRaw($whereStm); - - return ($query->count() == 0); - } - - /** - * Checks that there are no duplicates for the `lft` and `rgt` columns. - * - * @return boolean - */ - protected function validateDuplicates() { - return ( - !$this->duplicatesExistForColumn($this->node->getQualifiedLeftColumnName()) && - !$this->duplicatesExistForColumn($this->node->getQualifiedRightColumnName()) - ); - } - - /** - * For each root of the whole nested set tree structure, checks that their - * `lft` and `rgt` bounds are properly set. - * - * @return boolean - */ - protected function validateRoots() { - $roots = forward_static_call(array(get_class($this->node), 'roots'))->get(); - - // If a scope is defined in the model we should check that the roots are - // valid *for each* value in the scope columns. - if ( $this->node->isScoped() ) - return $this->validateRootsByScope($roots); - - return $this->isEachRootValid($roots); - } - - /** - * Checks if duplicate values for the column specified exist. Takes - * the Nested Set scope columns into account (if appropiate). - * - * @param string $column - * @return boolean - */ - protected function duplicatesExistForColumn($column) { - $connection = $this->node->getConnection(); - $grammar = $connection->getQueryGrammar(); - - $columns = array_merge($this->node->getQualifiedScopedColumns(), array($column)); - - $columnsForSelect = implode(', ', array_map(function($col) use ($grammar) { - return $grammar->wrap($col); }, $columns)); - - $wrappedColumn = $grammar->wrap($column); - - $query = $this->node->newQuery() - ->select($connection->raw("$columnsForSelect, COUNT($wrappedColumn)")) - ->havingRaw("COUNT($wrappedColumn) > 1"); - - foreach($columns as $col) - $query->groupBy($col); - - $result = $query->first(); - - return !is_null($result); - } - - /** - * Check that each root node in the list supplied satisfies that its bounds - * values (lft, rgt indexes) are less than the next. - * - * @param mixed $roots - * @return boolean - */ - protected function isEachRootValid($roots) { - $left = $right = 0; - - foreach($roots as $root) { - $rootLeft = $root->getLeft(); - $rootRight = $root->getRight(); - - if ( !($rootLeft > $left && $rootRight > $right) ) - return false; - - $left = $rootLeft; - $right = $rootRight; + $query = $this->node->newQuery() + ->join($connection->raw($grammar->wrapTable($tableName) . ' AS parent'), + $parentColumn, '=', $connection->raw('parent.' . $grammar->wrap($primaryKeyName)), + 'left outer') + ->whereRaw($whereStm); + + return ($query->count() == 0); + } + + /** + * Checks that there are no duplicates for the `lft` and `rgt` columns. + * + * @return boolean + */ + protected function validateDuplicates() + { + return ( + !$this->duplicatesExistForColumn($this->node->getQualifiedLeftColumnName()) && + !$this->duplicatesExistForColumn($this->node->getQualifiedRightColumnName()) + ); + } + + /** + * For each root of the whole nested set tree structure, checks that their + * `lft` and `rgt` bounds are properly set. + * + * @return boolean + */ + protected function validateRoots() + { + $roots = forward_static_call(array(get_class($this->node), 'roots'))->get(); + + // If a scope is defined in the model we should check that the roots are + // valid *for each* value in the scope columns. + if ($this->node->isScoped()) + return $this->validateRootsByScope($roots); + + return $this->isEachRootValid($roots); } - return true; - } - - /** - * Check that each root node in the list supplied satisfies that its bounds - * values (lft, rgt indexes) are less than the next *within each scope*. - * - * @param mixed $roots - * @return boolean - */ - protected function validateRootsByScope($roots) { - foreach($this->groupRootsByScope($roots) as $scope => $groupedRoots) { - $valid = $this->isEachRootValid($groupedRoots); - - if ( !$valid ) - return false; + /** + * Checks if duplicate values for the column specified exist. Takes + * the Nested Set scope columns into account (if appropiate). + * + * @param string $column + * @return boolean + */ + protected function duplicatesExistForColumn($column) + { + $connection = $this->node->getConnection(); + $grammar = $connection->getQueryGrammar(); + + $columns = array_merge($this->node->getQualifiedScopedColumns(), array($column)); + + $columnsForSelect = implode(', ', array_map(function ($col) use ($grammar) { + return $grammar->wrap($col); + }, $columns)); + + $wrappedColumn = $grammar->wrap($column); + + $query = $this->node->newQuery() + ->select($connection->raw("$columnsForSelect, COUNT($wrappedColumn)")) + ->havingRaw("COUNT($wrappedColumn) > 1"); + + foreach ($columns as $col) + $query->groupBy($col); + + $result = $query->first(); + + return !is_null($result); + } + + /** + * Check that each root node in the list supplied satisfies that its bounds + * values (lft, rgt indexes) are less than the next. + * + * @param mixed $roots + * @return boolean + */ + protected function isEachRootValid($roots) + { + $left = $right = 0; + + foreach ($roots as $root) { + $rootLeft = $root->getLeft(); + $rootRight = $root->getRight(); + + if (!($rootLeft > $left && $rootRight > $right)) + return false; + + $left = $rootLeft; + $right = $rootRight; + } + + return true; } - return true; - } + /** + * Check that each root node in the list supplied satisfies that its bounds + * values (lft, rgt indexes) are less than the next *within each scope*. + * + * @param mixed $roots + * @return boolean + */ + protected function validateRootsByScope($roots) + { + foreach ($this->groupRootsByScope($roots) as $scope => $groupedRoots) { + $valid = $this->isEachRootValid($groupedRoots); + + if (!$valid) + return false; + } + + return true; + } + + /** + * Given a list of root nodes, it returns an array in which the keys are the + * array of the actual scope column values and the values are the root nodes + * inside that scope themselves + * + * @param mixed $roots + * @return array + */ + protected function groupRootsByScope($roots) + { + $rootsGroupedByScope = array(); - /** - * Given a list of root nodes, it returns an array in which the keys are the - * array of the actual scope column values and the values are the root nodes - * inside that scope themselves - * - * @param mixed $roots - * @return array - */ - protected function groupRootsByScope($roots) { - $rootsGroupedByScope = array(); + foreach ($roots as $root) { + $key = $this->keyForScope($root); - foreach($roots as $root) { - $key = $this->keyForScope($root); + if (!isset($rootsGroupedByScope[$key])) + $rootsGroupedByScope[$key] = array(); - if ( !isset($rootsGroupedByScope[$key]) ) - $rootsGroupedByScope[$key] = array(); + $rootsGroupedByScope[$key][] = $root; + } - $rootsGroupedByScope[$key][] = $root; + return $rootsGroupedByScope; } - return $rootsGroupedByScope; - } - - /** - * Builds a single string for the given scope columns values. Useful for - * making array keys for grouping. - * - * @param Baum\Node $node - * @return string - */ - protected function keyForScope($node) { - return implode('-', array_map(function($column) use ($node) { - $value = $node->getAttribute($column); - - if ( is_null($value) ) - return 'NULL'; - - return $value; - }, $node->getScopedColumns())); - } + /** + * Builds a single string for the given scope columns values. Useful for + * making array keys for grouping. + * + * @param Baum\Node $node + * @return string + */ + protected function keyForScope($node) + { + return implode('-', array_map(function ($column) use ($node) { + $value = $node->getAttribute($column); + + if (is_null($value)) + return 'NULL'; + + return $value; + }, $node->getScopedColumns())); + } } diff --git a/tests/suite/BaumTestCase.php b/tests/suite/BaumTestCase.php index 68b6e445..17d730a4 100644 --- a/tests/suite/BaumTestCase.php +++ b/tests/suite/BaumTestCase.php @@ -2,13 +2,13 @@ use PHPUnit\Framework\TestCase; -class BaumTestCase extends TestCase { - - public function assertArraysAreEqual($expected, $actual, $message = '') { - $ex = var_export($expected, true); - $ac = var_export($actual, true); - - return $this->assertEquals($ex, $ac, $message); - } - +class BaumTestCase extends TestCase +{ + public function assertArraysAreEqual($expected, $actual, $message = '') + { + $ex = var_export($expected, true); + $ac = var_export($actual, true); + + $this->assertEquals($ex, $ac, $message); + } } diff --git a/tests/suite/Category/CategoryColumnsTest.php b/tests/suite/Category/CategoryColumnsTest.php index 02f1bd3b..f970eead 100644 --- a/tests/suite/Category/CategoryColumnsTest.php +++ b/tests/suite/Category/CategoryColumnsTest.php @@ -1,131 +1,150 @@ assertEquals(with(new Category)->getParentColumnName(), 'parent_id'); - } + $this->assertEquals(with(new Category)->getParentColumnName(), 'parent_id'); + } - public function testGetQualifiedParentColumnName() { - $category = new Category; + public function testGetQualifiedParentColumnName() + { + $category = new Category; - $this->assertEquals($category->getQualifiedParentColumnName(), 'categories.parent_id'); - } + $this->assertEquals($category->getQualifiedParentColumnName(), 'categories.parent_id'); + } - public function testGetParentId() { - $this->assertNull($this->categories('Root 1')->getParentId()); + public function testGetParentId() + { + $this->assertNull($this->categories('Root 1')->getParentId()); - $this->assertEquals($this->categories('Child 1')->getParentId(), 1); - } + $this->assertEquals($this->categories('Child 1')->getParentId(), 1); + } - public function testGetLeftColumnName() { - $category = new Category; + public function testGetLeftColumnName() + { + $category = new Category; - $this->assertEquals($category->getLeftColumnName(), 'lft'); - } + $this->assertEquals($category->getLeftColumnName(), 'lft'); + } - public function testGetQualifiedLeftColumnName() { - $category = new Category; + public function testGetQualifiedLeftColumnName() + { + $category = new Category; - $this->assertEquals($category->getQualifiedLeftColumnName(), 'categories.lft'); - } + $this->assertEquals($category->getQualifiedLeftColumnName(), 'categories.lft'); + } - public function testGetLeft() { - $category = $this->categories('Root 1'); + public function testGetLeft() + { + $category = $this->categories('Root 1'); - $this->assertEquals($category->getLeft(), 1); - } + $this->assertEquals($category->getLeft(), 1); + } - public function testGetRightColumnName() { - $category = new Category; + public function testGetRightColumnName() + { + $category = new Category; - $this->assertEquals($category->getRightColumnName(), 'rgt'); - } + $this->assertEquals($category->getRightColumnName(), 'rgt'); + } - public function testGetQualifiedRightColumnName() { - $category = new Category; + public function testGetQualifiedRightColumnName() + { + $category = new Category; - $this->assertEquals($category->getQualifiedRightColumnName(), 'categories.rgt'); - } + $this->assertEquals($category->getQualifiedRightColumnName(), 'categories.rgt'); + } - public function testGetRight() { - $category = $this->categories('Root 1'); + public function testGetRight() + { + $category = $this->categories('Root 1'); - $this->assertEquals($category->getRight(), 10); - } + $this->assertEquals($category->getRight(), 10); + } - public function testGetOrderColumName() { - $category = new Category; + public function testGetOrderColumName() + { + $category = new Category; - $this->assertEquals($category->getOrderColumnName(), $category->getLeftColumnName()); - } + $this->assertEquals($category->getOrderColumnName(), $category->getLeftColumnName()); + } - public function testGetQualifiedOrderColumnName() { - $category = new Category; + public function testGetQualifiedOrderColumnName() + { + $category = new Category; - $this->assertEquals($category->getQualifiedOrderColumnName(), $category->getQualifiedLeftColumnName()); - } + $this->assertEquals($category->getQualifiedOrderColumnName(), $category->getQualifiedLeftColumnName()); + } - public function testGetOrder() { - $category = $this->categories('Root 1'); + public function testGetOrder() + { + $category = $this->categories('Root 1'); - $this->assertEquals($category->getOrder(), $category->getLeft()); - } + $this->assertEquals($category->getOrder(), $category->getLeft()); + } - public function testGetOrderColumnNameNonDefault() { - $category = new OrderedCategory; + public function testGetOrderColumnNameNonDefault() + { + $category = new OrderedCategory; - $this->assertEquals($category->getOrderColumnName(), 'name'); - } + $this->assertEquals($category->getOrderColumnName(), 'name'); + } - public function testGetQualifiedOrderColumnNameNonDefault() { - $category = new OrderedCategory; + public function testGetQualifiedOrderColumnNameNonDefault() + { + $category = new OrderedCategory; - $this->assertEquals($category->getQualifiedOrderColumnName(), 'categories.name'); - } + $this->assertEquals($category->getQualifiedOrderColumnName(), 'categories.name'); + } - public function testGetOrderNonDefault() { - $category = $this->categories('Root 1', 'OrderedCategory'); + public function testGetOrderNonDefault() + { + $category = $this->categories('Root 1', 'OrderedCategory'); - $this->assertEquals($category->getOrder(), 'Root 1'); - } + $this->assertEquals($category->getOrder(), 'Root 1'); + } - public function testGetScopedColumns() { - $category = new Category; - $this->assertEquals($category->getScopedColumns(), array()); + public function testGetScopedColumns() + { + $category = new Category; + $this->assertEquals($category->getScopedColumns(), array()); - $category = new ScopedCategory; - $this->assertEquals($category->getScopedColumns(), array('company_id')); + $category = new ScopedCategory; + $this->assertEquals($category->getScopedColumns(), array('company_id')); - $category = new MultiScopedCategory; - $this->assertEquals($category->getScopedColumns(), array('company_id', 'language')); - } + $category = new MultiScopedCategory; + $this->assertEquals($category->getScopedColumns(), array('company_id', 'language')); + } - public function testGetQualifiedScopedColumns() { - $category = new Category; - $this->assertEquals($category->getQualifiedScopedColumns(), array()); + public function testGetQualifiedScopedColumns() + { + $category = new Category; + $this->assertEquals($category->getQualifiedScopedColumns(), array()); - $category = new ScopedCategory; - $this->assertEquals($category->getQualifiedScopedColumns(), array('categories.company_id')); + $category = new ScopedCategory; + $this->assertEquals($category->getQualifiedScopedColumns(), array('categories.company_id')); - $category = new MultiScopedCategory; - $this->assertEquals($category->getQualifiedScopedColumns(), array('categories.company_id', 'categories.language')); - } + $category = new MultiScopedCategory; + $this->assertEquals($category->getQualifiedScopedColumns(), array('categories.company_id', 'categories.language')); + } - public function testIsScoped() { - $category = new Category; - $this->assertFalse($category->isScoped()); + public function testIsScoped() + { + $category = new Category; + $this->assertFalse($category->isScoped()); - $category = new ScopedCategory; - $this->assertTrue($category->isScoped()); + $category = new ScopedCategory; + $this->assertTrue($category->isScoped()); - $category = new MultiScopedCategory; - $this->assertTrue($category->isScoped()); + $category = new MultiScopedCategory; + $this->assertTrue($category->isScoped()); - $category = new OrderedCategory(); - $this->assertFalse($category->isScoped()); - } + $category = new OrderedCategory(); + $this->assertFalse($category->isScoped()); + } } diff --git a/tests/suite/Category/CategoryCustomEventsTest.php b/tests/suite/Category/CategoryCustomEventsTest.php index ec2ada15..a5b088f8 100644 --- a/tests/suite/Category/CategoryCustomEventsTest.php +++ b/tests/suite/Category/CategoryCustomEventsTest.php @@ -2,55 +2,61 @@ use Mockery as m; -class CategoryCustomEventsTest extends CategoryTestCase { +class CategoryCustomEventsTest extends CategoryTestCase +{ - public function tearDown() { - m::close(); - } + public function tearDown(): void + { + m::close(); + } - public function testMovementEventsFire() { - $child1 = $this->categories('Child 1'); - $child3 = $this->categories('Child 3'); + public function testMovementEventsFire() + { + $child1 = $this->categories('Child 1'); + $child3 = $this->categories('Child 3'); - $dispatcher = Category::getEventDispatcher(); + $dispatcher = Category::getEventDispatcher(); - Category::setEventDispatcher($events = m::mock('\Illuminate\Events\Dispatcher')->makePartial()); + Category::setEventDispatcher($events = m::mock('\Illuminate\Events\Dispatcher')->makePartial()); - $events->shouldReceive('until')->once()->with('eloquent.moving: '.get_class($child1), $child1)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.moving: ' . get_class($child1), $child1)->andReturn(true); - $events->shouldReceive('dispatch')->once()->with('eloquent.moved: '.get_class($child1), $child1)->andReturn(true); + $events->shouldReceive('dispatch')->once()->with('eloquent.moved: ' . get_class($child1), $child1)->andReturn(true); - $child1->moveToRightOf($child3); + $child1->moveToRightOf($child3); - Category::unsetEventDispatcher(); - Category::setEventDispatcher($dispatcher); - } + Category::unsetEventDispatcher(); + Category::setEventDispatcher($dispatcher); + } - public function testMovementHaltsWhenReturningFalseFromMoving() { - $unchanged = $this->categories('Child 2'); + public function testMovementHaltsWhenReturningFalseFromMoving() + { + $unchanged = $this->categories('Child 2'); - $dispatcher = Category::getEventDispatcher(); + $dispatcher = Category::getEventDispatcher(); - Category::setEventDispatcher($events = m::mock('Illuminate\Events\Dispatcher')->makePartial()); - $events->shouldReceive('until')->once()->with('eloquent.moving: '.get_class($unchanged), $unchanged)->andReturn(false); + Category::setEventDispatcher($events = m::mock('Illuminate\Events\Dispatcher')->makePartial()); + $events->shouldReceive('until')->once()->with('eloquent.moving: ' . get_class($unchanged), $unchanged)->andReturn(false); - // Force "moving" to return false - Category::moving(function($node) { return false; }); + // Force "moving" to return false + Category::moving(function ($node) { + return false; + }); - $unchanged->makeRoot(); + $unchanged->makeRoot(); - $unchanged->reload(); + $unchanged->reload(); - $this->assertEquals(1, $unchanged->getParentId()); - $this->assertEquals(1, $unchanged->getLevel()); - $this->assertEquals(4, $unchanged->getLeft()); - $this->assertEquals(7, $unchanged->getRight()); + $this->assertEquals(1, $unchanged->getParentId()); + $this->assertEquals(1, $unchanged->getLevel()); + $this->assertEquals(4, $unchanged->getLeft()); + $this->assertEquals(7, $unchanged->getRight()); - // Restore - Category::getEventDispatcher()->forget('eloquent.moving: '.get_class($unchanged)); + // Restore + Category::getEventDispatcher()->forget('eloquent.moving: ' . get_class($unchanged)); - Category::unsetEventDispatcher(); - Category::setEventDispatcher($dispatcher); - } + Category::unsetEventDispatcher(); + Category::setEventDispatcher($dispatcher); + } } diff --git a/tests/suite/Category/CategoryHierarchyTest.php b/tests/suite/Category/CategoryHierarchyTest.php index bad299c6..2f40d0a7 100644 --- a/tests/suite/Category/CategoryHierarchyTest.php +++ b/tests/suite/Category/CategoryHierarchyTest.php @@ -1,705 +1,761 @@ orderBy('lft')->get(); + public function testAllStatic() + { + $results = Category::all(); + $expected = Category::query()->orderBy('lft')->get(); - $this->assertEquals($results, $expected); - } + $this->assertEquals($results, $expected); + } - public function testAllStaticWithCustomOrder() { - $results = OrderedCategory::all(); - $expected = OrderedCategory::query()->orderBy('name')->get(); + public function testAllStaticWithCustomOrder() + { + $results = OrderedCategory::all(); + $expected = OrderedCategory::query()->orderBy('name')->get(); - $this->assertEquals($results, $expected); - } + $this->assertEquals($results, $expected); + } - public function testRootsStatic() { - $query = Category::whereNull('parent_id')->get(); + public function testRootsStatic() + { + $query = Category::whereNull('parent_id')->get(); - $roots = Category::roots()->get(); + $roots = Category::roots()->get(); - $this->assertEquals($query->count(), $roots->count()); - $this->assertCount(2, $roots); + $this->assertEquals($query->count(), $roots->count()); + $this->assertCount(2, $roots); - foreach($query->pluck('id')as $node) - $this->assertContains($node, $roots->pluck('id')); - } + foreach ($query->pluck('id') as $node) + $this->assertContains($node, $roots->pluck('id')); + } - public function testRootsStaticWithCustomOrder() { - $category = OrderedCategory::create(array('name' => 'A new root is born')); - $category->syncOriginal(); // ¿? --> This should be done already !? + public function testRootsStaticWithCustomOrder() + { + $category = OrderedCategory::create(array('name' => 'A new root is born')); + $category->syncOriginal(); // ¿? --> This should be done already !? - $roots = OrderedCategory::roots()->get(); + $roots = OrderedCategory::roots()->get(); - $this->assertCount(3, $roots); - $this->assertEquals($category->getAttributes(), $roots->first()->getAttributes()); - } + $this->assertCount(3, $roots); + $this->assertEquals($category->getAttributes(), $roots->first()->getAttributes()); + } - public function testRootStatic() { - $this->assertEquals(Category::root(), $this->categories('Root 1')); - } + public function testRootStatic() + { + $this->assertEquals(Category::root(), $this->categories('Root 1')); + } - public function testAllLeavesStatic() { - $allLeaves = Category::allLeaves()->get(); + public function testAllLeavesStatic() + { + $allLeaves = Category::allLeaves()->get(); - $this->assertCount(4, $allLeaves); + $this->assertCount(4, $allLeaves); - $leaves = $allLeaves->pluck('name'); + $leaves = $allLeaves->pluck('name'); - $this->assertContains('Child 1' , $leaves); - $this->assertContains('Child 2.1' , $leaves); - $this->assertContains('Child 3' , $leaves); - $this->assertContains('Root 2' , $leaves); - } + $this->assertContains('Child 1', $leaves); + $this->assertContains('Child 2.1', $leaves); + $this->assertContains('Child 3', $leaves); + $this->assertContains('Root 2', $leaves); + } - public function testAllTrunksStatic() { - $allTrunks = Category::allTrunks()->get(); + public function testAllTrunksStatic() + { + $allTrunks = Category::allTrunks()->get(); - $this->assertCount(1, $allTrunks); + $this->assertCount(1, $allTrunks); - $trunks = $allTrunks->pluck('name'); - $this->assertContains('Child 2', $trunks); - } + $trunks = $allTrunks->pluck('name'); + $this->assertContains('Child 2', $trunks); + } - public function testGetRoot() { - $this->assertEquals($this->categories('Root 1'), $this->categories('Root 1')->getRoot()); - $this->assertEquals($this->categories('Root 2'), $this->categories('Root 2')->getRoot()); + public function testGetRoot() + { + $this->assertEquals($this->categories('Root 1'), $this->categories('Root 1')->getRoot()); + $this->assertEquals($this->categories('Root 2'), $this->categories('Root 2')->getRoot()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Child 1')->getRoot()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Child 2')->getRoot()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Child 2.1')->getRoot()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Child 3')->getRoot()); - } + $this->assertEquals($this->categories('Root 1'), $this->categories('Child 1')->getRoot()); + $this->assertEquals($this->categories('Root 1'), $this->categories('Child 2')->getRoot()); + $this->assertEquals($this->categories('Root 1'), $this->categories('Child 2.1')->getRoot()); + $this->assertEquals($this->categories('Root 1'), $this->categories('Child 3')->getRoot()); + } - public function testGetRootEqualsSelfIfUnpersisted() { - $category = new Category; + public function testGetRootEqualsSelfIfUnpersisted() + { + $category = new Category; - $this->assertEquals($category->getRoot(), $category); - } + $this->assertEquals($category->getRoot(), $category); + } - public function testGetRootEqualsValueIfSetIfUnpersisted() { - $parent = Category::roots()->first(); + public function testGetRootEqualsValueIfSetIfUnpersisted() + { + $parent = Category::roots()->first(); - $child = new Category; - $child->setAttribute($child->getParentColumnName(), $parent->getKey()); + $child = new Category; + $child->setAttribute($child->getParentColumnName(), $parent->getKey()); - $this->assertEquals($child->getRoot(), $parent); - } + $this->assertEquals($child->getRoot(), $parent); + } - public function testIsRoot() { - $this->assertTrue($this->categories('Root 1')->isRoot()); - $this->assertTrue($this->categories('Root 2')->isRoot()); + public function testIsRoot() + { + $this->assertTrue($this->categories('Root 1')->isRoot()); + $this->assertTrue($this->categories('Root 2')->isRoot()); - $this->assertFalse($this->categories('Child 1')->isRoot()); - $this->assertFalse($this->categories('Child 2')->isRoot()); - $this->assertFalse($this->categories('Child 2.1')->isRoot()); - $this->assertFalse($this->categories('Child 3')->isRoot()); - } + $this->assertFalse($this->categories('Child 1')->isRoot()); + $this->assertFalse($this->categories('Child 2')->isRoot()); + $this->assertFalse($this->categories('Child 2.1')->isRoot()); + $this->assertFalse($this->categories('Child 3')->isRoot()); + } - public function testGetLeaves() { - $leaves = array($this->categories('Child 1'), $this->categories('Child 2.1'), $this->categories('Child 3')); + public function testGetLeaves() + { + $leaves = array($this->categories('Child 1'), $this->categories('Child 2.1'), $this->categories('Child 3')); - $this->assertEquals($leaves, $this->categories('Root 1')->getLeaves()->all()); - } + $this->assertEquals($leaves, $this->categories('Root 1')->getLeaves()->all()); + } - public function testGetLeavesInIteration() { - $node = $this->categories('Root 1'); + public function testGetLeavesInIteration() + { + $node = $this->categories('Root 1'); - $expectedIds = array(2, 4, 5); + $expectedIds = array(2, 4, 5); - foreach($node->getLeaves() as $i => $leaf) - $this->assertEquals($expectedIds[$i], $leaf->getKey()); - } + foreach ($node->getLeaves() as $i => $leaf) + $this->assertEquals($expectedIds[$i], $leaf->getKey()); + } - public function testGetTrunks() { - $trunks = array($this->categories('Child 2')); + public function testGetTrunks() + { + $trunks = array($this->categories('Child 2')); - $this->assertEquals($trunks, $this->categories('Root 1')->getTrunks()->all()); - } + $this->assertEquals($trunks, $this->categories('Root 1')->getTrunks()->all()); + } - public function testGetTrunksInIteration() { - $node = $this->categories('Root 1'); + public function testGetTrunksInIteration() + { + $node = $this->categories('Root 1'); - $expectedIds = array(3); + $expectedIds = array(3); - foreach($node->getTrunks() as $i => $trunk) - $this->assertEquals($expectedIds[$i], $trunk->getKey()); - } + foreach ($node->getTrunks() as $i => $trunk) + $this->assertEquals($expectedIds[$i], $trunk->getKey()); + } - public function testIsLeaf() { - $this->assertTrue($this->categories('Child 1')->isLeaf()); - $this->assertTrue($this->categories('Child 2.1')->isLeaf()); - $this->assertTrue($this->categories('Child 3')->isLeaf()); - $this->assertTrue($this->categories('Root 2')->isLeaf()); + public function testIsLeaf() + { + $this->assertTrue($this->categories('Child 1')->isLeaf()); + $this->assertTrue($this->categories('Child 2.1')->isLeaf()); + $this->assertTrue($this->categories('Child 3')->isLeaf()); + $this->assertTrue($this->categories('Root 2')->isLeaf()); - $this->assertFalse($this->categories('Root 1')->isLeaf()); - $this->assertFalse($this->categories('Child 2')->isLeaf()); + $this->assertFalse($this->categories('Root 1')->isLeaf()); + $this->assertFalse($this->categories('Child 2')->isLeaf()); - $new = new Category; - $this->assertFalse($new->isLeaf()); - } + $new = new Category; + $this->assertFalse($new->isLeaf()); + } - public function testIsTrunk() { - $this->assertFalse($this->categories('Child 1')->isTrunk()); - $this->assertFalse($this->categories('Child 2.1')->isTrunk()); - $this->assertFalse($this->categories('Child 3')->isTrunk()); - $this->assertFalse($this->categories('Root 2')->isTrunk()); + public function testIsTrunk() + { + $this->assertFalse($this->categories('Child 1')->isTrunk()); + $this->assertFalse($this->categories('Child 2.1')->isTrunk()); + $this->assertFalse($this->categories('Child 3')->isTrunk()); + $this->assertFalse($this->categories('Root 2')->isTrunk()); - $this->assertFalse($this->categories('Root 1')->isTrunk()); - $this->assertTrue($this->categories('Child 2')->isTrunk()); + $this->assertFalse($this->categories('Root 1')->isTrunk()); + $this->assertTrue($this->categories('Child 2')->isTrunk()); - $new = new Category; - $this->assertFalse($new->isTrunk()); - } + $new = new Category; + $this->assertFalse($new->isTrunk()); + } - public function testWithoutNodeScope() { - $child = $this->categories('Child 2.1'); + public function testWithoutNodeScope() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Root 1'), $child); + $expected = array($this->categories('Root 1'), $child); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutNode($this->categories('Child 2'))->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutNode($this->categories('Child 2'))->get()->all()); + } - public function testWithoutSelfScope() { - $child = $this->categories('Child 2.1'); + public function testWithoutSelfScope() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Root 1'), $this->categories('Child 2')); + $expected = array($this->categories('Root 1'), $this->categories('Child 2')); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutSelf()->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutSelf()->get()->all()); + } - public function testWithoutRootScope() { - $child = $this->categories('Child 2.1'); + public function testWithoutRootScope() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Child 2'), $child); + $expected = array($this->categories('Child 2'), $child); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutRoot()->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutRoot()->get()->all()); + } - public function testLimitDepthScope() { - with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 10); + public function testLimitDepthScope() + { + with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 10); - $node = $this->categories('Child 2'); + $node = $this->categories('Child 2'); - $descendancy = $node->descendants()->pluck('id')->all(); + $descendancy = $node->descendants()->pluck('id')->all(); - $this->assertEmpty($node->descendants()->limitDepth(0)->pluck('id')->all()); - $this->assertEquals($node->getAttributes(), $node->descendantsAndSelf()->limitDepth(0)->first()->getAttributes()); + $this->assertEmpty($node->descendants()->limitDepth(0)->pluck('id')->all()); + $this->assertEquals($node->getAttributes(), $node->descendantsAndSelf()->limitDepth(0)->first()->getAttributes()); - $this->assertEquals(array_slice($descendancy, 0, 3), $node->descendants()->limitDepth(3)->pluck('id')->all()); - $this->assertEquals(array_slice($descendancy, 0, 5), $node->descendants()->limitDepth(5)->pluck('id')->all()); - $this->assertEquals(array_slice($descendancy, 0, 7), $node->descendants()->limitDepth(7)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 3), $node->descendants()->limitDepth(3)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 5), $node->descendants()->limitDepth(5)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 7), $node->descendants()->limitDepth(7)->pluck('id')->all()); - $this->assertEquals($descendancy, $node->descendants()->limitDepth(1000)->pluck('id')->all()); - } + $this->assertEquals($descendancy, $node->descendants()->limitDepth(1000)->pluck('id')->all()); + } - public function testGetAncestorsAndSelf() { - $child = $this->categories('Child 2.1'); + public function testGetAncestorsAndSelf() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Root 1'), $this->categories('Child 2'), $child); + $expected = array($this->categories('Root 1'), $this->categories('Child 2'), $child); - $this->assertEquals($expected, $child->getAncestorsAndSelf()->all()); - } + $this->assertEquals($expected, $child->getAncestorsAndSelf()->all()); + } - public function testGetAncestorsAndSelfWithoutRoot() { - $child = $this->categories('Child 2.1'); + public function testGetAncestorsAndSelfWithoutRoot() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Child 2'), $child); + $expected = array($this->categories('Child 2'), $child); - $this->assertEquals($expected, $child->getAncestorsAndSelfWithoutRoot()->all()); - } + $this->assertEquals($expected, $child->getAncestorsAndSelfWithoutRoot()->all()); + } - public function testGetAncestors() { - $child = $this->categories('Child 2.1'); + public function testGetAncestors() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Root 1'), $this->categories('Child 2')); + $expected = array($this->categories('Root 1'), $this->categories('Child 2')); - $this->assertEquals($expected, $child->getAncestors()->all()); - } + $this->assertEquals($expected, $child->getAncestors()->all()); + } - public function testGetAncestorsWithoutRoot() { - $child = $this->categories('Child 2.1'); + public function testGetAncestorsWithoutRoot() + { + $child = $this->categories('Child 2.1'); - $expected = array($this->categories('Child 2')); + $expected = array($this->categories('Child 2')); - $this->assertEquals($expected, $child->getAncestorsWithoutRoot()->all()); - } + $this->assertEquals($expected, $child->getAncestorsWithoutRoot()->all()); + } - public function testGetDescendantsAndSelf() { - $parent = $this->categories('Root 1'); + public function testGetDescendantsAndSelf() + { + $parent = $this->categories('Root 1'); - $expected = array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 3') - ); + $expected = array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 3') + ); - $this->assertCount(count($expected), $parent->getDescendantsAndSelf()); + $this->assertCount(count($expected), $parent->getDescendantsAndSelf()); - $this->assertEquals($expected, $parent->getDescendantsAndSelf()->all()); - } + $this->assertEquals($expected, $parent->getDescendantsAndSelf()->all()); + } - public function testGetDescendantsAndSelfWithLimit() { - with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 3); - - $parent = $this->categories('Root 1'); - - $this->assertEquals(array($parent), $parent->getDescendantsAndSelf(0)->all()); - - $this->assertEquals(array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 3') - ), $parent->getDescendantsAndSelf(1)->all()); - - $this->assertEquals(array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 3') - ), $parent->getDescendantsAndSelf(2)->all()); - - $this->assertEquals(array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 3') - ), $parent->getDescendantsAndSelf(3)->all()); - - $this->assertEquals(array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 2.1.1.1'), - $this->categories('Child 3') - ), $parent->getDescendantsAndSelf(4)->all()); - - $this->assertEquals(array( - $parent, - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 2.1.1.1'), - $this->categories('Child 2.1.1.1.1'), - $this->categories('Child 3') - ), $parent->getDescendantsAndSelf(10)->all()); - } - - public function testGetDescendants() { - $parent = $this->categories('Root 1'); - - $expected = array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 3') - ); - - $this->assertCount(count($expected), $parent->getDescendants()); - - $this->assertEquals($expected, $parent->getDescendants()->all()); - } - - public function testGetDescendantsWithLimit() { - with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 3); - - $parent = $this->categories('Root 1'); - - $this->assertEmpty($parent->getDescendants(0)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 3') - ), $parent->getDescendants(1)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 3') - ), $parent->getDescendants(2)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 3') - ), $parent->getDescendants(3)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 2.1.1.1'), - $this->categories('Child 3') - ), $parent->getDescendants(4)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 2.1.1.1'), - $this->categories('Child 2.1.1.1.1'), - $this->categories('Child 3') - ), $parent->getDescendants(5)->all()); - - $this->assertEquals(array( - $this->categories('Child 1'), - $this->categories('Child 2'), - $this->categories('Child 2.1'), - $this->categories('Child 2.1.1'), - $this->categories('Child 2.1.1.1'), - $this->categories('Child 2.1.1.1.1'), - $this->categories('Child 3') - ), $parent->getDescendants(10)->all()); - } - - public function testDescendantsRecursesChildren() { - $a = Category::create(array('name' => 'A')); - $b = Category::create(array('name' => 'B')); - $c = Category::create(array('name' => 'C')); - - // a > b > c - $b->makeChildOf($a); - $c->makeChildOf($b); - - $a->reload(); $b->reload(); $c->reload(); - - $this->assertEquals(1, $a->children()->count()); - $this->assertEquals(1, $b->children()->count()); - $this->assertEquals(2, $a->descendants()->count()); - } - - public function testGetImmediateDescendants() { - $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); - - $this->assertEquals($expected, $this->categories('Root 1')->getImmediateDescendants()->all()); - - $this->assertEquals(array($this->categories('Child 2.1')), $this->categories('Child 2')->getImmediateDescendants()->all()); - - $this->assertEmpty($this->categories('Root 2')->getImmediateDescendants()->all()); - } - - public function testIsSelfOrAncestorOf() { - $this->assertTrue($this->categories('Root 1')->isSelfOrAncestorOf($this->categories('Child 1'))); - $this->assertTrue($this->categories('Root 1')->isSelfOrAncestorOf($this->categories('Child 2.1'))); - $this->assertTrue($this->categories('Child 2')->isSelfOrAncestorOf($this->categories('Child 2.1'))); - $this->assertFalse($this->categories('Child 2.1')->isSelfOrAncestorOf($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 1')->isSelfOrAncestorOf($this->categories('Child 2'))); - $this->assertTrue($this->categories('Child 1')->isSelfOrAncestorOf($this->categories('Child 1'))); - } - - public function testIsAncestorOf() { - $this->assertTrue($this->categories('Root 1')->isAncestorOf($this->categories('Child 1'))); - $this->assertTrue($this->categories('Root 1')->isAncestorOf($this->categories('Child 2.1'))); - $this->assertTrue($this->categories('Child 2')->isAncestorOf($this->categories('Child 2.1'))); - $this->assertFalse($this->categories('Child 2.1')->isAncestorOf($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 1')->isAncestorOf($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 1')->isAncestorOf($this->categories('Child 1'))); - } - - public function testIsSelfOrDescendantOf() { - $this->assertTrue($this->categories('Child 1')->isSelfOrDescendantOf($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2.1')->isSelfOrDescendantOf($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2.1')->isSelfOrDescendantOf($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 2')->isSelfOrDescendantOf($this->categories('Child 2.1'))); - $this->assertFalse($this->categories('Child 2')->isSelfOrDescendantOf($this->categories('Child 1'))); - $this->assertTrue($this->categories('Child 1')->isSelfOrDescendantOf($this->categories('Child 1'))); - } - - public function testIsDescendantOf() { - $this->assertTrue($this->categories('Child 1')->isDescendantOf($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2.1')->isDescendantOf($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2.1')->isDescendantOf($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 2')->isDescendantOf($this->categories('Child 2.1'))); - $this->assertFalse($this->categories('Child 2')->isDescendantOf($this->categories('Child 1'))); - $this->assertFalse($this->categories('Child 1')->isDescendantOf($this->categories('Child 1'))); - } - - public function testGetSiblingsAndSelf() { - $child = $this->categories('Child 2'); - - $expected = array($this->categories('Child 1'), $child, $this->categories('Child 3')); - $this->assertEquals($expected, $child->getSiblingsAndSelf()->all()); - - $expected = array($this->categories('Root 1'), $this->categories('Root 2')); - $this->assertEquals($expected, $this->categories('Root 1')->getSiblingsAndSelf()->all()); - } - - public function testGetSiblings() { - $child = $this->categories('Child 2'); - - $expected = array($this->categories('Child 1'), $this->categories('Child 3')); - - $this->assertEquals($expected, $child->getSiblings()->all()); - } - - public function testGetLeftSibling() { - $this->assertEquals($this->categories('Child 1'), $this->categories('Child 2')->getLeftSibling()); - $this->assertEquals($this->categories('Child 2'), $this->categories('Child 3')->getLeftSibling()); - } - - public function testGetLeftSiblingOfFirstRootIsNull() { - $this->assertNull($this->categories('Root 1')->getLeftSibling()); - } - - public function testGetLeftSiblingWithNoneIsNull() { - $this->assertNull($this->categories('Child 2.1')->getLeftSibling()); - } - - public function testGetLeftSiblingOfLeftmostNodeIsNull() { - $this->assertNull($this->categories('Child 1')->getLeftSibling()); - } - - public function testGetRightSibling() { - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 2')->getRightSibling()); - $this->assertEquals($this->categories('Child 2'), $this->categories('Child 1')->getRightSibling()); - } - - public function testGetRightSiblingOfRoots() { - $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getRightSibling()); - $this->assertNull($this->categories('Root 2')->getRightSibling()); - } - - public function testGetRightSiblingWithNoneIsNull() { - $this->assertNull($this->categories('Child 2.1')->getRightSibling()); - } - - public function testGetRightSiblingOfRightmostNodeIsNull() { - $this->assertNull($this->categories('Child 3')->getRightSibling()); - } - - public function testInsideSubtree() { - $this->assertFalse($this->categories('Child 1')->insideSubtree($this->categories('Root 2'))); - $this->assertFalse($this->categories('Child 2')->insideSubtree($this->categories('Root 2'))); - $this->assertFalse($this->categories('Child 3')->insideSubtree($this->categories('Root 2'))); - - $this->assertTrue($this->categories('Child 1')->insideSubtree($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2')->insideSubtree($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 2.1')->insideSubtree($this->categories('Root 1'))); - $this->assertTrue($this->categories('Child 3')->insideSubtree($this->categories('Root 1'))); - - $this->assertTrue($this->categories('Child 2.1')->insideSubtree($this->categories('Child 2'))); - $this->assertFalse($this->categories('Child 2.1')->insideSubtree($this->categories('Root 2'))); - } - - public function testGetLevel() { - $this->assertEquals(0, $this->categories('Root 1')->getLevel()); - $this->assertEquals(1, $this->categories('Child 1')->getLevel()); - $this->assertEquals(2, $this->categories('Child 2.1')->getLevel()); - } - - public function testToHierarchyReturnsAnEloquentCollection() { - $categories = Category::all()->toHierarchy(); - - $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $categories); - } - - public function testToHierarchyReturnsHierarchicalData() { - $categories = Category::all()->toHierarchy(); - - $this->assertEquals(2, $categories->count()); - - $first = $categories->first(); - $this->assertEquals('Root 1', $first->name); - $this->assertEquals(3, $first->children->count()); - - $first_lvl2 = $first->children->first(); - $this->assertEquals('Child 1', $first_lvl2->name); - $this->assertEquals(0, $first_lvl2->children->count()); - } - - public function testToHierarchyNestsCorrectly() { - // Prune all categories - Category::query()->delete(); - - // Build a sample tree structure: - // - // - A - // |- A.1 - // |- A.2 - // - B - // |- B.1 - // |- B.2 - // |- B.2.1 - // |- B.2.2 - // |- B.2.2.1 - // |- B.2.3 - // |- B.3 - // - C - // |- C.1 - // |- C.2 - // - D - // - $a = Category::create(array('name' => 'A')); - $b = Category::create(array('name' => 'B')); - $c = Category::create(array('name' => 'C')); - $d = Category::create(array('name' => 'D')); - - $ch = Category::create(array('name' => 'A.1')); - $ch->makeChildOf($a); - - $ch = Category::create(array('name' => 'A.2')); - $ch->makeChildOf($a); - - $ch = Category::create(array('name' => 'B.1')); - $ch->makeChildOf($b); - - $ch = Category::create(array('name' => 'B.2')); - $ch->makeChildOf($b); - - $ch2 = Category::create(array('name' => 'B.2.1')); - $ch2->makeChildOf($ch); - - $ch2 = Category::create(array('name' => 'B.2.2')); - $ch2->makeChildOf($ch); - - $ch3 = Category::create(array('name' => 'B.2.2.1')); - $ch3->makeChildOf($ch2); - - $ch2 = Category::create(array('name' => 'B.2.3')); - $ch2->makeChildOf($ch); - - $ch = Category::create(array('name' => 'B.3')); - $ch->makeChildOf($b); - - $ch = Category::create(array('name' => 'C.1')); - $ch->makeChildOf($c); - - $ch = Category::create(array('name' => 'C.2')); - $ch->makeChildOf($c); - - $this->assertTrue(Category::isValidNestedSet()); - - // Build expectations (expected trees/subtrees) - $expectedWholeTree = array( - 'A' => array ( 'A.1' => null, 'A.2' => null ), - 'B' => array ( - 'B.1' => null, - 'B.2' => - array ( - 'B.2.1' => null, - 'B.2.2' => array ( 'B.2.2.1' => null ), - 'B.2.3' => null, - ), - 'B.3' => null, - ), - 'C' => array ( 'C.1' => null, 'C.2' => null ), - 'D' => null - ); - - $expectedSubtreeA = array('A' => array ( 'A.1' => null, 'A.2' => null )); - - $expectedSubtreeB = array( - 'B' => array ( - 'B.1' => null, - 'B.2' => - array ( - 'B.2.1' => null, - 'B.2.2' => array ( 'B.2.2.1' => null ), - 'B.2.3' => null - ), - 'B.3' => null - ) - ); - - $expectedSubtreeC = array( 'C.1' => null, 'C.2' => null ); - - $expectedSubtreeD = array('D' => null); - - // Perform assertions - $wholeTree = hmap(Category::all()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedWholeTree, $wholeTree); - - $subtreeA = hmap($this->categories('A')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeA, $subtreeA); - - $subtreeB = hmap($this->categories('B')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeB, $subtreeB); - - $subtreeC = hmap($this->categories('C')->getDescendants()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeC, $subtreeC); - - $subtreeD = hmap($this->categories('D')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeD, $subtreeD); - - $this->assertTrue($this->categories('D')->getDescendants()->toHierarchy()->isEmpty()); - } - - public function testToHierarchyNestsCorrectlyNotSequential() { - $parent = $this->categories('Child 1'); - - $parent->children()->create(array('name' => 'Child 1.1')); - - $parent->children()->create(array('name' => 'Child 1.2')); - - $this->assertTrue(Category::isValidNestedSet()); - - $expected = array( - 'Child 1' => array( - 'Child 1.1' => null, - 'Child 1.2' => null - ) - ); - - $parent->reload(); - $this->assertArraysAreEqual($expected, hmap($parent->getDescendantsAndSelf()->toHierarchy()->toArray())); - } - - public function testToHierarchyNestsCorrectlyWithOrder() { - with(new OrderedCategorySeeder)->run(); - - $expectedWhole = array( - 'Root A' => null, - 'Root Z' => array( - 'Child A' => null, - 'Child C' => null, - 'Child G' => array( 'Child G.1' => null ) - ) - ); - - $this->assertArraysAreEqual($expectedWhole, hmap(OrderedCategory::all()->toHierarchy()->toArray())); - - $expectedSubtreeZ = array( - 'Root Z' => array( - 'Child A' => null, - 'Child C' => null, - 'Child G' => array( 'Child G.1' => null ) - ) - ); - $this->assertArraysAreEqual($expectedSubtreeZ, hmap($this->categories('Root Z', 'OrderedCategory')->getDescendantsAndSelf()->toHierarchy()->toArray())); - } - - public function testGetNestedList() { - $seperator = ' '; - $nestedList = Category::getNestedList('name', 'id', $seperator); - - $expected = array( - 1 => str_repeat($seperator, 0). 'Root 1', - 2 => str_repeat($seperator, 1). 'Child 1', - 3 => str_repeat($seperator, 1). 'Child 2', - 4 => str_repeat($seperator, 2). 'Child 2.1', - 5 => str_repeat($seperator, 1). 'Child 3', - 6 => str_repeat($seperator, 0). 'Root 2', - ); - - $this->assertArraysAreEqual($expected, $nestedList); - } + public function testGetDescendantsAndSelfWithLimit() + { + with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 3); + + $parent = $this->categories('Root 1'); + + $this->assertEquals(array($parent), $parent->getDescendantsAndSelf(0)->all()); + + $this->assertEquals(array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 3') + ), $parent->getDescendantsAndSelf(1)->all()); + + $this->assertEquals(array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 3') + ), $parent->getDescendantsAndSelf(2)->all()); + + $this->assertEquals(array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 3') + ), $parent->getDescendantsAndSelf(3)->all()); + + $this->assertEquals(array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 2.1.1.1'), + $this->categories('Child 3') + ), $parent->getDescendantsAndSelf(4)->all()); + + $this->assertEquals(array( + $parent, + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 2.1.1.1'), + $this->categories('Child 2.1.1.1.1'), + $this->categories('Child 3') + ), $parent->getDescendantsAndSelf(10)->all()); + } + + public function testGetDescendants() + { + $parent = $this->categories('Root 1'); + + $expected = array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 3') + ); + + $this->assertCount(count($expected), $parent->getDescendants()); + + $this->assertEquals($expected, $parent->getDescendants()->all()); + } + + public function testGetDescendantsWithLimit() + { + with(new CategorySeeder)->nestUptoAt($this->categories('Child 2.1'), 3); + + $parent = $this->categories('Root 1'); + + $this->assertEmpty($parent->getDescendants(0)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 3') + ), $parent->getDescendants(1)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 3') + ), $parent->getDescendants(2)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 3') + ), $parent->getDescendants(3)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 2.1.1.1'), + $this->categories('Child 3') + ), $parent->getDescendants(4)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 2.1.1.1'), + $this->categories('Child 2.1.1.1.1'), + $this->categories('Child 3') + ), $parent->getDescendants(5)->all()); + + $this->assertEquals(array( + $this->categories('Child 1'), + $this->categories('Child 2'), + $this->categories('Child 2.1'), + $this->categories('Child 2.1.1'), + $this->categories('Child 2.1.1.1'), + $this->categories('Child 2.1.1.1.1'), + $this->categories('Child 3') + ), $parent->getDescendants(10)->all()); + } + + public function testDescendantsRecursesChildren() + { + $a = Category::create(array('name' => 'A')); + $b = Category::create(array('name' => 'B')); + $c = Category::create(array('name' => 'C')); + + // a > b > c + $b->makeChildOf($a); + $c->makeChildOf($b); + + $a->reload(); + $b->reload(); + $c->reload(); + + $this->assertEquals(1, $a->children()->count()); + $this->assertEquals(1, $b->children()->count()); + $this->assertEquals(2, $a->descendants()->count()); + } + + public function testGetImmediateDescendants() + { + $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); + + $this->assertEquals($expected, $this->categories('Root 1')->getImmediateDescendants()->all()); + + $this->assertEquals(array($this->categories('Child 2.1')), $this->categories('Child 2')->getImmediateDescendants()->all()); + + $this->assertEmpty($this->categories('Root 2')->getImmediateDescendants()->all()); + } + + public function testIsSelfOrAncestorOf() + { + $this->assertTrue($this->categories('Root 1')->isSelfOrAncestorOf($this->categories('Child 1'))); + $this->assertTrue($this->categories('Root 1')->isSelfOrAncestorOf($this->categories('Child 2.1'))); + $this->assertTrue($this->categories('Child 2')->isSelfOrAncestorOf($this->categories('Child 2.1'))); + $this->assertFalse($this->categories('Child 2.1')->isSelfOrAncestorOf($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 1')->isSelfOrAncestorOf($this->categories('Child 2'))); + $this->assertTrue($this->categories('Child 1')->isSelfOrAncestorOf($this->categories('Child 1'))); + } + + public function testIsAncestorOf() + { + $this->assertTrue($this->categories('Root 1')->isAncestorOf($this->categories('Child 1'))); + $this->assertTrue($this->categories('Root 1')->isAncestorOf($this->categories('Child 2.1'))); + $this->assertTrue($this->categories('Child 2')->isAncestorOf($this->categories('Child 2.1'))); + $this->assertFalse($this->categories('Child 2.1')->isAncestorOf($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 1')->isAncestorOf($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 1')->isAncestorOf($this->categories('Child 1'))); + } + + public function testIsSelfOrDescendantOf() + { + $this->assertTrue($this->categories('Child 1')->isSelfOrDescendantOf($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2.1')->isSelfOrDescendantOf($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2.1')->isSelfOrDescendantOf($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 2')->isSelfOrDescendantOf($this->categories('Child 2.1'))); + $this->assertFalse($this->categories('Child 2')->isSelfOrDescendantOf($this->categories('Child 1'))); + $this->assertTrue($this->categories('Child 1')->isSelfOrDescendantOf($this->categories('Child 1'))); + } + + public function testIsDescendantOf() + { + $this->assertTrue($this->categories('Child 1')->isDescendantOf($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2.1')->isDescendantOf($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2.1')->isDescendantOf($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 2')->isDescendantOf($this->categories('Child 2.1'))); + $this->assertFalse($this->categories('Child 2')->isDescendantOf($this->categories('Child 1'))); + $this->assertFalse($this->categories('Child 1')->isDescendantOf($this->categories('Child 1'))); + } + + public function testGetSiblingsAndSelf() + { + $child = $this->categories('Child 2'); + + $expected = array($this->categories('Child 1'), $child, $this->categories('Child 3')); + $this->assertEquals($expected, $child->getSiblingsAndSelf()->all()); + + $expected = array($this->categories('Root 1'), $this->categories('Root 2')); + $this->assertEquals($expected, $this->categories('Root 1')->getSiblingsAndSelf()->all()); + } + + public function testGetSiblings() + { + $child = $this->categories('Child 2'); + + $expected = array($this->categories('Child 1'), $this->categories('Child 3')); + + $this->assertEquals($expected, $child->getSiblings()->all()); + } + + public function testGetLeftSibling() + { + $this->assertEquals($this->categories('Child 1'), $this->categories('Child 2')->getLeftSibling()); + $this->assertEquals($this->categories('Child 2'), $this->categories('Child 3')->getLeftSibling()); + } + + public function testGetLeftSiblingOfFirstRootIsNull() + { + $this->assertNull($this->categories('Root 1')->getLeftSibling()); + } + + public function testGetLeftSiblingWithNoneIsNull() + { + $this->assertNull($this->categories('Child 2.1')->getLeftSibling()); + } + + public function testGetLeftSiblingOfLeftmostNodeIsNull() + { + $this->assertNull($this->categories('Child 1')->getLeftSibling()); + } + + public function testGetRightSibling() + { + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 2')->getRightSibling()); + $this->assertEquals($this->categories('Child 2'), $this->categories('Child 1')->getRightSibling()); + } + + public function testGetRightSiblingOfRoots() + { + $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getRightSibling()); + $this->assertNull($this->categories('Root 2')->getRightSibling()); + } + + public function testGetRightSiblingWithNoneIsNull() + { + $this->assertNull($this->categories('Child 2.1')->getRightSibling()); + } + + public function testGetRightSiblingOfRightmostNodeIsNull() + { + $this->assertNull($this->categories('Child 3')->getRightSibling()); + } + + public function testInsideSubtree() + { + $this->assertFalse($this->categories('Child 1')->insideSubtree($this->categories('Root 2'))); + $this->assertFalse($this->categories('Child 2')->insideSubtree($this->categories('Root 2'))); + $this->assertFalse($this->categories('Child 3')->insideSubtree($this->categories('Root 2'))); + + $this->assertTrue($this->categories('Child 1')->insideSubtree($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2')->insideSubtree($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 2.1')->insideSubtree($this->categories('Root 1'))); + $this->assertTrue($this->categories('Child 3')->insideSubtree($this->categories('Root 1'))); + + $this->assertTrue($this->categories('Child 2.1')->insideSubtree($this->categories('Child 2'))); + $this->assertFalse($this->categories('Child 2.1')->insideSubtree($this->categories('Root 2'))); + } + + public function testGetLevel() + { + $this->assertEquals(0, $this->categories('Root 1')->getLevel()); + $this->assertEquals(1, $this->categories('Child 1')->getLevel()); + $this->assertEquals(2, $this->categories('Child 2.1')->getLevel()); + } + + public function testToHierarchyReturnsAnEloquentCollection() + { + $categories = Category::all()->toHierarchy(); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $categories); + } + + public function testToHierarchyReturnsHierarchicalData() + { + $categories = Category::all()->toHierarchy(); + + $this->assertEquals(2, $categories->count()); + + $first = $categories->first(); + $this->assertEquals('Root 1', $first->name); + $this->assertEquals(3, $first->children->count()); + + $first_lvl2 = $first->children->first(); + $this->assertEquals('Child 1', $first_lvl2->name); + $this->assertEquals(0, $first_lvl2->children->count()); + } + + public function testToHierarchyNestsCorrectly() + { + // Prune all categories + Category::query()->delete(); + + // Build a sample tree structure: + // + // - A + // |- A.1 + // |- A.2 + // - B + // |- B.1 + // |- B.2 + // |- B.2.1 + // |- B.2.2 + // |- B.2.2.1 + // |- B.2.3 + // |- B.3 + // - C + // |- C.1 + // |- C.2 + // - D + // + $a = Category::create(array('name' => 'A')); + $b = Category::create(array('name' => 'B')); + $c = Category::create(array('name' => 'C')); + $d = Category::create(array('name' => 'D')); + + $ch = Category::create(array('name' => 'A.1')); + $ch->makeChildOf($a); + + $ch = Category::create(array('name' => 'A.2')); + $ch->makeChildOf($a); + + $ch = Category::create(array('name' => 'B.1')); + $ch->makeChildOf($b); + + $ch = Category::create(array('name' => 'B.2')); + $ch->makeChildOf($b); + + $ch2 = Category::create(array('name' => 'B.2.1')); + $ch2->makeChildOf($ch); + + $ch2 = Category::create(array('name' => 'B.2.2')); + $ch2->makeChildOf($ch); + + $ch3 = Category::create(array('name' => 'B.2.2.1')); + $ch3->makeChildOf($ch2); + + $ch2 = Category::create(array('name' => 'B.2.3')); + $ch2->makeChildOf($ch); + + $ch = Category::create(array('name' => 'B.3')); + $ch->makeChildOf($b); + + $ch = Category::create(array('name' => 'C.1')); + $ch->makeChildOf($c); + + $ch = Category::create(array('name' => 'C.2')); + $ch->makeChildOf($c); + + $this->assertTrue(Category::isValidNestedSet()); + + // Build expectations (expected trees/subtrees) + $expectedWholeTree = array( + 'A' => array('A.1' => null, 'A.2' => null), + 'B' => array( + 'B.1' => null, + 'B.2' => + array( + 'B.2.1' => null, + 'B.2.2' => array('B.2.2.1' => null), + 'B.2.3' => null, + ), + 'B.3' => null, + ), + 'C' => array('C.1' => null, 'C.2' => null), + 'D' => null + ); + + $expectedSubtreeA = array('A' => array('A.1' => null, 'A.2' => null)); + + $expectedSubtreeB = array( + 'B' => array( + 'B.1' => null, + 'B.2' => + array( + 'B.2.1' => null, + 'B.2.2' => array('B.2.2.1' => null), + 'B.2.3' => null + ), + 'B.3' => null + ) + ); + + $expectedSubtreeC = array('C.1' => null, 'C.2' => null); + + $expectedSubtreeD = array('D' => null); + + // Perform assertions + $wholeTree = hmap(Category::all()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedWholeTree, $wholeTree); + + $subtreeA = hmap($this->categories('A')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeA, $subtreeA); + + $subtreeB = hmap($this->categories('B')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeB, $subtreeB); + + $subtreeC = hmap($this->categories('C')->getDescendants()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeC, $subtreeC); + + $subtreeD = hmap($this->categories('D')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeD, $subtreeD); + + $this->assertTrue($this->categories('D')->getDescendants()->toHierarchy()->isEmpty()); + } + + public function testToHierarchyNestsCorrectlyNotSequential() + { + $parent = $this->categories('Child 1'); + + $parent->children()->create(array('name' => 'Child 1.1')); + + $parent->children()->create(array('name' => 'Child 1.2')); + + $this->assertTrue(Category::isValidNestedSet()); + + $expected = array( + 'Child 1' => array( + 'Child 1.1' => null, + 'Child 1.2' => null + ) + ); + + $parent->reload(); + $this->assertArraysAreEqual($expected, hmap($parent->getDescendantsAndSelf()->toHierarchy()->toArray())); + } + + public function testToHierarchyNestsCorrectlyWithOrder() + { + with(new OrderedCategorySeeder)->run(); + + $expectedWhole = array( + 'Root A' => null, + 'Root Z' => array( + 'Child A' => null, + 'Child C' => null, + 'Child G' => array('Child G.1' => null) + ) + ); + + $this->assertArraysAreEqual($expectedWhole, hmap(OrderedCategory::all()->toHierarchy()->toArray())); + + $expectedSubtreeZ = array( + 'Root Z' => array( + 'Child A' => null, + 'Child C' => null, + 'Child G' => array('Child G.1' => null) + ) + ); + $this->assertArraysAreEqual($expectedSubtreeZ, hmap($this->categories('Root Z', 'OrderedCategory')->getDescendantsAndSelf()->toHierarchy()->toArray())); + } + + public function testGetNestedList() + { + $seperator = ' '; + $nestedList = Category::getNestedList('name', 'id', $seperator); + + $expected = array( + 1 => str_repeat($seperator, 0) . 'Root 1', + 2 => str_repeat($seperator, 1) . 'Child 1', + 3 => str_repeat($seperator, 1) . 'Child 2', + 4 => str_repeat($seperator, 2) . 'Child 2.1', + 5 => str_repeat($seperator, 1) . 'Child 3', + 6 => str_repeat($seperator, 0) . 'Root 2', + ); + + $this->assertArraysAreEqual($expected, $nestedList); + } } diff --git a/tests/suite/Category/CategoryMovementTest.php b/tests/suite/Category/CategoryMovementTest.php index ddb821fd..3883a5ed 100644 --- a/tests/suite/Category/CategoryMovementTest.php +++ b/tests/suite/Category/CategoryMovementTest.php @@ -1,528 +1,568 @@ categories('Child 2')->moveLeft(); + public function testMoveLeft() + { + $this->categories('Child 2')->moveLeft(); - $this->assertNull($this->categories('Child 2')->getLeftSibling()); + $this->assertNull($this->categories('Child 2')->getLeftSibling()); - $this->assertEquals($this->categories('Child 1'), $this->categories('Child 2')->getRightSibling()); + $this->assertEquals($this->categories('Child 1'), $this->categories('Child 2')->getRightSibling()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveLeftRaisesAnExceptionWhenNotPossible() { - $node = $this->categories('Child 2'); + public function testMoveLeftRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $node->moveLeft(); - $node->moveLeft(); - } + $node = $this->categories('Child 2'); - public function testMoveLeftDoesNotChangeDepth() { - $this->categories('Child 2')->moveLeft(); + $node->moveLeft(); + $node->moveLeft(); + } - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + public function testMoveLeftDoesNotChangeDepth() + { + $this->categories('Child 2')->moveLeft(); - public function testMoveLeftWithSubtree() { - $this->categories('Root 2')->moveLeft(); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertNull($this->categories('Root 2')->getLeftSibling()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Root 2')->getRightSibling()); - $this->assertTrue(Category::isValidNestedSet()); + public function testMoveLeftWithSubtree() + { + $this->categories('Root 2')->moveLeft(); - $this->assertEquals(0, $this->categories('Root 1')->getDepth()); - $this->assertEquals(0, $this->categories('Root 2')->getDepth()); + $this->assertNull($this->categories('Root 2')->getLeftSibling()); + $this->assertEquals($this->categories('Root 1'), $this->categories('Root 2')->getRightSibling()); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals(1, $this->categories('Child 1')->getDepth()); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(1, $this->categories('Child 3')->getDepth()); + $this->assertEquals(0, $this->categories('Root 1')->getDepth()); + $this->assertEquals(0, $this->categories('Root 2')->getDepth()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $this->assertEquals(1, $this->categories('Child 1')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(1, $this->categories('Child 3')->getDepth()); - public function testMoveToLeftOf() { - $this->categories('Child 3')->moveToLeftOf($this->categories('Child 1')); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertNull($this->categories('Child 3')->getLeftSibling()); + public function testMoveToLeftOf() + { + $this->categories('Child 3')->moveToLeftOf($this->categories('Child 1')); - $this->assertEquals($this->categories('Child 1'), $this->categories('Child 3')->getRightSibling()); + $this->assertNull($this->categories('Child 3')->getLeftSibling()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertEquals($this->categories('Child 1'), $this->categories('Child 3')->getRightSibling()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveToLeftOfRaisesAnExceptionWhenNotPossible() { - $this->categories('Child 1')->moveToLeftOf($this->categories('Child 1')->getLeftSibling()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMoveToLeftOfDoesNotChangeDepth() { - $this->categories('Child 2')->moveToLeftOf($this->categories('Child 1')); + public function testMoveToLeftOfRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $this->categories('Child 1')->moveToLeftOf($this->categories('Child 1')->getLeftSibling()); + } - public function testMoveToLeftOfWithSubtree() { - $this->categories('Root 2')->moveToLeftOf($this->categories('Root 1')); + public function testMoveToLeftOfDoesNotChangeDepth() + { + $this->categories('Child 2')->moveToLeftOf($this->categories('Child 1')); - $this->assertNull($this->categories('Root 2')->getLeftSibling()); - $this->assertEquals($this->categories('Root 1'), $this->categories('Root 2')->getRightSibling()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertEquals(0, $this->categories('Root 1')->getDepth()); - $this->assertEquals(0, $this->categories('Root 2')->getDepth()); + public function testMoveToLeftOfWithSubtree() + { + $this->categories('Root 2')->moveToLeftOf($this->categories('Root 1')); - $this->assertEquals(1, $this->categories('Child 1')->getDepth()); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(1, $this->categories('Child 3')->getDepth()); + $this->assertNull($this->categories('Root 2')->getLeftSibling()); + $this->assertEquals($this->categories('Root 1'), $this->categories('Root 2')->getRightSibling()); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $this->assertEquals(0, $this->categories('Root 1')->getDepth()); + $this->assertEquals(0, $this->categories('Root 2')->getDepth()); - public function testMoveRight() { - $this->categories('Child 2')->moveRight(); + $this->assertEquals(1, $this->categories('Child 1')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(1, $this->categories('Child 3')->getDepth()); - $this->assertNull($this->categories('Child 2')->getRightSibling()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 2')->getLeftSibling()); + public function testMoveRight() + { + $this->categories('Child 2')->moveRight(); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertNull($this->categories('Child 2')->getRightSibling()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveRightRaisesAnExceptionWhenNotPossible() { - $node = $this->categories('Child 2'); + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 2')->getLeftSibling()); - $node->moveRight(); - $node->moveRight(); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMoveRightDoesNotChangeDepth() { - $this->categories('Child 2')->moveRight(); + public function testMoveRightRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $node = $this->categories('Child 2'); - public function testMoveRightWithSubtree() { - $this->categories('Root 1')->moveRight(); + $node->moveRight(); + $node->moveRight(); + } - $this->assertNull($this->categories('Root 1')->getRightSibling()); - $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getLeftSibling()); - $this->assertTrue(Category::isValidNestedSet()); + public function testMoveRightDoesNotChangeDepth() + { + $this->categories('Child 2')->moveRight(); - $this->assertEquals(0, $this->categories('Root 1')->getDepth()); - $this->assertEquals(0, $this->categories('Root 2')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertEquals(1, $this->categories('Child 1')->getDepth()); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(1, $this->categories('Child 3')->getDepth()); + public function testMoveRightWithSubtree() + { + $this->categories('Root 1')->moveRight(); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $this->assertNull($this->categories('Root 1')->getRightSibling()); + $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getLeftSibling()); + $this->assertTrue(Category::isValidNestedSet()); - public function testMoveToRightOf() { - $this->categories('Child 1')->moveToRightOf($this->categories('Child 3')); + $this->assertEquals(0, $this->categories('Root 1')->getDepth()); + $this->assertEquals(0, $this->categories('Root 2')->getDepth()); - $this->assertNull($this->categories('Child 1')->getRightSibling()); + $this->assertEquals(1, $this->categories('Child 1')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(1, $this->categories('Child 3')->getDepth()); - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->getLeftSibling()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertTrue(Category::isValidNestedSet()); - } + public function testMoveToRightOf() + { + $this->categories('Child 1')->moveToRightOf($this->categories('Child 3')); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveToRightOfRaisesAnExceptionWhenNotPossible() { - $this->categories('Child 3')->moveToRightOf($this->categories('Child 3')->getRightSibling()); - } + $this->assertNull($this->categories('Child 1')->getRightSibling()); - public function testMoveToRightOfDoesNotChangeDepth() { - $this->categories('Child 2')->moveToRightOf($this->categories('Child 3')); + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->getLeftSibling()); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMoveToRightOfWithSubtree() { - $this->categories('Root 1')->moveToRightOf($this->categories('Root 2')); + public function testMoveToRightOfRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertNull($this->categories('Root 1')->getRightSibling()); - $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getLeftSibling()); - $this->assertTrue(Category::isValidNestedSet()); + $this->categories('Child 3')->moveToRightOf($this->categories('Child 3')->getRightSibling()); + } - $this->assertEquals(0, $this->categories('Root 1')->getDepth()); - $this->assertEquals(0, $this->categories('Root 2')->getDepth()); + public function testMoveToRightOfDoesNotChangeDepth() + { + $this->categories('Child 2')->moveToRightOf($this->categories('Child 3')); - $this->assertEquals(1, $this->categories('Child 1')->getDepth()); - $this->assertEquals(1, $this->categories('Child 2')->getDepth()); - $this->assertEquals(1, $this->categories('Child 3')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); - } + public function testMoveToRightOfWithSubtree() + { + $this->categories('Root 1')->moveToRightOf($this->categories('Root 2')); - public function testMakeRoot() { - $this->categories('Child 2')->makeRoot(); + $this->assertNull($this->categories('Root 1')->getRightSibling()); + $this->assertEquals($this->categories('Root 2'), $this->categories('Root 1')->getLeftSibling()); + $this->assertTrue(Category::isValidNestedSet()); - $newRoot = $this->categories('Child 2'); + $this->assertEquals(0, $this->categories('Root 1')->getDepth()); + $this->assertEquals(0, $this->categories('Root 2')->getDepth()); - $this->assertNull($newRoot->parent()->first()); - $this->assertEquals(0, $newRoot->getLevel()); - $this->assertEquals(9, $newRoot->getLeft()); - $this->assertEquals(12, $newRoot->getRight()); + $this->assertEquals(1, $this->categories('Child 1')->getDepth()); + $this->assertEquals(1, $this->categories('Child 2')->getDepth()); + $this->assertEquals(1, $this->categories('Child 3')->getDepth()); - $this->assertEquals(1, $this->categories('Child 2.1')->getLevel()); + $this->assertEquals(2, $this->categories('Child 2.1')->getDepth()); + } - $this->assertTrue(Category::isValidNestedSet()); - } + public function testMakeRoot() + { + $this->categories('Child 2')->makeRoot(); - public function testNullifyParentColumnMakesItRoot() { - $node = $this->categories('Child 2'); + $newRoot = $this->categories('Child 2'); - $node->parent_id = null; + $this->assertNull($newRoot->parent()->first()); + $this->assertEquals(0, $newRoot->getLevel()); + $this->assertEquals(9, $newRoot->getLeft()); + $this->assertEquals(12, $newRoot->getRight()); - $node->save(); + $this->assertEquals(1, $this->categories('Child 2.1')->getLevel()); - $this->assertNull($node->parent()->first()); - $this->assertEquals(0, $node->getLevel()); - $this->assertEquals(9, $node->getLeft()); - $this->assertEquals(12, $node->getRight()); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertEquals(1, $this->categories('Child 2.1')->getLevel()); + public function testNullifyParentColumnMakesItRoot() + { + $node = $this->categories('Child 2'); - $this->assertTrue(Category::isValidNestedSet()); - } + $node->parent_id = null; - public function testNullifyParentColumnOnNewNodes() { - $node = new Category(['name' => 'Root 3']); + $node->save(); - $node->parent_id = null; + $this->assertNull($node->parent()->first()); + $this->assertEquals(0, $node->getLevel()); + $this->assertEquals(9, $node->getLeft()); + $this->assertEquals(12, $node->getRight()); - $node->save(); + $this->assertEquals(1, $this->categories('Child 2.1')->getLevel()); - $node->reload(); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertNull($node->parent()->first()); - $this->assertEquals(0, $node->getLevel()); - $this->assertEquals(13, $node->getLeft()); - $this->assertEquals(14, $node->getRight()); + public function testNullifyParentColumnOnNewNodes() + { + $node = new Category(['name' => 'Root 3']); - $this->assertTrue(Category::isValidNestedSet()); - } + $node->parent_id = null; - public function testNewCategoryWithNullParent() { - $node = new Category(['name' => 'Root 3']); - $this->assertTrue($node->isRoot()); + $node->save(); - $node->save(); - $this->assertTrue($node->isRoot()); + $node->reload(); - $node->makeRoot(); - $this->assertTrue($node->isRoot()); - } + $this->assertNull($node->parent()->first()); + $this->assertEquals(0, $node->getLevel()); + $this->assertEquals(13, $node->getLeft()); + $this->assertEquals(14, $node->getRight()); - public function testMakeChildOf() { - $this->categories('Child 1')->makeChildOf($this->categories('Child 3')); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); + public function testNewCategoryWithNullParent() + { + $node = new Category(['name' => 'Root 3']); + $this->assertTrue($node->isRoot()); - $this->assertTrue(Category::isValidNestedSet()); - } + $node->save(); + $this->assertTrue($node->isRoot()); - public function testMakeChildOfAppendsAtTheEnd() { - $newChild = Category::create(array('name' => 'Child 4')); + $node->makeRoot(); + $this->assertTrue($node->isRoot()); + } - $newChild->makeChildOf($this->categories('Root 1')); + public function testMakeChildOf() + { + $this->categories('Child 1')->makeChildOf($this->categories('Child 3')); - $lastChild = $this->categories('Root 1')->children()->get()->last(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMakeChildOfMovesWithSubtree() { - $this->categories('Child 2')->makeChildOf($this->categories('Child 1')); + public function testMakeChildOfAppendsAtTheEnd() + { + $newChild = Category::create(array('name' => 'Child 4')); - $this->assertTrue(Category::isValidNestedSet()); + $newChild->makeChildOf($this->categories('Root 1')); - $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); + $lastChild = $this->categories('Root 1')->children()->get()->last(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->categories('Child 2')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2')->getRight()); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertEquals(2, $this->categories('Child 1')->getLeft()); - $this->assertEquals(7, $this->categories('Child 1')->getRight()); - } + public function testMakeChildOfMovesWithSubtree() + { + $this->categories('Child 2')->makeChildOf($this->categories('Child 1')); - public function testMakeChildOfSwappingRoots() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); - $this->categories('Root 2')->makeChildOf($newRoot); + $this->assertEquals(3, $this->categories('Child 2')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2')->getRight()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(2, $this->categories('Child 1')->getLeft()); + $this->assertEquals(7, $this->categories('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); + public function testMakeChildOfSwappingRoots() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->categories('Root 2')->getLeft()); - $this->assertEquals(13, $this->categories('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->categories('Root 2')->makeChildOf($newRoot); - public function testMakeChildOfSwappingRootsWithSubtrees() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Root 1')->makeChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(12, $this->categories('Root 2')->getLeft()); + $this->assertEquals(13, $this->categories('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->categories('Root 1')->getLeft()); - $this->assertEquals(13, $this->categories('Root 1')->getRight()); + public function testMakeChildOfSwappingRootsWithSubtrees() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); - } + $this->categories('Root 1')->makeChildOf($newRoot); - public function testMakeFirstChildOf() { - $this->categories('Child 1')->makeFirstChildOf($this->categories('Child 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); + $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertEquals(4, $this->categories('Root 1')->getLeft()); + $this->assertEquals(13, $this->categories('Root 1')->getRight()); - public function testMakeFirstChildOfAppendsAtTheBeginning() { - $newChild = Category::create(array('name' => 'Child 4')); + $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); + } - $newChild->makeFirstChildOf($this->categories('Root 1')); + public function testMakeFirstChildOf() + { + $this->categories('Child 1')->makeFirstChildOf($this->categories('Child 3')); - $lastChild = $this->categories('Root 1')->children()->get()->first(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMakeFirstChildOfMovesWithSubtree() { - $this->categories('Child 2')->makeFirstChildOf($this->categories('Child 1')); + public function testMakeFirstChildOfAppendsAtTheBeginning() + { + $newChild = Category::create(array('name' => 'Child 4')); - $this->assertTrue(Category::isValidNestedSet()); + $newChild->makeFirstChildOf($this->categories('Root 1')); - $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); + $lastChild = $this->categories('Root 1')->children()->get()->first(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->categories('Child 2')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2')->getRight()); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertEquals(2, $this->categories('Child 1')->getLeft()); - $this->assertEquals(7, $this->categories('Child 1')->getRight()); - } + public function testMakeFirstChildOfMovesWithSubtree() + { + $this->categories('Child 2')->makeFirstChildOf($this->categories('Child 1')); - public function testMakeFirstChildOfSwappingRoots() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); - $this->categories('Root 2')->makeFirstChildOf($newRoot); + $this->assertEquals(3, $this->categories('Child 2')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2')->getRight()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(2, $this->categories('Child 1')->getLeft()); + $this->assertEquals(7, $this->categories('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); + public function testMakeFirstChildOfSwappingRoots() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->categories('Root 2')->getLeft()); - $this->assertEquals(13, $this->categories('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->categories('Root 2')->makeFirstChildOf($newRoot); - public function testMakeFirstChildOfSwappingRootsWithSubtrees() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Root 1')->makeFirstChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(12, $this->categories('Root 2')->getLeft()); + $this->assertEquals(13, $this->categories('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->categories('Root 1')->getLeft()); - $this->assertEquals(13, $this->categories('Root 1')->getRight()); + public function testMakeFirstChildOfSwappingRootsWithSubtrees() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); - } + $this->categories('Root 1')->makeFirstChildOf($newRoot); - public function testMakeLastChildOf() { - $this->categories('Child 1')->makeLastChildOf($this->categories('Child 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); + $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertEquals(4, $this->categories('Root 1')->getLeft()); + $this->assertEquals(13, $this->categories('Root 1')->getRight()); - public function testMakeLastChildOfAppendsAtTheEnd() { - $newChild = Category::create(array('name' => 'Child 4')); + $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); + } - $newChild->makeLastChildOf($this->categories('Root 1')); + public function testMakeLastChildOf() + { + $this->categories('Child 1')->makeLastChildOf($this->categories('Child 3')); - $lastChild = $this->categories('Root 1')->children()->get()->last(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->categories('Child 3'), $this->categories('Child 1')->parent()->first()); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->assertTrue(Category::isValidNestedSet()); + } - public function testMakeLastChildOfMovesWithSubtree() { - $this->categories('Child 2')->makeLastChildOf($this->categories('Child 1')); + public function testMakeLastChildOfAppendsAtTheEnd() + { + $newChild = Category::create(array('name' => 'Child 4')); - $this->assertTrue(Category::isValidNestedSet()); + $newChild->makeLastChildOf($this->categories('Root 1')); - $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); + $lastChild = $this->categories('Root 1')->children()->get()->last(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->categories('Child 2')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2')->getRight()); + $this->assertTrue(Category::isValidNestedSet()); + } - $this->assertEquals(2, $this->categories('Child 1')->getLeft()); - $this->assertEquals(7, $this->categories('Child 1')->getRight()); - } + public function testMakeLastChildOfMovesWithSubtree() + { + $this->categories('Child 2')->makeLastChildOf($this->categories('Child 1')); - public function testMakeLastChildOfSwappingRoots() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->categories('Child 1')->getKey(), $this->categories('Child 2')->getParentId()); - $this->categories('Root 2')->makeLastChildOf($newRoot); + $this->assertEquals(3, $this->categories('Child 2')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2')->getRight()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(2, $this->categories('Child 1')->getLeft()); + $this->assertEquals(7, $this->categories('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); + public function testMakeLastChildOfSwappingRoots() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->categories('Root 2')->getLeft()); - $this->assertEquals(13, $this->categories('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->categories('Root 2')->makeLastChildOf($newRoot); - public function testMakeLastChildOfSwappingRootsWithSubtrees() { - $newRoot = Category::create(array('name' => 'Root 3')); + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Root 1')->makeLastChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->categories('Root 2')->getParentId()); - $this->assertTrue(Category::isValidNestedSet()); + $this->assertEquals(12, $this->categories('Root 2')->getLeft()); + $this->assertEquals(13, $this->categories('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->categories('Root 1')->getLeft()); - $this->assertEquals(13, $this->categories('Root 1')->getRight()); + public function testMakeLastChildOfSwappingRootsWithSubtrees() + { + $newRoot = Category::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); - } + $this->categories('Root 1')->makeLastChildOf($newRoot); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testUnpersistedNodeCannotBeMoved() { - $unpersisted = new Category(array('name' => 'Unpersisted')); + $this->assertTrue(Category::isValidNestedSet()); - $unpersisted->moveToRightOf($this->categories('Root 1')); - } + $this->assertEquals($newRoot->getKey(), $this->categories('Root 1')->getParentId()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testUnpersistedNodeCannotBeMadeChild() { - $unpersisted = new Category(array('name' => 'Unpersisted')); + $this->assertEquals(4, $this->categories('Root 1')->getLeft()); + $this->assertEquals(13, $this->categories('Root 1')->getRight()); - $unpersisted->makeChildOf($this->categories('Root 1')); - } + $this->assertEquals(8, $this->categories('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->categories('Child 2.1')->getRight()); + } - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMovedToItself() { - $node = $this->categories('Child 1'); + public function testUnpersistedNodeCannotBeMoved() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $node->moveToRightOf($node); - } + $unpersisted = new Category(array('name' => 'Unpersisted')); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMadeChildOfThemselves() { - $node = $this->categories('Child 1'); + $unpersisted->moveToRightOf($this->categories('Root 1')); + } - $node->makeChildOf($node); - } + public function testUnpersistedNodeCannotBeMadeChild() + { + $this->expectException(Baum\MoveNotPossibleException::class); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMovedToDescendantsOfThemselves() { - $node = $this->categories('Root 1'); + $unpersisted = new Category(array('name' => 'Unpersisted')); - $node->makeChildOf($this->categories('Child 2.1')); - } + $unpersisted->makeChildOf($this->categories('Root 1')); + } - public function testDepthIsUpdatedWhenMadeChild() { - $a = Category::create(array('name' => 'A')); - $b = Category::create(array('name' => 'B')); - $c = Category::create(array('name' => 'C')); - $d = Category::create(array('name' => 'D')); + public function testNodesCannotBeMovedToItself() + { + $this->expectException(Baum\MoveNotPossibleException::class); - // a > b > c > d - $b->makeChildOf($a); - $c->makeChildOf($b); - $d->makeChildOf($c); + $node = $this->categories('Child 1'); - $a->reload(); - $b->reload(); - $c->reload(); - $d->reload(); + $node->moveToRightOf($node); + } - $this->assertEquals(0, $a->getDepth()); - $this->assertEquals(1, $b->getDepth()); - $this->assertEquals(2, $c->getDepth()); - $this->assertEquals(3, $d->getDepth()); - } + public function testNodesCannotBeMadeChildOfThemselves() + { + $this->expectException(Baum\MoveNotPossibleException::class); - public function testDepthIsUpdatedOnDescendantsWhenParentMoves() { - $a = Category::create(array('name' => 'A')); - $b = Category::create(array('name' => 'B')); - $c = Category::create(array('name' => 'C')); - $d = Category::create(array('name' => 'D')); + $node = $this->categories('Child 1'); - // a > b > c > d - $b->makeChildOf($a); - $c->makeChildOf($b); - $d->makeChildOf($c); + $node->makeChildOf($node); + } - $a->reload(); $b->reload(); $c->reload(); $d->reload(); + public function testNodesCannotBeMovedToDescendantsOfThemselves() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $b->moveToRightOf($a); + $node = $this->categories('Root 1'); - $a->reload(); $b->reload(); $c->reload(); $d->reload(); + $node->makeChildOf($this->categories('Child 2.1')); + } - $this->assertEquals(0, $b->getDepth()); - $this->assertEquals(1, $c->getDepth()); - $this->assertEquals(2, $d->getDepth()); - } + public function testDepthIsUpdatedWhenMadeChild() + { + $a = Category::create(array('name' => 'A')); + $b = Category::create(array('name' => 'B')); + $c = Category::create(array('name' => 'C')); + $d = Category::create(array('name' => 'D')); + + // a > b > c > d + $b->makeChildOf($a); + $c->makeChildOf($b); + $d->makeChildOf($c); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $this->assertEquals(0, $a->getDepth()); + $this->assertEquals(1, $b->getDepth()); + $this->assertEquals(2, $c->getDepth()); + $this->assertEquals(3, $d->getDepth()); + } + + public function testDepthIsUpdatedOnDescendantsWhenParentMoves() + { + $a = Category::create(array('name' => 'A')); + $b = Category::create(array('name' => 'B')); + $c = Category::create(array('name' => 'C')); + $d = Category::create(array('name' => 'D')); + + // a > b > c > d + $b->makeChildOf($a); + $c->makeChildOf($b); + $d->makeChildOf($c); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $b->moveToRightOf($a); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $this->assertEquals(0, $b->getDepth()); + $this->assertEquals(1, $c->getDepth()); + $this->assertEquals(2, $d->getDepth()); + } } diff --git a/tests/suite/Category/CategoryRelationsTest.php b/tests/suite/Category/CategoryRelationsTest.php index 7f3e008e..85dbf0a8 100644 --- a/tests/suite/Category/CategoryRelationsTest.php +++ b/tests/suite/Category/CategoryRelationsTest.php @@ -1,121 +1,135 @@ assertInstanceOf('Illuminate\Database\Eloquent\Relations\BelongsTo', $category->parent()); - } - - public function testParentRelationIsSelfReferential() { - $category = new Category; + $this->assertInstanceOf('Illuminate\Database\Eloquent\Relations\BelongsTo', $category->parent()); + } - $this->assertInstanceOf('Baum\Node', $category->parent()->getRelated()); - } + public function testParentRelationIsSelfReferential() + { + $category = new Category; - public function testParentRelationRefersToCorrectField() { - $category = new Category; + $this->assertInstanceOf('Baum\Node', $category->parent()->getRelated()); + } - if (method_exists($category->parent(), 'getForeignKeyName')) { - // For Laravel 5.6+ - $this->assertEquals($category->getParentColumnName(), $category->parent()->getForeignKeyName()); - $this->assertEquals($category->getQualifiedParentColumnName(), $category->parent()->getQualifiedForeignKeyName()); - } else { - $this->assertEquals($category->getParentColumnName(), $category->parent()->getForeignKey()); - $this->assertEquals($category->getQualifiedParentColumnName(), $category->parent()->getQualifiedForeignKey()); + public function testParentRelationRefersToCorrectField() + { + $category = new Category; + + if (method_exists($category->parent(), 'getForeignKeyName')) { + // For Laravel 5.6+ + $this->assertEquals($category->getParentColumnName(), $category->parent()->getForeignKeyName()); + $this->assertEquals($category->getQualifiedParentColumnName(), $category->parent()->getQualifiedForeignKeyName()); + } else { + $this->assertEquals($category->getParentColumnName(), $category->parent()->getForeignKey()); + $this->assertEquals($category->getQualifiedParentColumnName(), $category->parent()->getQualifiedForeignKey()); + } } - } - public function testParentRelation() { - $this->assertEquals($this->categories('Child 2.1')->parent()->first(), $this->categories('Child 2')); - $this->assertEquals($this->categories('Child 2')->parent()->first(), $this->categories('Root 1')); - $this->assertNull($this->categories('Root 1')->parent()->first()); - } + public function testParentRelation() + { + $this->assertEquals($this->categories('Child 2.1')->parent()->first(), $this->categories('Child 2')); + $this->assertEquals($this->categories('Child 2')->parent()->first(), $this->categories('Root 1')); + $this->assertNull($this->categories('Root 1')->parent()->first()); + } - public function testChildrenRelationIsAHasMany() { - $category = new Category; + public function testChildrenRelationIsAHasMany() + { + $category = new Category; - $this->assertInstanceOf('Illuminate\Database\Eloquent\Relations\HasMany', $category->children()); - } + $this->assertInstanceOf('Illuminate\Database\Eloquent\Relations\HasMany', $category->children()); + } - public function testChildrenRelationIsSelfReferential() { - $category = new Category; + public function testChildrenRelationIsSelfReferential() + { + $category = new Category; - $this->assertInstanceOf('Baum\Node', $category->children()->getRelated()); - } + $this->assertInstanceOf('Baum\Node', $category->children()->getRelated()); + } - public function testChildrenRelationReferesToCorrectField() { - $category = new Category; + public function testChildrenRelationReferesToCorrectField() + { + $category = new Category; - $this->assertEquals($category->getParentColumnName(), $category->children()->getForeignKeyName()); + $this->assertEquals($category->getParentColumnName(), $category->children()->getForeignKeyName()); - $this->assertEquals($category->getQualifiedParentColumnName(), $category->children()->getQualifiedForeignKeyName()); - } + $this->assertEquals($category->getQualifiedParentColumnName(), $category->children()->getQualifiedForeignKeyName()); + } - public function testChildrenRelation() { - $root = $this->categories('Root 1'); + public function testChildrenRelation() + { + $root = $this->categories('Root 1'); - foreach($root->children() as $child) - $this->assertEquals($root->getKey(), $child->getParentId()); + foreach ($root->children() as $child) + $this->assertEquals($root->getKey(), $child->getParentId()); - $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); + $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); - $this->assertEquals($expected, $root->children()->get()->all()); + $this->assertEquals($expected, $root->children()->get()->all()); - $this->assertEmpty($this->categories('Child 3')->children()->get()->all()); - } + $this->assertEmpty($this->categories('Child 3')->children()->get()->all()); + } - public function testChildrenRelationUsesDefaultOrdering() { - $category = new Category; + public function testChildrenRelationUsesDefaultOrdering() + { + $category = new Category; - $query = $category->children()->getQuery()->getQuery(); + $query = $category->children()->getQuery()->getQuery(); - $expected = array('column' => 'lft', 'direction' => 'asc'); - $this->assertEquals($expected, $query->orders[0]); - } + $expected = array('column' => 'lft', 'direction' => 'asc'); + $this->assertEquals($expected, $query->orders[0]); + } - public function testChildrenRelationUsesCustomOrdering() { - $category = new OrderedCategory; + public function testChildrenRelationUsesCustomOrdering() + { + $category = new OrderedCategory; - $query = $category->children()->getQuery()->getQuery(); + $query = $category->children()->getQuery()->getQuery(); - $expected = array('column' => 'name', 'direction' => 'asc'); - $this->assertEquals($expected, $query->orders[0]); - } + $expected = array('column' => 'name', 'direction' => 'asc'); + $this->assertEquals($expected, $query->orders[0]); + } - public function testChildrenRelationObeysDefaultOrdering() { - $children = $this->categories('Root 1')->children()->get()->all(); + public function testChildrenRelationObeysDefaultOrdering() + { + $children = $this->categories('Root 1')->children()->get()->all(); - $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); - $this->assertEquals($expected, $children); + $expected = array($this->categories('Child 1'), $this->categories('Child 2'), $this->categories('Child 3')); + $this->assertEquals($expected, $children); - // Swap 2 nodes & re-test - Category::query()->where('id', '=', 2)->update(array('lft' => 8, 'rgt' => 9)); - Category::query()->where('id', '=', 5)->update(array('lft' => 2, 'rgt' => 3)); + // Swap 2 nodes & re-test + Category::query()->where('id', '=', 2)->update(array('lft' => 8, 'rgt' => 9)); + Category::query()->where('id', '=', 5)->update(array('lft' => 2, 'rgt' => 3)); - $children = $this->categories('Root 1')->children()->get()->all(); + $children = $this->categories('Root 1')->children()->get()->all(); - $expected = array($this->categories('Child 3'), $this->categories('Child 2'), $this->categories('Child 1')); - $this->assertEquals($expected, $children); - } + $expected = array($this->categories('Child 3'), $this->categories('Child 2'), $this->categories('Child 1')); + $this->assertEquals($expected, $children); + } - public function testChildrenRelationObeysCustomOrdering() { - with(new OrderedCategorySeeder)->run(); + public function testChildrenRelationObeysCustomOrdering() + { + with(new OrderedCategorySeeder)->run(); - $children = OrderedCategory::find(1)->children()->get()->all(); + $children = OrderedCategory::find(1)->children()->get()->all(); - $expected = array(OrderedCategory::find(5), OrderedCategory::find(2), OrderedCategory::find(3)); - $this->assertEquals($expected, $children); - } + $expected = array(OrderedCategory::find(5), OrderedCategory::find(2), OrderedCategory::find(3)); + $this->assertEquals($expected, $children); + } - public function testChildrenRelationAllowsNodeCreation() { - $child = new Category(array('name' => 'Child 3.1')); + public function testChildrenRelationAllowsNodeCreation() + { + $child = new Category(array('name' => 'Child 3.1')); - $this->categories('Child 3')->children()->save($child); + $this->categories('Child 3')->children()->save($child); - $this->assertTrue($child->exists); - $this->assertEquals($this->categories('Child 3')->getKey(), $child->getParentId()); - } + $this->assertTrue($child->exists); + $this->assertEquals($this->categories('Child 3')->getKey(), $child->getParentId()); + } } diff --git a/tests/suite/Category/CategoryScopedTest.php b/tests/suite/Category/CategoryScopedTest.php index e723e8f3..2570cd77 100644 --- a/tests/suite/Category/CategoryScopedTest.php +++ b/tests/suite/Category/CategoryScopedTest.php @@ -1,320 +1,300 @@ run(); - } + public function setUp(): void + { + with(new MultiScopedCategorySeeder)->run(); + } - public function testInSameScope() { - $root1 = $this->categories('Root 1', 'ScopedCategory'); - $child1 = $this->categories('Child 1', 'ScopedCategory'); - $child2 = $this->categories('Child 2', 'ScopedCategory'); + public function testInSameScope() + { + $root1 = $this->categories('Root 1', 'ScopedCategory'); + $child1 = $this->categories('Child 1', 'ScopedCategory'); + $child2 = $this->categories('Child 2', 'ScopedCategory'); - $root2 = $this->categories('Root 2', 'ScopedCategory'); - $child4 = $this->categories('Child 4', 'ScopedCategory'); - $child5 = $this->categories('Child 5', 'ScopedCategory'); + $root2 = $this->categories('Root 2', 'ScopedCategory'); + $child4 = $this->categories('Child 4', 'ScopedCategory'); + $child5 = $this->categories('Child 5', 'ScopedCategory'); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - $this->assertTrue($root1->inSameScope($child1)); - $this->assertTrue($child1->inSameScope($child2)); - $this->assertTrue($child2->inSameScope($root1)); + $this->assertTrue($root1->inSameScope($child1)); + $this->assertTrue($child1->inSameScope($child2)); + $this->assertTrue($child2->inSameScope($root1)); - $this->assertTrue($root2->inSameScope($child4)); - $this->assertTrue($child4->inSameScope($child5)); - $this->assertTrue($child5->inSameScope($root2)); + $this->assertTrue($root2->inSameScope($child4)); + $this->assertTrue($child4->inSameScope($child5)); + $this->assertTrue($child5->inSameScope($root2)); - $this->assertFalse($root1->inSameScope($root2)); - $this->assertFalse($root2->inSameScope($root1)); + $this->assertFalse($root1->inSameScope($root2)); + $this->assertFalse($root2->inSameScope($root1)); - $this->assertFalse($child1->inSameScope($child4)); - $this->assertFalse($child4->inSameScope($child1)); + $this->assertFalse($child1->inSameScope($child4)); + $this->assertFalse($child4->inSameScope($child1)); - $this->assertFalse($child2->inSameScope($child5)); - $this->assertFalse($child5->inSameScope($child2)); - } + $this->assertFalse($child2->inSameScope($child5)); + $this->assertFalse($child5->inSameScope($child2)); + } - public function testInSameScopeMultiple() { - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + public function testInSameScopeMultiple() + { + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - $child1 = $this->categories('Child 1', 'MultiScopedCategory'); - $child2 = $this->categories('Child 2', 'MultiScopedCategory'); + $child1 = $this->categories('Child 1', 'MultiScopedCategory'); + $child2 = $this->categories('Child 2', 'MultiScopedCategory'); - $child4 = $this->categories('Child 4', 'MultiScopedCategory'); - $child5 = $this->categories('Child 5', 'MultiScopedCategory'); + $child4 = $this->categories('Child 4', 'MultiScopedCategory'); + $child5 = $this->categories('Child 5', 'MultiScopedCategory'); - $enfant1 = $this->categories('Enfant 1', 'MultiScopedCategory'); - $enfant2 = $this->categories('Enfant 2', 'MultiScopedCategory'); + $enfant1 = $this->categories('Enfant 1', 'MultiScopedCategory'); + $enfant2 = $this->categories('Enfant 2', 'MultiScopedCategory'); - $hijo1 = $this->categories('Hijo 1', 'MultiScopedCategory'); - $hijo2 = $this->categorieS('Hijo 2', 'MultiScopedCategory'); + $hijo1 = $this->categories('Hijo 1', 'MultiScopedCategory'); + $hijo2 = $this->categorieS('Hijo 2', 'MultiScopedCategory'); - $this->assertTrue($child1->inSameScope($child2)); - $this->assertTrue($child4->inSameScope($child5)); - $this->assertTrue($enfant1->inSameScope($enfant2)); - $this->assertTrue($hijo1->inSameScope($hijo2)); + $this->assertTrue($child1->inSameScope($child2)); + $this->assertTrue($child4->inSameScope($child5)); + $this->assertTrue($enfant1->inSameScope($enfant2)); + $this->assertTrue($hijo1->inSameScope($hijo2)); - $this->assertFalse($child2->inSameScope($child4)); - $this->assertFalse($child5->inSameScope($enfant1)); - $this->assertFalse($enfant2->inSameScope($hijo1)); - $this->assertFalse($hijo2->inSameScope($child1)); - } + $this->assertFalse($child2->inSameScope($child4)); + $this->assertFalse($child5->inSameScope($enfant1)); + $this->assertFalse($enfant2->inSameScope($hijo1)); + $this->assertFalse($hijo2->inSameScope($child1)); + } - public function testIsSelfOrAncestorOf() { - $root1 = $this->categories('Root 1', 'ScopedCategory'); - $child21 = $this->categories('Child 2.1', 'ScopedCategory'); + public function testIsSelfOrAncestorOf() + { + $root1 = $this->categories('Root 1', 'ScopedCategory'); + $child21 = $this->categories('Child 2.1', 'ScopedCategory'); - $root2 = $this->categories('Root 2', 'ScopedCategory'); - $child51 = $this->categories('Child 5.1', 'ScopedCategory'); + $root2 = $this->categories('Root 2', 'ScopedCategory'); + $child51 = $this->categories('Child 5.1', 'ScopedCategory'); - $this->assertTrue($root1->isSelfOrAncestorOf($child21)); - $this->assertTrue($root2->isSelfOrAncestorOf($child51)); + $this->assertTrue($root1->isSelfOrAncestorOf($child21)); + $this->assertTrue($root2->isSelfOrAncestorOf($child51)); - $this->assertFalse($root1->isSelfOrAncestorOf($child51)); - $this->assertFalse($root2->isSelfOrAncestorOf($child21)); - } + $this->assertFalse($root1->isSelfOrAncestorOf($child51)); + $this->assertFalse($root2->isSelfOrAncestorOf($child21)); + } - public function testIsSelfOrDescendantOf() { - $root1 = $this->categories('Root 1', 'ScopedCategory'); - $child21 = $this->categories('Child 2.1', 'ScopedCategory'); + public function testIsSelfOrDescendantOf() + { + $root1 = $this->categories('Root 1', 'ScopedCategory'); + $child21 = $this->categories('Child 2.1', 'ScopedCategory'); - $root2 = $this->categories('Root 2', 'ScopedCategory'); - $child51 = $this->categories('Child 5.1', 'ScopedCategory'); + $root2 = $this->categories('Root 2', 'ScopedCategory'); + $child51 = $this->categories('Child 5.1', 'ScopedCategory'); - $this->assertTrue($child21->isSelfOrDescendantOf($root1)); - $this->assertTrue($child51->isSelfOrDescendantOf($root2)); + $this->assertTrue($child21->isSelfOrDescendantOf($root1)); + $this->assertTrue($child51->isSelfOrDescendantOf($root2)); - $this->assertFalse($child21->isSelfOrDescendantOf($root2)); - $this->assertFalse($child51->isSelfOrDescendantOf($root1)); - } + $this->assertFalse($child21->isSelfOrDescendantOf($root2)); + $this->assertFalse($child51->isSelfOrDescendantOf($root1)); + } - public function testGetSiblingsAndSelf() { - $root2 = $this->categories('Root 2', 'ScopedCategory'); + public function testGetSiblingsAndSelf() + { + $root2 = $this->categories('Root 2', 'ScopedCategory'); - $child1 = $this->categories('Child 1', 'ScopedCategory'); - $child2 = $this->categories('Child 2', 'ScopedCategory'); - $child3 = $this->categories('Child 3', 'ScopedCategory'); + $child1 = $this->categories('Child 1', 'ScopedCategory'); + $child2 = $this->categories('Child 2', 'ScopedCategory'); + $child3 = $this->categories('Child 3', 'ScopedCategory'); - $expected = array($root2); - $this->assertEquals($expected, $root2->getSiblingsAndSelf()->all()); + $expected = array($root2); + $this->assertEquals($expected, $root2->getSiblingsAndSelf()->all()); - $expected = array($child1, $child2, $child3); - $this->assertEquals($expected, $child2->getSiblingsAndSelf()->all()); - } + $expected = array($child1, $child2, $child3); + $this->assertEquals($expected, $child2->getSiblingsAndSelf()->all()); + } - public function testGetSiblingsAndSelfMultiple() { - $root1 = $this->categories('Racine 1', 'MultiScopedCategory'); + public function testGetSiblingsAndSelfMultiple() + { + $root1 = $this->categories('Racine 1', 'MultiScopedCategory'); - $child1 = $this->categories('Hijo 1', 'MultiScopedCategory'); - $child2 = $this->categories('Hijo 2', 'MultiScopedCategory'); - $child3 = $this->categories('Hijo 3', 'MultiScopedCategory'); + $child1 = $this->categories('Hijo 1', 'MultiScopedCategory'); + $child2 = $this->categories('Hijo 2', 'MultiScopedCategory'); + $child3 = $this->categories('Hijo 3', 'MultiScopedCategory'); - $expected = array($root1); - $this->assertEquals($expected, $root1->getSiblingsAndSelf()->all()); + $expected = array($root1); + $this->assertEquals($expected, $root1->getSiblingsAndSelf()->all()); - $expected = array($child1, $child2, $child3); - $this->assertEquals($expected, $child3->getSiblingsAndSelf()->all()); - } + $expected = array($child1, $child2, $child3); + $this->assertEquals($expected, $child3->getSiblingsAndSelf()->all()); + } - public function testSimpleMovements() { - with(new ScopedCategorySeeder)->run(); + public function testSimpleMovements() + { + with(new ScopedCategorySeeder)->run(); - $this->assertTrue(ScopedCategory::isValidNestedSet()); + $this->assertTrue(ScopedCategory::isValidNestedSet()); - $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); - $this->assertTrue(ScopedCategory::isValidNestedSet()); + $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); + $this->assertTrue(ScopedCategory::isValidNestedSet()); - $this->categories('Child 6', 'ScopedCategory')->makeChildOf($root3); - $this->assertTrue(ScopedCategory::isValidNestedSet()); + $this->categories('Child 6', 'ScopedCategory')->makeChildOf($root3); + $this->assertTrue(ScopedCategory::isValidNestedSet()); - $root3->reload(); - $expected = array($this->categories('Child 6', 'ScopedCategory')); - $this->assertEquals($expected, $root3->children()->get()->all()); - } + $root3->reload(); + $expected = array($this->categories('Child 6', 'ScopedCategory')); + $this->assertEquals($expected, $root3->children()->get()->all()); + } - public function testSimpleSubtreeMovements() { - with(new ScopedCategorySeeder)->run(); - - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $this->categories('Child 5', 'ScopedCategory')->makeChildOf($root3); - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $root3->reload(); - $expected = array( - $this->categories('Child 5', 'ScopedCategory'), - $this->categories('Child 5.1', 'ScopedCategory') - ); - $this->assertEquals($expected, $root3->getDescendants()->all()); - } - - public function testFullSubtreeMovements() { - with(new ScopedCategorySeeder)->run(); - - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $this->categories('Root 2', 'ScopedCategory')->makeChildOf($root3); - $this->assertTrue(ScopedCategory::isValidNestedSet()); - - $root3->reload(); - $expected = array( - $this->categories('Root 2' , 'ScopedCategory'), - $this->categories('Child 4' , 'ScopedCategory'), - $this->categories('Child 5' , 'ScopedCategory'), - $this->categories('Child 5.1' , 'ScopedCategory'), - $this->categories('Child 6' , 'ScopedCategory') - ); - $this->assertEquals($expected, $root3->getDescendants()->all()); - } - - public function testSimpleMovementsMultiple() { - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $this->categories('Hijo 1', 'MultiScopedCategory')->makeChildOf($root2); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2->reload(); - $expected = array($this->categories('Hijo 1', 'MultiScopedCategory')); - $this->assertEquals($expected, $root2->children()->get()->all()); - } - - public function testSimpleSubtreeMovementsMultiple() { - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $this->categories('Hijo 2', 'MultiScopedCategory')->makeChildOf($root2); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2->reload(); - $expected = array( - $this->categories('Hijo 2', 'MultiScopedCategory'), - $this->categories('Hijo 2.1', 'MultiScopedCategory') - ); - $this->assertEquals($expected, $root2->getDescendants()->all()); - } - - public function testFullSubtreeMovementsMultiple() { - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $this->categories('Raiz 1', 'MultiScopedCategory')->makeChildOf($root2); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - - $root2->reload(); - $expected = array( - $this->categories('Raiz 1', 'MultiScopedCategory'), - $this->categories('Hijo 1', 'MultiScopedCategory'), - $this->categories('Hijo 2', 'MultiScopedCategory'), - $this->categories('Hijo 2.1', 'MultiScopedCategory'), - $this->categories('Hijo 3', 'MultiScopedCategory') - ); - $this->assertEquals($expected, $root2->getDescendants()->all()); - } - - public function testToHierarchyNestsCorrectlyWithScopedOrder() { - with(new OrderedScopedCategorySeeder)->run(); - - $expectedWhole1 = array( - 'Root 1' => array( - 'Child 1' => null, - 'Child 2' => array ( - 'Child 2.1' => null - ), - 'Child 3' => null - ) - ); - - $expectedWhole2 = array( - 'Root 2' => array( - 'Child 4' => null, - 'Child 5' => array ( - 'Child 5.1' => null - ), - 'Child 6' => null - ) - ); - - $this->assertArraysAreEqual($expectedWhole1, hmap(OrderedScopedCategory::where('company_id', 1)->get()->toHierarchy()->toArray())); - $this->assertArraysAreEqual($expectedWhole2, hmap(OrderedScopedCategory::where('company_id', 2)->get()->toHierarchy()->toArray())); - } - - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotMoveBetweenScopes() { - $child4 = $this->categories('Child 4', 'ScopedCategory'); - $root1 = $this->categories('Root 1', 'ScopedCategory'); - - $child4->makeChildOf($root1); - } - - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotMoveBetweenScopesMultiple() { - $root1 = $this->categories('Root 1', 'MultiScopedCategory'); - $child4 = $this->categories('Child 4', 'MultiScopedCategory'); - - $child4->makeChildOf($root1); - } - - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotMoveBetweenScopesMultiple2() { - $root1 = $this->categories('Racine 1', 'MultiScopedCategory'); - $child2 = $this->categories('Hijo 2', 'MultiScopedCategory'); - - $child2->makeChildOf($root1); - } - - // TODO: Moving nodes between scopes is problematic ATM. Fix it or find a work-around. - public function testMoveNodeBetweenScopes() { - $this->markTestSkipped(); - - // $root1 = Menu::create(array('caption' => 'TL1', 'site_id' => 1, 'language' => 'en')); - // $child11 = Menu::create(array('caption' => 'C11', 'site_id' => 1, 'language' => 'en')); - // $child12 = Menu::create(array('caption' => 'C12', 'site_id' => 1, 'language' => 'en')); - - // $this->assertTrue(Menu::isValidNestedSet()); - - // $child11->makeChildOf($root1); - // $child12->makeChildOf($root1); - - // $this->assertTrue(Menu::isValidNestedSet()); - - // $root2 = Menu::create(array('caption' => 'TL2', 'site_id' => 2, 'language' => 'en')); - // $child21 = Menu::create(array('caption' => 'C21', 'site_id' => 2, 'language' => 'en')); - // $child22 = Menu::create(array('caption' => 'C22', 'site_id' => 2, 'language' => 'en')); - // $child21->makeChildOf($root2); - // $child22->makeChildOf($root2); - - // $this->assertTrue(Menu::isValidNestedSet()); - - // $child11->update(array('site_id' => 2)); - // $child11->makeChildOf($root2); - - // $this->assertTrue(Menu::isValidNestedSet()); - - // $expected = array($this->menus('C12')); - // $this->assertEquals($expected, $root1->children()->get()->all()); - - // $expected = array($this->menus('C21'), $this->menus('C22'), $this->menus('C11')); - // $this->assertEquals($expected, $root2->children()->get()->all()); - } + public function testSimpleSubtreeMovements() + { + with(new ScopedCategorySeeder)->run(); + + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $this->categories('Child 5', 'ScopedCategory')->makeChildOf($root3); + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $root3->reload(); + $expected = array( + $this->categories('Child 5', 'ScopedCategory'), + $this->categories('Child 5.1', 'ScopedCategory') + ); + $this->assertEquals($expected, $root3->getDescendants()->all()); + } + + public function testFullSubtreeMovements() + { + with(new ScopedCategorySeeder)->run(); + + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $root3 = ScopedCategory::create(array('name' => 'Root 3', 'company_id' => 2)); + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $this->categories('Root 2', 'ScopedCategory')->makeChildOf($root3); + $this->assertTrue(ScopedCategory::isValidNestedSet()); + + $root3->reload(); + $expected = array( + $this->categories('Root 2', 'ScopedCategory'), + $this->categories('Child 4', 'ScopedCategory'), + $this->categories('Child 5', 'ScopedCategory'), + $this->categories('Child 5.1', 'ScopedCategory'), + $this->categories('Child 6', 'ScopedCategory') + ); + $this->assertEquals($expected, $root3->getDescendants()->all()); + } + + public function testSimpleMovementsMultiple() + { + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $this->categories('Hijo 1', 'MultiScopedCategory')->makeChildOf($root2); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2->reload(); + $expected = array($this->categories('Hijo 1', 'MultiScopedCategory')); + $this->assertEquals($expected, $root2->children()->get()->all()); + } + + public function testSimpleSubtreeMovementsMultiple() + { + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $this->categories('Hijo 2', 'MultiScopedCategory')->makeChildOf($root2); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2->reload(); + $expected = array( + $this->categories('Hijo 2', 'MultiScopedCategory'), + $this->categories('Hijo 2.1', 'MultiScopedCategory') + ); + $this->assertEquals($expected, $root2->getDescendants()->all()); + } + + public function testFullSubtreeMovementsMultiple() + { + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2 = MultiScopedCategory::create(array('name' => 'Raiz 2', 'company_id' => 3, 'language' => 'es')); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $this->categories('Raiz 1', 'MultiScopedCategory')->makeChildOf($root2); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + + $root2->reload(); + $expected = array( + $this->categories('Raiz 1', 'MultiScopedCategory'), + $this->categories('Hijo 1', 'MultiScopedCategory'), + $this->categories('Hijo 2', 'MultiScopedCategory'), + $this->categories('Hijo 2.1', 'MultiScopedCategory'), + $this->categories('Hijo 3', 'MultiScopedCategory') + ); + $this->assertEquals($expected, $root2->getDescendants()->all()); + } + + public function testToHierarchyNestsCorrectlyWithScopedOrder() + { + with(new OrderedScopedCategorySeeder)->run(); + + $expectedWhole1 = array( + 'Root 1' => array( + 'Child 1' => null, + 'Child 2' => array( + 'Child 2.1' => null + ), + 'Child 3' => null + ) + ); + + $expectedWhole2 = array( + 'Root 2' => array( + 'Child 4' => null, + 'Child 5' => array( + 'Child 5.1' => null + ), + 'Child 6' => null + ) + ); + + $this->assertArraysAreEqual($expectedWhole1, hmap(OrderedScopedCategory::where('company_id', 1)->get()->toHierarchy()->toArray())); + $this->assertArraysAreEqual($expectedWhole2, hmap(OrderedScopedCategory::where('company_id', 2)->get()->toHierarchy()->toArray())); + } + + public function testNodesCannotMoveBetweenScopes() + { + $this->expectException(Baum\MoveNotPossibleException::class); + + $child4 = $this->categories('Child 4', 'ScopedCategory'); + $root1 = $this->categories('Root 1', 'ScopedCategory'); + + $child4->makeChildOf($root1); + } + + public function testNodesCannotMoveBetweenScopesMultiple() + { + $this->expectException(Baum\MoveNotPossibleException::class); + + $root1 = $this->categories('Root 1', 'MultiScopedCategory'); + $child4 = $this->categories('Child 4', 'MultiScopedCategory'); + + $child4->makeChildOf($root1); + } + + public function testNodesCannotMoveBetweenScopesMultiple2() + { + $this->expectException(Baum\MoveNotPossibleException::class); + + $root1 = $this->categories('Racine 1', 'MultiScopedCategory'); + $child2 = $this->categories('Hijo 2', 'MultiScopedCategory'); + + $child2->makeChildOf($root1); + } } diff --git a/tests/suite/Category/CategorySoftDeletesTest.php b/tests/suite/Category/CategorySoftDeletesTest.php index 62ccbe55..946bb5aa 100644 --- a/tests/suite/Category/CategorySoftDeletesTest.php +++ b/tests/suite/Category/CategorySoftDeletesTest.php @@ -1,259 +1,273 @@ categories('Child 3', 'SoftCategory'); + public function testReload() + { + $node = $this->categories('Child 3', 'SoftCategory'); - $this->assertTrue($node->exists); - $this->assertFalse($node->trashed()); + $this->assertTrue($node->exists); + $this->assertFalse($node->trashed()); - $node->delete(); + $node->delete(); - $this->assertTrue($node->trashed()); + $this->assertTrue($node->trashed()); - $node->reload(); + $node->reload(); - $this->assertTrue($node->trashed()); - $this->assertTrue($node->exists); - } + $this->assertTrue($node->trashed()); + $this->assertTrue($node->exists); + } - public function testDeleteMaintainsTreeValid() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testDeleteMaintainsTreeValid() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child3 = $this->categories('Child 3', 'SoftCategory'); - $child3->delete(); + $child3 = $this->categories('Child 3', 'SoftCategory'); + $child3->delete(); - $this->assertTrue($child3->trashed()); - $this->assertTrue(SoftCategory::isValidNestedSet()); - } + $this->assertTrue($child3->trashed()); + $this->assertTrue(SoftCategory::isValidNestedSet()); + } - public function testDeleteMaintainsTreeValidWithSubtrees() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testDeleteMaintainsTreeValidWithSubtrees() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child2 = $this->categories('Child 2', 'SoftCategory'); - $child2->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $child2 = $this->categories('Child 2', 'SoftCategory'); + $child2->delete(); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1', 'SoftCategory'), - $this->categories('Child 3', 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - } + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + } - public function testDeleteShiftsIndexes() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testDeleteShiftsIndexes() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Child 1', 'SoftCategory')->delete(); + $this->categories('Child 1', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 2' , 'SoftCategory'), - $this->categories('Child 2.1' , 'SoftCategory'), - $this->categories('Child 3' , 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + $expected = array( + $this->categories('Child 2', 'SoftCategory'), + $this->categories('Child 2.1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); - $this->assertEquals(8, $this->categories('Root 1', 'SoftCategory')->getRight()); + $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); + $this->assertEquals(8, $this->categories('Root 1', 'SoftCategory')->getRight()); - $this->assertEquals(2, $this->categories('Child 2', 'SoftCategory')->getLeft()); - $this->assertEquals(5, $this->categories('Child 2', 'SoftCategory')->getRight()); + $this->assertEquals(2, $this->categories('Child 2', 'SoftCategory')->getLeft()); + $this->assertEquals(5, $this->categories('Child 2', 'SoftCategory')->getRight()); - $this->assertEquals(3, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); - $this->assertEquals(4, $this->categories('Child 2.1', 'SoftCategory')->getRight()); + $this->assertEquals(3, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); + $this->assertEquals(4, $this->categories('Child 2.1', 'SoftCategory')->getRight()); - $this->assertEquals(6, $this->categories('Child 3', 'SoftCategory')->getLeft()); - $this->assertEquals(7, $this->categories('Child 3', 'SoftCategory')->getRight()); - } + $this->assertEquals(6, $this->categories('Child 3', 'SoftCategory')->getLeft()); + $this->assertEquals(7, $this->categories('Child 3', 'SoftCategory')->getRight()); + } - public function testDeleteShiftsIndexesSubtree() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testDeleteShiftsIndexesSubtree() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Child 2', 'SoftCategory')->delete(); + $this->categories('Child 2', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1', 'SoftCategory'), - $this->categories('Child 3', 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); - $this->assertEquals(6, $this->categories('Root 1', 'SoftCategory')->getRight()); + $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); + $this->assertEquals(6, $this->categories('Root 1', 'SoftCategory')->getRight()); - $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); - $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); + $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); + $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); - $this->assertEquals(4, $this->categories('Child 3', 'SoftCategory')->getLeft()); - $this->assertEquals(5, $this->categories('Child 3', 'SoftCategory')->getRight()); - } + $this->assertEquals(4, $this->categories('Child 3', 'SoftCategory')->getLeft()); + $this->assertEquals(5, $this->categories('Child 3', 'SoftCategory')->getRight()); + } - public function testDeleteShiftsIndexesFullSubtree() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testDeleteShiftsIndexesFullSubtree() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Root 1', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->categories('Root 1', 'SoftCategory')->delete(); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->assertEmpty($this->categories('Root 2', 'SoftCategory')->getSiblings()->all()); - $this->assertEquals(1, $this->categories('Root 2', 'SoftCategory')->getLeft()); - $this->assertEquals(2, $this->categories('Root 2', 'SoftCategory')->getRight()); - } + $this->assertEmpty($this->categories('Root 2', 'SoftCategory')->getSiblings()->all()); + $this->assertEquals(1, $this->categories('Root 2', 'SoftCategory')->getLeft()); + $this->assertEquals(2, $this->categories('Root 2', 'SoftCategory')->getRight()); + } - public function testRestoreMaintainsTreeValid() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testRestoreMaintainsTreeValid() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child3 = $this->categories('Child 3', 'SoftCategory'); - $child3->delete(); + $child3 = $this->categories('Child 3', 'SoftCategory'); + $child3->delete(); - $this->assertTrue($child3->trashed()); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue($child3->trashed()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child3->reload(); - $child3->restore(); + $child3->reload(); + $child3->restore(); - $this->assertFalse($child3->trashed()); - $this->assertTrue(SoftCategory::isValidNestedSet()); - } + $this->assertFalse($child3->trashed()); + $this->assertTrue(SoftCategory::isValidNestedSet()); + } - public function testRestoreMaintainsTreeValidWithSubtrees() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testRestoreMaintainsTreeValidWithSubtrees() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child2 = $this->categories('Child 2', 'SoftCategory'); - $child2->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $child2 = $this->categories('Child 2', 'SoftCategory'); + $child2->delete(); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $child2->reload(); - $child2->restore(); + $child2->reload(); + $child2->restore(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1' , 'SoftCategory'), - $this->categories('Child 2' , 'SoftCategory'), - $this->categories('Child 2.1' , 'SoftCategory'), - $this->categories('Child 3' , 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - } + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 2', 'SoftCategory'), + $this->categories('Child 2.1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + } - public function testRestoreUnshiftsIndexes() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testRestoreUnshiftsIndexes() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Child 1', 'SoftCategory')->delete(); + $this->categories('Child 1', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - SoftCategory::withTrashed()->where('name', 'Child 1')->first()->restore(); + SoftCategory::withTrashed()->where('name', 'Child 1')->first()->restore(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1' , 'SoftCategory'), - $this->categories('Child 2' , 'SoftCategory'), - $this->categories('Child 2.1' , 'SoftCategory'), - $this->categories('Child 3' , 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 2', 'SoftCategory'), + $this->categories('Child 2.1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); - $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); + $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); + $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); - $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); - $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); + $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); + $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); - $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); - $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); - $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); + $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); + $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); + $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); - $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); - $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); - } + $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); + $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); + } - public function testRestoreUnshiftsIndexesSubtree() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testRestoreUnshiftsIndexesSubtree() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Child 2', 'SoftCategory')->delete(); + $this->categories('Child 2', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - SoftCategory::withTrashed()->where('name', 'Child 2')->first()->restore(); + SoftCategory::withTrashed()->where('name', 'Child 2')->first()->restore(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1' , 'SoftCategory'), - $this->categories('Child 2' , 'SoftCategory'), - $this->categories('Child 2.1' , 'SoftCategory'), - $this->categories('Child 3' , 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 2', 'SoftCategory'), + $this->categories('Child 2.1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); - $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); + $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); + $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); - $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); - $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); + $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); + $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); - $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); - $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); - $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); + $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); + $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); + $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); - $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); - $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); - } + $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); + $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); + } - public function testRestoreUnshiftsIndexesFullSubtree() { - $this->assertTrue(SoftCategory::isValidNestedSet()); + public function testRestoreUnshiftsIndexesFullSubtree() + { + $this->assertTrue(SoftCategory::isValidNestedSet()); - $this->categories('Root 1', 'SoftCategory')->delete(); + $this->categories('Root 1', 'SoftCategory')->delete(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - SoftCategory::withTrashed()->where('name', 'Root 1')->first()->restore(); + SoftCategory::withTrashed()->where('name', 'Root 1')->first()->restore(); - $this->assertTrue(SoftCategory::isValidNestedSet()); + $this->assertTrue(SoftCategory::isValidNestedSet()); - $expected = array( - $this->categories('Child 1' , 'SoftCategory'), - $this->categories('Child 2' , 'SoftCategory'), - $this->categories('Child 2.1' , 'SoftCategory'), - $this->categories('Child 3' , 'SoftCategory') - ); - $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); + $expected = array( + $this->categories('Child 1', 'SoftCategory'), + $this->categories('Child 2', 'SoftCategory'), + $this->categories('Child 2.1', 'SoftCategory'), + $this->categories('Child 3', 'SoftCategory') + ); + $this->assertEquals($expected, $this->categories('Root 1', 'SoftCategory')->getDescendants()->all()); - $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); - $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); + $this->assertEquals(1, $this->categories('Root 1', 'SoftCategory')->getLeft()); + $this->assertEquals(10, $this->categories('Root 1', 'SoftCategory')->getRight()); - $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); - $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); + $this->assertEquals(2, $this->categories('Child 1', 'SoftCategory')->getLeft()); + $this->assertEquals(3, $this->categories('Child 1', 'SoftCategory')->getRight()); - $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); - $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); - $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); - $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); + $this->assertEquals(4, $this->categories('Child 2', 'SoftCategory')->getLeft()); + $this->assertEquals(7, $this->categories('Child 2', 'SoftCategory')->getRight()); + $this->assertEquals(5, $this->categories('Child 2.1', 'SoftCategory')->getLeft()); + $this->assertEquals(6, $this->categories('Child 2.1', 'SoftCategory')->getRight()); - $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); - $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); - } + $this->assertEquals(8, $this->categories('Child 3', 'SoftCategory')->getLeft()); + $this->assertEquals(9, $this->categories('Child 3', 'SoftCategory')->getRight()); + } - public function testAllStatic() { - $expected = array('Root 1', 'Child 1', 'Child 2', 'Child 2.1', 'Child 3', 'Root 2'); + public function testAllStatic() + { + $expected = array('Root 1', 'Child 1', 'Child 2', 'Child 2.1', 'Child 3', 'Root 2'); - $this->assertArraysAreEqual($expected, SoftCategory::all()->pluck('name')->all()); - } + $this->assertArraysAreEqual($expected, SoftCategory::all()->pluck('name')->all()); + } - public function testAllStaticWithSoftDeletes() { - $this->categories('Child 1', 'SoftCategory')->delete(); - $this->categories('Child 3', 'SoftCategory')->delete(); + public function testAllStaticWithSoftDeletes() + { + $this->categories('Child 1', 'SoftCategory')->delete(); + $this->categories('Child 3', 'SoftCategory')->delete(); - $expected = array('Root 1', 'Child 2', 'Child 2.1', 'Root 2'); - $this->assertArraysAreEqual($expected, SoftCategory::all()->pluck('name')->all()); - } + $expected = array('Root 1', 'Child 2', 'Child 2.1', 'Root 2'); + $this->assertArraysAreEqual($expected, SoftCategory::all()->pluck('name')->all()); + } } diff --git a/tests/suite/Category/CategoryTreeMapperTest.php b/tests/suite/Category/CategoryTreeMapperTest.php index 8edd5336..d94bccaf 100644 --- a/tests/suite/Category/CategoryTreeMapperTest.php +++ b/tests/suite/Category/CategoryTreeMapperTest.php @@ -2,209 +2,215 @@ use Illuminate\Database\Capsule\Manager as DB; -class CategoryTreeMapperTest extends BaumTestCase { - - public function setUp() { - with(new CategoryMigrator)->up(); - } - - public function testBuildTree() { - $tree = array( - array('id' => 1, 'name' => 'A'), - array('id' => 2, 'name' => 'B'), - array('id' => 3, 'name' => 'C', 'children' => array( - array('id' => 4, 'name' => 'C.1', 'children' => array( - array('id' => 5, 'name' => 'C.1.1'), - array('id' => 6, 'name' => 'C.1.2') - )), - array('id' => 7, 'name' => 'C.2'), - array('id' => 8, 'name' => 'C.3') - )), - array('id' => 9, 'name' => 'D') - ); - $this->assertTrue(Category::buildTree($tree)); - $this->assertTrue(Category::isValidNestedSet()); - - $hierarchy = Category::all()->toHierarchy()->toArray(); - $this->assertArraysAreEqual($tree, array_ints_keys(hmap($hierarchy, array('id', 'name')))); - } - - public function testBuildTreePrunesAndInserts() { - $tree = array( - array('id' => 1, 'name' => 'A'), - array('id' => 2, 'name' => 'B'), - array('id' => 3, 'name' => 'C', 'children' => array( - array('id' => 4, 'name' => 'C.1', 'children' => array( - array('id' => 5, 'name' => 'C.1.1'), - array('id' => 6, 'name' => 'C.1.2') - )), - array('id' => 7, 'name' => 'C.2'), - array('id' => 8, 'name' => 'C.3') - )), - array('id' => 9, 'name' => 'D') - ); - $this->assertTrue(Category::buildTree($tree)); - $this->assertTrue(Category::isValidNestedSet()); - - // Postgres fix - if ( DB::connection()->getDriverName() === 'pgsql' ) { - $tablePrefix = DB::connection()->getTablePrefix(); - - $sequenceName = $tablePrefix . 'categories_id_seq'; - - DB::connection()->statement('ALTER SEQUENCE ' . $sequenceName . ' RESTART WITH 10'); +class CategoryTreeMapperTest extends BaumTestCase +{ + + public function setUp(): void + { + with(new CategoryMigrator)->up(); + } + + public function testBuildTree() + { + $tree = array( + array('id' => 1, 'name' => 'A'), + array('id' => 2, 'name' => 'B'), + array('id' => 3, 'name' => 'C', 'children' => array( + array('id' => 4, 'name' => 'C.1', 'children' => array( + array('id' => 5, 'name' => 'C.1.1'), + array('id' => 6, 'name' => 'C.1.2') + )), + array('id' => 7, 'name' => 'C.2'), + array('id' => 8, 'name' => 'C.3') + )), + array('id' => 9, 'name' => 'D') + ); + $this->assertTrue(Category::buildTree($tree)); + $this->assertTrue(Category::isValidNestedSet()); + + $hierarchy = Category::all()->toHierarchy()->toArray(); + $this->assertArraysAreEqual($tree, array_ints_keys(hmap($hierarchy, array('id', 'name')))); + } + + public function testBuildTreePrunesAndInserts() + { + $tree = array( + array('id' => 1, 'name' => 'A'), + array('id' => 2, 'name' => 'B'), + array('id' => 3, 'name' => 'C', 'children' => array( + array('id' => 4, 'name' => 'C.1', 'children' => array( + array('id' => 5, 'name' => 'C.1.1'), + array('id' => 6, 'name' => 'C.1.2') + )), + array('id' => 7, 'name' => 'C.2'), + array('id' => 8, 'name' => 'C.3') + )), + array('id' => 9, 'name' => 'D') + ); + $this->assertTrue(Category::buildTree($tree)); + $this->assertTrue(Category::isValidNestedSet()); + + // Postgres fix + if (DB::connection()->getDriverName() === 'pgsql') { + $tablePrefix = DB::connection()->getTablePrefix(); + + $sequenceName = $tablePrefix . 'categories_id_seq'; + + DB::connection()->statement('ALTER SEQUENCE ' . $sequenceName . ' RESTART WITH 10'); + } + + $updated = array( + array('id' => 1, 'name' => 'A'), + array('id' => 2, 'name' => 'B'), + array('id' => 3, 'name' => 'C', 'children' => array( + array('id' => 4, 'name' => 'C.1', 'children' => array( + array('id' => 5, 'name' => 'C.1.1'), + array('id' => 6, 'name' => 'C.1.2') + )), + array('id' => 7, 'name' => 'C.2', 'children' => array( + array('name' => 'C.2.1'), + array('name' => 'C.2.2') + )) + )), + array('id' => 9, 'name' => 'D') + ); + $this->assertTrue(Category::buildTree($updated)); + $this->assertTrue(Category::isValidNestedSet()); + + $expected = array( + array('id' => 1, 'name' => 'A'), + array('id' => 2, 'name' => 'B'), + array('id' => 3, 'name' => 'C', 'children' => array( + array('id' => 4, 'name' => 'C.1', 'children' => array( + array('id' => 5, 'name' => 'C.1.1'), + array('id' => 6, 'name' => 'C.1.2') + )), + array('id' => 7, 'name' => 'C.2', 'children' => array( + array('id' => 10, 'name' => 'C.2.1'), + array('id' => 11, 'name' => 'C.2.2') + )) + )), + array('id' => 9, 'name' => 'D') + ); + + $hierarchy = Category::all()->toHierarchy()->toArray(); + $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); + } + + public function testMakeTree() + { + with(new CategorySeeder)->run(); + + $parent = Category::find(3); + + $subtree = array( + array('id' => 4, 'name' => 'Child 2.1'), + array('name' => 'Child 2.2'), + array('name' => 'Child 2.3', 'children' => array( + array('name' => 'Child 2.3.1', 'children' => array( + array('name' => 'Child 2.3.1.1'), + array('name' => 'Child 2.3.1.1') + )), + array('name' => 'Child 2.3.2'), + array('name' => 'Child 2.3.3') + )), + array('name' => 'Child 2.4') + ); + + $this->assertTrue($parent->makeTree($subtree)); + $this->assertTrue(Category::isValidNestedSet()); + + $expected = array( + array('id' => 4, 'name' => 'Child 2.1'), + array('id' => 7, 'name' => 'Child 2.2'), + array('id' => 8, 'name' => 'Child 2.3', 'children' => array( + array('id' => 9, 'name' => 'Child 2.3.1', 'children' => array( + array('id' => 10, 'name' => 'Child 2.3.1.1'), + array('id' => 11, 'name' => 'Child 2.3.1.1') + )), + array('id' => 12, 'name' => 'Child 2.3.2'), + array('id' => 13, 'name' => 'Child 2.3.3') + )), + array('id' => 14, 'name' => 'Child 2.4') + ); + + $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); + $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); } - $updated = array( - array('id' => 1, 'name' => 'A'), - array('id' => 2, 'name' => 'B'), - array('id' => 3, 'name' => 'C', 'children' => array( - array('id' => 4, 'name' => 'C.1', 'children' => array( - array('id' => 5, 'name' => 'C.1.1'), - array('id' => 6, 'name' => 'C.1.2') - )), - array('id' => 7, 'name' => 'C.2', 'children' => array( - array('name' => 'C.2.1'), - array('name' => 'C.2.2') - )) - )), - array('id' => 9, 'name' => 'D') - ); - $this->assertTrue(Category::buildTree($updated)); - $this->assertTrue(Category::isValidNestedSet()); - - $expected = array( - array('id' => 1, 'name' => 'A'), - array('id' => 2, 'name' => 'B'), - array('id' => 3, 'name' => 'C', 'children' => array( - array('id' => 4, 'name' => 'C.1', 'children' => array( - array('id' => 5, 'name' => 'C.1.1'), - array('id' => 6, 'name' => 'C.1.2') - )), - array('id' => 7, 'name' => 'C.2', 'children' => array( - array('id' => 10, 'name' => 'C.2.1'), - array('id' => 11, 'name' => 'C.2.2') - )) - )), - array('id' => 9, 'name' => 'D') - ); - - $hierarchy = Category::all()->toHierarchy()->toArray(); - $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); - } - - public function testMakeTree() { - with(new CategorySeeder)->run(); - - $parent = Category::find(3); - - $subtree = array( - array('id' => 4, 'name' => 'Child 2.1'), - array('name' => 'Child 2.2'), - array('name' => 'Child 2.3', 'children' => array( - array('name' => 'Child 2.3.1', 'children' => array( - array('name' => 'Child 2.3.1.1'), - array('name' => 'Child 2.3.1.1') - )), - array('name' => 'Child 2.3.2'), - array('name' => 'Child 2.3.3') - )), - array('name' => 'Child 2.4') - ); - - $this->assertTrue($parent->makeTree($subtree)); - $this->assertTrue(Category::isValidNestedSet()); - - $expected = array( - array('id' => 4, 'name' => 'Child 2.1'), - array('id' => 7, 'name' => 'Child 2.2'), - array('id' => 8, 'name' => 'Child 2.3', 'children' => array( - array('id' => 9, 'name' => 'Child 2.3.1', 'children' => array( - array('id' => 10, 'name' => 'Child 2.3.1.1'), - array('id' => 11, 'name' => 'Child 2.3.1.1') - )), - array('id' => 12, 'name' => 'Child 2.3.2'), - array('id' => 13, 'name' => 'Child 2.3.3') - )), - array('id' => 14, 'name' => 'Child 2.4') - ); - - $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); - $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); - } - - public function testMakeTreePrunesAndInserts() { - with(new CategorySeeder)->run(); - - $parent = Category::find(3); - - $subtree = array( - array('id' => 4, 'name' => 'Child 2.1'), - array('name' => 'Child 2.2'), - array('name' => 'Child 2.3', 'children' => array( - array('name' => 'Child 2.3.1', 'children' => array( - array('name' => 'Child 2.3.1.1'), - array('name' => 'Child 2.3.1.1') - )), - array('name' => 'Child 2.3.2'), - array('name' => 'Child 2.3.3') - )), - array('name' => 'Child 2.4') - ); - - $this->assertTrue($parent->makeTree($subtree)); - $this->assertTrue(Category::isValidNestedSet()); - - $expected = array( - array('id' => 4, 'name' => 'Child 2.1'), - array('id' => 7, 'name' => 'Child 2.2'), - array('id' => 8, 'name' => 'Child 2.3', 'children' => array( - array('id' => 9, 'name' => 'Child 2.3.1', 'children' => array( - array('id' => 10, 'name' => 'Child 2.3.1.1'), - array('id' => 11, 'name' => 'Child 2.3.1.1') - )), - array('id' => 12, 'name' => 'Child 2.3.2'), - array('id' => 13, 'name' => 'Child 2.3.3') - )), - array('id' => 14, 'name' => 'Child 2.4') - ); - - $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); - $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); - - $modified = array( - array('id' => 7, 'name' => 'Child 2.2'), - array('id' => 8, 'name' => 'Child 2.3'), - array('id' => 14, 'name' => 'Child 2.4'), - array('name' => 'Child 2.5', 'children' => array( - array('name' => 'Child 2.5.1', 'children' => array( - array('name' => 'Child 2.5.1.1'), - array('name' => 'Child 2.5.1.1') - )), - array('name' => 'Child 2.5.2'), - array('name' => 'Child 2.5.3') - )) - ); - - $this->assertTrue($parent->makeTree($modified)); - $this->assertTrue(Category::isValidNestedSet()); - - $expected = array( - array('id' => 7 , 'name' => 'Child 2.2'), - array('id' => 8 , 'name' => 'Child 2.3'), - array('id' => 14, 'name' => 'Child 2.4'), - array('id' => 15, 'name' => 'Child 2.5', 'children' => array( - array('id' => 16, 'name' => 'Child 2.5.1', 'children' => array( - array('id' => 17, 'name' => 'Child 2.5.1.1'), - array('id' => 18, 'name' => 'Child 2.5.1.1') - )), - array('id' => 19, 'name' => 'Child 2.5.2'), - array('id' => 20, 'name' => 'Child 2.5.3') - )) - ); - - $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); - $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); - } + public function testMakeTreePrunesAndInserts() + { + with(new CategorySeeder)->run(); + + $parent = Category::find(3); + + $subtree = array( + array('id' => 4, 'name' => 'Child 2.1'), + array('name' => 'Child 2.2'), + array('name' => 'Child 2.3', 'children' => array( + array('name' => 'Child 2.3.1', 'children' => array( + array('name' => 'Child 2.3.1.1'), + array('name' => 'Child 2.3.1.1') + )), + array('name' => 'Child 2.3.2'), + array('name' => 'Child 2.3.3') + )), + array('name' => 'Child 2.4') + ); + + $this->assertTrue($parent->makeTree($subtree)); + $this->assertTrue(Category::isValidNestedSet()); + + $expected = array( + array('id' => 4, 'name' => 'Child 2.1'), + array('id' => 7, 'name' => 'Child 2.2'), + array('id' => 8, 'name' => 'Child 2.3', 'children' => array( + array('id' => 9, 'name' => 'Child 2.3.1', 'children' => array( + array('id' => 10, 'name' => 'Child 2.3.1.1'), + array('id' => 11, 'name' => 'Child 2.3.1.1') + )), + array('id' => 12, 'name' => 'Child 2.3.2'), + array('id' => 13, 'name' => 'Child 2.3.3') + )), + array('id' => 14, 'name' => 'Child 2.4') + ); + + $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); + $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); + + $modified = array( + array('id' => 7, 'name' => 'Child 2.2'), + array('id' => 8, 'name' => 'Child 2.3'), + array('id' => 14, 'name' => 'Child 2.4'), + array('name' => 'Child 2.5', 'children' => array( + array('name' => 'Child 2.5.1', 'children' => array( + array('name' => 'Child 2.5.1.1'), + array('name' => 'Child 2.5.1.1') + )), + array('name' => 'Child 2.5.2'), + array('name' => 'Child 2.5.3') + )) + ); + + $this->assertTrue($parent->makeTree($modified)); + $this->assertTrue(Category::isValidNestedSet()); + + $expected = array( + array('id' => 7, 'name' => 'Child 2.2'), + array('id' => 8, 'name' => 'Child 2.3'), + array('id' => 14, 'name' => 'Child 2.4'), + array('id' => 15, 'name' => 'Child 2.5', 'children' => array( + array('id' => 16, 'name' => 'Child 2.5.1', 'children' => array( + array('id' => 17, 'name' => 'Child 2.5.1.1'), + array('id' => 18, 'name' => 'Child 2.5.1.1') + )), + array('id' => 19, 'name' => 'Child 2.5.2'), + array('id' => 20, 'name' => 'Child 2.5.3') + )) + ); + + $hierarchy = $parent->reload()->getDescendants()->toHierarchy()->toArray(); + $this->assertArraysAreEqual($expected, array_ints_keys(hmap($hierarchy, array('id', 'name')))); + } } diff --git a/tests/suite/Category/CategoryTreeRebuildingTest.php b/tests/suite/Category/CategoryTreeRebuildingTest.php index 8e951d58..471c92bd 100644 --- a/tests/suite/Category/CategoryTreeRebuildingTest.php +++ b/tests/suite/Category/CategoryTreeRebuildingTest.php @@ -1,96 +1,106 @@ assertTrue(Category::isValidNestedSet()); + public function testRebuild() + { + $this->assertTrue(Category::isValidNestedSet()); - $root = Category::root(); - Category::query()->update(array('lft' => null, 'rgt' => null)); - $this->assertFalse(Category::isValidNestedSet()); + $root = Category::root(); + Category::query()->update(array('lft' => null, 'rgt' => null)); + $this->assertFalse(Category::isValidNestedSet()); - Category::rebuild(); - $this->assertTrue(Category::isValidNestedSet()); + Category::rebuild(); + $this->assertTrue(Category::isValidNestedSet()); - $this->assertEquals($root, Category::root()); - } + $this->assertEquals($root, Category::root()); + } - public function testRebuildPresevesRootNodes() { - $root1 = Category::create(array('name' => 'Test Root 1')); - $root2 = Category::create(array('name' => 'Test Root 2')); - $root3 = Category::create(array('name' => 'Test Root 3')); + public function testRebuildPresevesRootNodes() + { + $root1 = Category::create(array('name' => 'Test Root 1')); + $root2 = Category::create(array('name' => 'Test Root 2')); + $root3 = Category::create(array('name' => 'Test Root 3')); - $root2->makeChildOf($root1); - $root3->makeChildOf($root1); + $root2->makeChildOf($root1); + $root3->makeChildOf($root1); - $lastRoot = Category::roots()->reOrderBy($root1->getLeftColumnName(), 'desc')->first(); + $lastRoot = Category::roots()->reOrderBy($root1->getLeftColumnName(), 'desc')->first(); - Category::query()->update(array('lft' => null, 'rgt' => null)); - Category::rebuild(); + Category::query()->update(array('lft' => null, 'rgt' => null)); + Category::rebuild(); - $this->assertEquals($lastRoot, Category::roots()->reOrderBy($root1->getLeftColumnName(), 'desc')->first()); - } + $this->assertEquals($lastRoot, Category::roots()->reOrderBy($root1->getLeftColumnName(), 'desc')->first()); + } - public function testRebuildRecomputesDepth() { - $this->assertTrue(Category::isValidNestedSet()); + public function testRebuildRecomputesDepth() + { + $this->assertTrue(Category::isValidNestedSet()); - Category::query()->update(array('lft' => null, 'rgt' => null, 'depth' => 0)); - $this->assertFalse(Category::isValidNestedSet()); + Category::query()->update(array('lft' => null, 'rgt' => null, 'depth' => 0)); + $this->assertFalse(Category::isValidNestedSet()); - Category::rebuild(); + Category::rebuild(); - $expected = array(0, 1, 1, 2, 1, 0); - $this->assertEquals($expected, Category::all()->map(function($n) { return $n->getDepth(); })->all()); - } + $expected = array(0, 1, 1, 2, 1, 0); + $this->assertEquals($expected, Category::all()->map(function ($n) { + return $n->getDepth(); + })->all()); + } - public function testRebuildWithScope() { - MultiScopedCategory::query()->delete(); + public function testRebuildWithScope() + { + MultiScopedCategory::query()->delete(); - $root = MultiScopedCategory::create(array('name' => 'A' , 'company_id' => 721, 'language' => 'es')); - $child1 = MultiScopedCategory::create(array('name' => 'A.1' , 'company_id' => 721, 'language' => 'es')); - $child2 = MultiscopedCategory::create(array('name' => 'A.2' , 'company_id' => 721, 'language' => 'es')); + $root = MultiScopedCategory::create(array('name' => 'A', 'company_id' => 721, 'language' => 'es')); + $child1 = MultiScopedCategory::create(array('name' => 'A.1', 'company_id' => 721, 'language' => 'es')); + $child2 = MultiscopedCategory::create(array('name' => 'A.2', 'company_id' => 721, 'language' => 'es')); - $child1->makeChildOf($root); - $child2->makeChildOf($root); + $child1->makeChildOf($root); + $child2->makeChildOf($root); - MultiscopedCategory::query()->update(array('lft' => null, 'rgt' => null)); - $this->assertFalse(MultiscopedCategory::isValidNestedSet()); + MultiscopedCategory::query()->update(array('lft' => null, 'rgt' => null)); + $this->assertFalse(MultiscopedCategory::isValidNestedSet()); - MultiscopedCategory::rebuild(); - $this->assertTrue(MultiscopedCategory::isValidNestedSet()); + MultiscopedCategory::rebuild(); + $this->assertTrue(MultiscopedCategory::isValidNestedSet()); - $this->assertEquals($root->getAttributes(), $this->categories('A', 'MultiScopedCategory')->getAttributes()); + $this->assertEquals($root->getAttributes(), $this->categories('A', 'MultiScopedCategory')->getAttributes()); - $expected = array($child1->getAttributes(), $child2->getAttributes()); - $actual = array_map(function ($ch) { return $ch->getAttributes(); }, $this->categories('A', 'MultiScopedCategory')->children()->get()->all()); + $expected = array($child1->getAttributes(), $child2->getAttributes()); + $actual = array_map(function ($ch) { + return $ch->getAttributes(); + }, $this->categories('A', 'MultiScopedCategory')->children()->get()->all()); - $this->assertEquals($expected, $actual); - } + $this->assertEquals($expected, $actual); + } - public function testRebuildWithMultipleScopes() { - MultiScopedCategory::query()->delete(); + public function testRebuildWithMultipleScopes() + { + MultiScopedCategory::query()->delete(); - $root1 = MultiScopedCategory::create(array('name' => 'TL1', 'company_id' => 1, 'language' => 'en')); - $child11 = MultiScopedCategory::create(array('name' => 'C11', 'company_id' => 1, 'language' => 'en')); - $child12 = MultiScopedCategory::create(array('name' => 'C12', 'company_id' => 1, 'language' => 'en')); - $child11->makeChildOf($root1); - $child12->makeChildOf($root1); + $root1 = MultiScopedCategory::create(array('name' => 'TL1', 'company_id' => 1, 'language' => 'en')); + $child11 = MultiScopedCategory::create(array('name' => 'C11', 'company_id' => 1, 'language' => 'en')); + $child12 = MultiScopedCategory::create(array('name' => 'C12', 'company_id' => 1, 'language' => 'en')); + $child11->makeChildOf($root1); + $child12->makeChildOf($root1); - $root2 = MultiScopedCategory::create(array('name' => 'TL2', 'company_id' => 2, 'language' => 'en')); - $child21 = MultiScopedCategory::create(array('name' => 'C21', 'company_id' => 2, 'language' => 'en')); - $child22 = MultiScopedCategory::create(array('name' => 'C22', 'company_id' => 2, 'language' => 'en')); - $child21->makeChildOf($root2); - $child22->makeChildOf($root2); + $root2 = MultiScopedCategory::create(array('name' => 'TL2', 'company_id' => 2, 'language' => 'en')); + $child21 = MultiScopedCategory::create(array('name' => 'C21', 'company_id' => 2, 'language' => 'en')); + $child22 = MultiScopedCategory::create(array('name' => 'C22', 'company_id' => 2, 'language' => 'en')); + $child21->makeChildOf($root2); + $child22->makeChildOf($root2); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - $tree = MultiScopedCategory::query()->orderBy($root1->getKeyName())->get()->all(); + $tree = MultiScopedCategory::query()->orderBy($root1->getKeyName())->get()->all(); - MultiScopedCategory::query()->update(array('lft' => null, 'rgt' => null)); - MultiScopedCategory::rebuild(); + MultiScopedCategory::query()->update(array('lft' => null, 'rgt' => null)); + MultiScopedCategory::rebuild(); - $this->assertTrue(MultiScopedCategory::isValidNestedSet()); - $this->assertEquals($tree, MultiScopedCategory::query()->orderBy($root1->getKeyName())->get()->all()); - } + $this->assertTrue(MultiScopedCategory::isValidNestedSet()); + $this->assertEquals($tree, MultiScopedCategory::query()->orderBy($root1->getKeyName())->get()->all()); + } } diff --git a/tests/suite/Category/CategoryTreeValidationTest.php b/tests/suite/Category/CategoryTreeValidationTest.php index c00992e9..035c312f 100644 --- a/tests/suite/Category/CategoryTreeValidationTest.php +++ b/tests/suite/Category/CategoryTreeValidationTest.php @@ -1,84 +1,94 @@ assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWithNullLefts() + { + $this->assertTrue(Category::isValidNestedSet()); - Category::query()->update(array('lft' => null)); - $this->assertFalse(Category::isValidNestedSet()); - } + Category::query()->update(array('lft' => null)); + $this->assertFalse(Category::isValidNestedSet()); + } - public function testTreeIsNotValidWithNullRights() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWithNullRights() + { + $this->assertTrue(Category::isValidNestedSet()); - Category::query()->update(array('rgt' => null)); - $this->assertFalse(Category::isValidNestedSet()); - } + Category::query()->update(array('rgt' => null)); + $this->assertFalse(Category::isValidNestedSet()); + } - public function testTreeIsNotValidWhenRightsEqualLefts() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWhenRightsEqualLefts() + { + $this->assertTrue(Category::isValidNestedSet()); - $child2 = $this->categories('Child 2'); - $child2->rgt = $child2->lft; - $child2->save(); + $child2 = $this->categories('Child 2'); + $child2->rgt = $child2->lft; + $child2->save(); - $this->assertFalse(Category::isValidNestedSet()); - } + $this->assertFalse(Category::isValidNestedSet()); + } - public function testTreeIsNotValidWhenLeftEqualsParent() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWhenLeftEqualsParent() + { + $this->assertTrue(Category::isValidNestedSet()); - $child2 = $this->categories('Child 2'); - $child2->lft = $this->categories('Root 1')->getLeft(); - $child2->save(); + $child2 = $this->categories('Child 2'); + $child2->lft = $this->categories('Root 1')->getLeft(); + $child2->save(); - $this->assertFalse(Category::isValidNestedSet()); - } + $this->assertFalse(Category::isValidNestedSet()); + } - public function testTreeIsNotValidWhenRightEqualsParent() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWhenRightEqualsParent() + { + $this->assertTrue(Category::isValidNestedSet()); - $child2 = $this->categories('Child 2'); - $child2->rgt = $this->categories('Root 1')->getRight(); - $child2->save(); + $child2 = $this->categories('Child 2'); + $child2->rgt = $this->categories('Root 1')->getRight(); + $child2->save(); - $this->assertFalse(Category::isValidNestedSet()); - } + $this->assertFalse(Category::isValidNestedSet()); + } - public function testTreeIsValidWithMissingMiddleNode() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsValidWithMissingMiddleNode() + { + $this->assertTrue(Category::isValidNestedSet()); - Category::query()->delete($this->categories('Child 2')->getKey()); - $this->assertTrue(Category::isValidNestedSet()); - } + Category::query()->delete($this->categories('Child 2')->getKey()); + $this->assertTrue(Category::isValidNestedSet()); + } - public function testTreeIsNotValidWithOverlappingRoots() { - $this->assertTrue(Category::isValidNestedSet()); + public function testTreeIsNotValidWithOverlappingRoots() + { + $this->assertTrue(Category::isValidNestedSet()); - // Force Root 2 to overlap with Root 1 - $root = $this->categories('Root 2'); - $root->lft = 0; - $root->save(); + // Force Root 2 to overlap with Root 1 + $root = $this->categories('Root 2'); + $root->lft = 0; + $root->save(); - $this->assertFalse(Category::isValidNestedSet()); - } + $this->assertFalse(Category::isValidNestedSet()); + } - public function testNodeDeletionDoesNotMakeTreeInvalid() { - $this->assertTrue(Category::isValidNestedSet()); + public function testNodeDeletionDoesNotMakeTreeInvalid() + { + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Root 2')->delete(); - $this->assertTrue(Category::isValidNestedSet()); + $this->categories('Root 2')->delete(); + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Child 1')->delete(); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->categories('Child 1')->delete(); + $this->assertTrue(Category::isValidNestedSet()); + } - public function testNodeDeletionWithSubtreeDoesNotMakeTreeInvalid() { - $this->assertTrue(Category::isValidNestedSet()); + public function testNodeDeletionWithSubtreeDoesNotMakeTreeInvalid() + { + $this->assertTrue(Category::isValidNestedSet()); - $this->categories('Child 2')->delete(); - $this->assertTrue(Category::isValidNestedSet()); - } + $this->categories('Child 2')->delete(); + $this->assertTrue(Category::isValidNestedSet()); + } } diff --git a/tests/suite/CategoryTestCase.php b/tests/suite/CategoryTestCase.php index b7998406..1424190e 100644 --- a/tests/suite/CategoryTestCase.php +++ b/tests/suite/CategoryTestCase.php @@ -1,17 +1,21 @@ up(); - } + public static function setUpBeforeClass(): void + { + with(new CategoryMigrator)->up(); + } - public function setUp() { - with(new CategorySeeder)->run(); - } + public function setUp(): void + { + with(new CategorySeeder)->run(); + } - protected function categories($name, $className = 'Category') { - return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); - } + protected function categories($name, $className = 'Category') + { + return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); + } } diff --git a/tests/suite/Cluster/ClusterColumnsTest.php b/tests/suite/Cluster/ClusterColumnsTest.php index 72279f3e..b1b0d417 100644 --- a/tests/suite/Cluster/ClusterColumnsTest.php +++ b/tests/suite/Cluster/ClusterColumnsTest.php @@ -1,19 +1,22 @@ assertTrue(is_string($root->getKey())); - $this->assertFalse(is_numeric($root->getKey())); - } + $this->assertTrue(is_string($root->getKey())); + $this->assertFalse(is_numeric($root->getKey())); + } - public function testParentKeyIsNonNumeric() { - $child1 = $this->clusters('Child 1'); + public function testParentKeyIsNonNumeric() + { + $child1 = $this->clusters('Child 1'); - $this->assertTrue(is_string($child1->getParentId())); - $this->assertFalse(is_numeric($child1->getParentId())); - } + $this->assertTrue(is_string($child1->getParentId())); + $this->assertFalse(is_numeric($child1->getParentId())); + } } diff --git a/tests/suite/Cluster/ClusterHierarchyTest.php b/tests/suite/Cluster/ClusterHierarchyTest.php index c660ee73..88670156 100644 --- a/tests/suite/Cluster/ClusterHierarchyTest.php +++ b/tests/suite/Cluster/ClusterHierarchyTest.php @@ -1,708 +1,764 @@ orderBy('lft')->get(); + public function testAllStatic() + { + $results = Cluster::all(); + $expected = Cluster::query()->orderBy('lft')->get(); - $this->assertEquals($results, $expected); - } + $this->assertEquals($results, $expected); + } - public function testAllStaticWithCustomOrder() { - $results = OrderedCluster::all(); - $expected = OrderedCluster::query()->orderBy('name')->get(); + public function testAllStaticWithCustomOrder() + { + $results = OrderedCluster::all(); + $expected = OrderedCluster::query()->orderBy('name')->get(); - $this->assertEquals($results, $expected); - } + $this->assertEquals($results, $expected); + } - public function testRootsStatic() { - $query = Cluster::whereNull('parent_id')->get(); + public function testRootsStatic() + { + $query = Cluster::whereNull('parent_id')->get(); - $roots = Cluster::roots()->get(); + $roots = Cluster::roots()->get(); - $this->assertEquals($query->count(), $roots->count()); - $this->assertCount(2, $roots); + $this->assertEquals($query->count(), $roots->count()); + $this->assertCount(2, $roots); - foreach ($query->pluck('id') as $node) - $this->assertContains($node, $roots->pluck('id')); - } + foreach ($query->pluck('id') as $node) + $this->assertContains($node, $roots->pluck('id')); + } - public function testRootsStaticWithCustomOrder() { - $cluster = OrderedCluster::create(array('name' => 'A new root is born')); - $cluster->syncOriginal(); // ¿? --> This should be done already !? + public function testRootsStaticWithCustomOrder() + { + $cluster = OrderedCluster::create(array('name' => 'A new root is born')); + $cluster->syncOriginal(); // ¿? --> This should be done already !? - $roots = OrderedCluster::roots()->get(); + $roots = OrderedCluster::roots()->get(); - $this->assertCount(3, $roots); - $this->assertEquals($cluster->getAttributes(), $roots->first()->getAttributes()); - } + $this->assertCount(3, $roots); + $this->assertEquals($cluster->getAttributes(), $roots->first()->getAttributes()); + } - public function testRootStatic() { - $this->assertEquals(Cluster::root(), $this->clusters('Root 1')); - } + public function testRootStatic() + { + $this->assertEquals(Cluster::root(), $this->clusters('Root 1')); + } - public function testAllLeavesStatic() { - $allLeaves = Cluster::allLeaves()->get(); + public function testAllLeavesStatic() + { + $allLeaves = Cluster::allLeaves()->get(); - $this->assertCount(4, $allLeaves); + $this->assertCount(4, $allLeaves); - $leaves = $allLeaves->pluck('name'); + $leaves = $allLeaves->pluck('name'); - $this->assertContains('Child 1' , $leaves); - $this->assertContains('Child 2.1' , $leaves); - $this->assertContains('Child 3' , $leaves); - $this->assertContains('Root 2' , $leaves); - } + $this->assertContains('Child 1', $leaves); + $this->assertContains('Child 2.1', $leaves); + $this->assertContains('Child 3', $leaves); + $this->assertContains('Root 2', $leaves); + } - public function testAllTrunksStatic() { - $allTrunks = Cluster::allTrunks()->get(); + public function testAllTrunksStatic() + { + $allTrunks = Cluster::allTrunks()->get(); - $this->assertCount(1, $allTrunks); + $this->assertCount(1, $allTrunks); - $trunks = $allTrunks->pluck('name'); - $this->assertContains('Child 2', $trunks); - } + $trunks = $allTrunks->pluck('name'); + $this->assertContains('Child 2', $trunks); + } - public function testGetRoot() { - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 1')->getRoot()); - $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 2')->getRoot()); + public function testGetRoot() + { + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 1')->getRoot()); + $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 2')->getRoot()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 1')->getRoot()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 2')->getRoot()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 2.1')->getRoot()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 3')->getRoot()); - } + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 1')->getRoot()); + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 2')->getRoot()); + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 2.1')->getRoot()); + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Child 3')->getRoot()); + } - public function testGetRootEqualsSelfIfUnpersisted() { - $cluster = new Cluster; + public function testGetRootEqualsSelfIfUnpersisted() + { + $cluster = new Cluster; - $this->assertEquals($cluster->getRoot(), $cluster); - } + $this->assertEquals($cluster->getRoot(), $cluster); + } - public function testGetRootEqualsValueIfSetIfUnpersisted() { - $parent = Cluster::roots()->first(); + public function testGetRootEqualsValueIfSetIfUnpersisted() + { + $parent = Cluster::roots()->first(); - $child = new Cluster; - $child->setAttribute($child->getParentColumnName(), $parent->getKey()); + $child = new Cluster; + $child->setAttribute($child->getParentColumnName(), $parent->getKey()); - $this->assertEquals($child->getRoot(), $parent); - } + $this->assertEquals($child->getRoot(), $parent); + } - public function testIsRoot() { - $this->assertTrue($this->clusters('Root 1')->isRoot()); - $this->assertTrue($this->clusters('Root 2')->isRoot()); + public function testIsRoot() + { + $this->assertTrue($this->clusters('Root 1')->isRoot()); + $this->assertTrue($this->clusters('Root 2')->isRoot()); - $this->assertFalse($this->clusters('Child 1')->isRoot()); - $this->assertFalse($this->clusters('Child 2')->isRoot()); - $this->assertFalse($this->clusters('Child 2.1')->isRoot()); - $this->assertFalse($this->clusters('Child 3')->isRoot()); - } + $this->assertFalse($this->clusters('Child 1')->isRoot()); + $this->assertFalse($this->clusters('Child 2')->isRoot()); + $this->assertFalse($this->clusters('Child 2.1')->isRoot()); + $this->assertFalse($this->clusters('Child 3')->isRoot()); + } - public function testGetLeaves() { - $leaves = array($this->clusters('Child 1'), $this->clusters('Child 2.1'), $this->clusters('Child 3')); + public function testGetLeaves() + { + $leaves = array($this->clusters('Child 1'), $this->clusters('Child 2.1'), $this->clusters('Child 3')); - $this->assertEquals($leaves, $this->clusters('Root 1')->getLeaves()->all()); - } + $this->assertEquals($leaves, $this->clusters('Root 1')->getLeaves()->all()); + } - public function testGetLeavesInIteration() { - $node = $this->clusters('Root 1'); + public function testGetLeavesInIteration() + { + $node = $this->clusters('Root 1'); - $expectedIds = array( - '5d7ce1fd-6151-46d3-a5b3-0ebb9988dc57', - '3315a297-af87-4ad3-9fa5-19785407573d', - '054476d2-6830-4014-a181-4de010ef7114' - ); + $expectedIds = array( + '5d7ce1fd-6151-46d3-a5b3-0ebb9988dc57', + '3315a297-af87-4ad3-9fa5-19785407573d', + '054476d2-6830-4014-a181-4de010ef7114' + ); - foreach($node->getLeaves() as $i => $leaf) - $this->assertEquals($expectedIds[$i], $leaf->getKey()); - } + foreach ($node->getLeaves() as $i => $leaf) + $this->assertEquals($expectedIds[$i], $leaf->getKey()); + } - public function testGetTrunks() { - $trunks = array($this->clusters('Child 2')); + public function testGetTrunks() + { + $trunks = array($this->clusters('Child 2')); - $this->assertEquals($trunks, $this->clusters('Root 1')->getTrunks()->all()); - } + $this->assertEquals($trunks, $this->clusters('Root 1')->getTrunks()->all()); + } - public function testGetTrunksInIteration() { - $node = $this->clusters('Root 1'); + public function testGetTrunksInIteration() + { + $node = $this->clusters('Root 1'); - $expectedIds = array('07c1fc8c-53b5-4fe7-b9c4-e09f266a455c'); + $expectedIds = array('07c1fc8c-53b5-4fe7-b9c4-e09f266a455c'); - foreach($node->getTrunks() as $i => $trunk) - $this->assertEquals($expectedIds[$i], $trunk->getKey()); - } + foreach ($node->getTrunks() as $i => $trunk) + $this->assertEquals($expectedIds[$i], $trunk->getKey()); + } - public function testIsLeaf() { - $this->assertTrue($this->clusters('Child 1')->isLeaf()); - $this->assertTrue($this->clusters('Child 2.1')->isLeaf()); - $this->assertTrue($this->clusters('Child 3')->isLeaf()); - $this->assertTrue($this->clusters('Root 2')->isLeaf()); + public function testIsLeaf() + { + $this->assertTrue($this->clusters('Child 1')->isLeaf()); + $this->assertTrue($this->clusters('Child 2.1')->isLeaf()); + $this->assertTrue($this->clusters('Child 3')->isLeaf()); + $this->assertTrue($this->clusters('Root 2')->isLeaf()); - $this->assertFalse($this->clusters('Root 1')->isLeaf()); - $this->assertFalse($this->clusters('Child 2')->isLeaf()); + $this->assertFalse($this->clusters('Root 1')->isLeaf()); + $this->assertFalse($this->clusters('Child 2')->isLeaf()); - $new = new Cluster; - $this->assertFalse($new->isLeaf()); - } + $new = new Cluster; + $this->assertFalse($new->isLeaf()); + } - public function testIsTrunk() { - $this->assertFalse($this->clusters('Child 1')->isTrunk()); - $this->assertFalse($this->clusters('Child 2.1')->isTrunk()); - $this->assertFalse($this->clusters('Child 3')->isTrunk()); - $this->assertFalse($this->clusters('Root 2')->isTrunk()); + public function testIsTrunk() + { + $this->assertFalse($this->clusters('Child 1')->isTrunk()); + $this->assertFalse($this->clusters('Child 2.1')->isTrunk()); + $this->assertFalse($this->clusters('Child 3')->isTrunk()); + $this->assertFalse($this->clusters('Root 2')->isTrunk()); - $this->assertFalse($this->clusters('Root 1')->isTrunk()); - $this->assertTrue($this->clusters('Child 2')->isTrunk()); + $this->assertFalse($this->clusters('Root 1')->isTrunk()); + $this->assertTrue($this->clusters('Child 2')->isTrunk()); - $new = new Cluster; - $this->assertFalse($new->isTrunk()); - } + $new = new Cluster; + $this->assertFalse($new->isTrunk()); + } - public function testWithoutNodeScope() { - $child = $this->clusters('Child 2.1'); + public function testWithoutNodeScope() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Root 1'), $child); + $expected = array($this->clusters('Root 1'), $child); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutNode($this->clusters('Child 2'))->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutNode($this->clusters('Child 2'))->get()->all()); + } - public function testWithoutSelfScope() { - $child = $this->clusters('Child 2.1'); + public function testWithoutSelfScope() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Root 1'), $this->clusters('Child 2')); + $expected = array($this->clusters('Root 1'), $this->clusters('Child 2')); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutSelf()->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutSelf()->get()->all()); + } - public function testWithoutRootScope() { - $child = $this->clusters('Child 2.1'); + public function testWithoutRootScope() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Child 2'), $child); + $expected = array($this->clusters('Child 2'), $child); - $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutRoot()->get()->all()); - } + $this->assertEquals($expected, $child->ancestorsAndSelf()->withoutRoot()->get()->all()); + } - public function testLimitDepthScope() { - with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 10); + public function testLimitDepthScope() + { + with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 10); - $node = $this->clusters('Child 2'); + $node = $this->clusters('Child 2'); - $descendancy = $node->descendants()->pluck('id')->all(); + $descendancy = $node->descendants()->pluck('id')->all(); - $this->assertEmpty($node->descendants()->limitDepth(0)->pluck('id')->all()); - $this->assertEquals($node->getAttributes(), $node->descendantsAndSelf()->limitDepth(0)->first()->getAttributes()); + $this->assertEmpty($node->descendants()->limitDepth(0)->pluck('id')->all()); + $this->assertEquals($node->getAttributes(), $node->descendantsAndSelf()->limitDepth(0)->first()->getAttributes()); - $this->assertEquals(array_slice($descendancy, 0, 3), $node->descendants()->limitDepth(3)->pluck('id')->all()); - $this->assertEquals(array_slice($descendancy, 0, 5), $node->descendants()->limitDepth(5)->pluck('id')->all()); - $this->assertEquals(array_slice($descendancy, 0, 7), $node->descendants()->limitDepth(7)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 3), $node->descendants()->limitDepth(3)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 5), $node->descendants()->limitDepth(5)->pluck('id')->all()); + $this->assertEquals(array_slice($descendancy, 0, 7), $node->descendants()->limitDepth(7)->pluck('id')->all()); - $this->assertEquals($descendancy, $node->descendants()->limitDepth(1000)->pluck('id')->all()); - } + $this->assertEquals($descendancy, $node->descendants()->limitDepth(1000)->pluck('id')->all()); + } - public function testGetAncestorsAndSelf() { - $child = $this->clusters('Child 2.1'); + public function testGetAncestorsAndSelf() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Root 1'), $this->clusters('Child 2'), $child); + $expected = array($this->clusters('Root 1'), $this->clusters('Child 2'), $child); - $this->assertEquals($expected, $child->getAncestorsAndSelf()->all()); - } + $this->assertEquals($expected, $child->getAncestorsAndSelf()->all()); + } - public function testGetAncestorsAndSelfWithoutRoot() { - $child = $this->clusters('Child 2.1'); + public function testGetAncestorsAndSelfWithoutRoot() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Child 2'), $child); + $expected = array($this->clusters('Child 2'), $child); - $this->assertEquals($expected, $child->getAncestorsAndSelfWithoutRoot()->all()); - } + $this->assertEquals($expected, $child->getAncestorsAndSelfWithoutRoot()->all()); + } - public function testGetAncestors() { - $child = $this->clusters('Child 2.1'); + public function testGetAncestors() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Root 1'), $this->clusters('Child 2')); + $expected = array($this->clusters('Root 1'), $this->clusters('Child 2')); - $this->assertEquals($expected, $child->getAncestors()->all()); - } + $this->assertEquals($expected, $child->getAncestors()->all()); + } - public function testGetAncestorsWithoutRoot() { - $child = $this->clusters('Child 2.1'); + public function testGetAncestorsWithoutRoot() + { + $child = $this->clusters('Child 2.1'); - $expected = array($this->clusters('Child 2')); + $expected = array($this->clusters('Child 2')); - $this->assertEquals($expected, $child->getAncestorsWithoutRoot()->all()); - } + $this->assertEquals($expected, $child->getAncestorsWithoutRoot()->all()); + } - public function testGetDescendantsAndSelf() { - $parent = $this->clusters('Root 1'); + public function testGetDescendantsAndSelf() + { + $parent = $this->clusters('Root 1'); - $expected = array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 3') - ); + $expected = array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 3') + ); - $this->assertCount(count($expected), $parent->getDescendantsAndSelf()); + $this->assertCount(count($expected), $parent->getDescendantsAndSelf()); - $this->assertEquals($expected, $parent->getDescendantsAndSelf()->all()); - } + $this->assertEquals($expected, $parent->getDescendantsAndSelf()->all()); + } - public function testGetDescendantsAndSelfWithLimit() { - with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 3); - - $parent = $this->clusters('Root 1'); - - $this->assertEquals(array($parent), $parent->getDescendantsAndSelf(0)->all()); - - $this->assertEquals(array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 3') - ), $parent->getDescendantsAndSelf(1)->all()); - - $this->assertEquals(array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 3') - ), $parent->getDescendantsAndSelf(2)->all()); - - $this->assertEquals(array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendantsAndSelf(3)->all()); - - $this->assertEquals(array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 2.1.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendantsAndSelf(4)->all()); - - $this->assertEquals(array( - $parent, - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 2.1.1.1'), - $this->clusters('Child 2.1.1.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendantsAndSelf(10)->all()); - } - - public function testGetDescendants() { - $parent = $this->clusters('Root 1'); - - $expected = array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 3') - ); - - $this->assertCount(count($expected), $parent->getDescendants()); - - $this->assertEquals($expected, $parent->getDescendants()->all()); - } - - public function testGetDescendantsWithLimit() { - with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 3); - - $parent = $this->clusters('Root 1'); - - $this->assertEmpty($parent->getDescendants(0)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 3') - ), $parent->getDescendants(1)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 3') - ), $parent->getDescendants(2)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendants(3)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 2.1.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendants(4)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 2.1.1.1'), - $this->clusters('Child 2.1.1.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendants(5)->all()); - - $this->assertEquals(array( - $this->clusters('Child 1'), - $this->clusters('Child 2'), - $this->clusters('Child 2.1'), - $this->clusters('Child 2.1.1'), - $this->clusters('Child 2.1.1.1'), - $this->clusters('Child 2.1.1.1.1'), - $this->clusters('Child 3') - ), $parent->getDescendants(10)->all()); - } - - public function testDescendantsRecursesChildren() { - $a = Cluster::create(array('name' => 'A')); - $b = Cluster::create(array('name' => 'B')); - $c = Cluster::create(array('name' => 'C')); - - // a > b > c - $b->makeChildOf($a); - $c->makeChildOf($b); - - $a->reload(); $b->reload(); $c->reload(); - - $this->assertEquals(1, $a->children()->count()); - $this->assertEquals(1, $b->children()->count()); - $this->assertEquals(2, $a->descendants()->count()); - } - - public function testGetImmediateDescendants() { - $expected = array($this->clusters('Child 1'), $this->clusters('Child 2'), $this->clusters('Child 3')); - - $this->assertEquals($expected, $this->clusters('Root 1')->getImmediateDescendants()->all()); - - $this->assertEquals(array($this->clusters('Child 2.1')), $this->clusters('Child 2')->getImmediateDescendants()->all()); - - $this->assertEmpty($this->clusters('Root 2')->getImmediateDescendants()->all()); - } - - public function testIsSelfOrAncestorOf() { - $this->assertTrue($this->clusters('Root 1')->isSelfOrAncestorOf($this->clusters('Child 1'))); - $this->assertTrue($this->clusters('Root 1')->isSelfOrAncestorOf($this->clusters('Child 2.1'))); - $this->assertTrue($this->clusters('Child 2')->isSelfOrAncestorOf($this->clusters('Child 2.1'))); - $this->assertFalse($this->clusters('Child 2.1')->isSelfOrAncestorOf($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 1')->isSelfOrAncestorOf($this->clusters('Child 2'))); - $this->assertTrue($this->clusters('Child 1')->isSelfOrAncestorOf($this->clusters('Child 1'))); - } - - public function testIsAncestorOf() { - $this->assertTrue($this->clusters('Root 1')->isAncestorOf($this->clusters('Child 1'))); - $this->assertTrue($this->clusters('Root 1')->isAncestorOf($this->clusters('Child 2.1'))); - $this->assertTrue($this->clusters('Child 2')->isAncestorOf($this->clusters('Child 2.1'))); - $this->assertFalse($this->clusters('Child 2.1')->isAncestorOf($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 1')->isAncestorOf($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 1')->isAncestorOf($this->clusters('Child 1'))); - } - - public function testIsSelfOrDescendantOf() { - $this->assertTrue($this->clusters('Child 1')->isSelfOrDescendantOf($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2.1')->isSelfOrDescendantOf($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2.1')->isSelfOrDescendantOf($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 2')->isSelfOrDescendantOf($this->clusters('Child 2.1'))); - $this->assertFalse($this->clusters('Child 2')->isSelfOrDescendantOf($this->clusters('Child 1'))); - $this->assertTrue($this->clusters('Child 1')->isSelfOrDescendantOf($this->clusters('Child 1'))); - } - - public function testIsDescendantOf() { - $this->assertTrue($this->clusters('Child 1')->isDescendantOf($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2.1')->isDescendantOf($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2.1')->isDescendantOf($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 2')->isDescendantOf($this->clusters('Child 2.1'))); - $this->assertFalse($this->clusters('Child 2')->isDescendantOf($this->clusters('Child 1'))); - $this->assertFalse($this->clusters('Child 1')->isDescendantOf($this->clusters('Child 1'))); - } - - public function testGetSiblingsAndSelf() { - $child = $this->clusters('Child 2'); - - $expected = array($this->clusters('Child 1'), $child, $this->clusters('Child 3')); - $this->assertEquals($expected, $child->getSiblingsAndSelf()->all()); - - $expected = array($this->clusters('Root 1'), $this->clusters('Root 2')); - $this->assertEquals($expected, $this->clusters('Root 1')->getSiblingsAndSelf()->all()); - } - - public function testGetSiblings() { - $child = $this->clusters('Child 2'); - - $expected = array($this->clusters('Child 1'), $this->clusters('Child 3')); - - $this->assertEquals($expected, $child->getSiblings()->all()); - } - - public function testGetLeftSibling() { - $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 2')->getLeftSibling()); - $this->assertEquals($this->clusters('Child 2'), $this->clusters('Child 3')->getLeftSibling()); - } - - public function testGetLeftSiblingOfFirstRootIsNull() { - $this->assertNull($this->clusters('Root 1')->getLeftSibling()); - } - - public function testGetLeftSiblingWithNoneIsNull() { - $this->assertNull($this->clusters('Child 2.1')->getLeftSibling()); - } - - public function testGetLeftSiblingOfLeftmostNodeIsNull() { - $this->assertNull($this->clusters('Child 1')->getLeftSibling()); - } - - public function testGetRightSibling() { - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 2')->getRightSibling()); - $this->assertEquals($this->clusters('Child 2'), $this->clusters('Child 1')->getRightSibling()); - } - - public function testGetRightSiblingOfRoots() { - $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getRightSibling()); - $this->assertNull($this->clusters('Root 2')->getRightSibling()); - } - - public function testGetRightSiblingWithNoneIsNull() { - $this->assertNull($this->clusters('Child 2.1')->getRightSibling()); - } - - public function testGetRightSiblingOfRightmostNodeIsNull() { - $this->assertNull($this->clusters('Child 3')->getRightSibling()); - } - - public function testInsideSubtree() { - $this->assertFalse($this->clusters('Child 1')->insideSubtree($this->clusters('Root 2'))); - $this->assertFalse($this->clusters('Child 2')->insideSubtree($this->clusters('Root 2'))); - $this->assertFalse($this->clusters('Child 3')->insideSubtree($this->clusters('Root 2'))); - - $this->assertTrue($this->clusters('Child 1')->insideSubtree($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2')->insideSubtree($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 2.1')->insideSubtree($this->clusters('Root 1'))); - $this->assertTrue($this->clusters('Child 3')->insideSubtree($this->clusters('Root 1'))); - - $this->assertTrue($this->clusters('Child 2.1')->insideSubtree($this->clusters('Child 2'))); - $this->assertFalse($this->clusters('Child 2.1')->insideSubtree($this->clusters('Root 2'))); - } - - public function testGetLevel() { - $this->assertEquals(0, $this->clusters('Root 1')->getLevel()); - $this->assertEquals(1, $this->clusters('Child 1')->getLevel()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getLevel()); - } - - public function testToHierarchyReturnsAnEloquentCollection() { - $categories = Cluster::all()->toHierarchy(); - - $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $categories); - } - - public function testToHierarchyReturnsHierarchicalData() { - $categories = Cluster::all()->toHierarchy(); - - $this->assertEquals(2, $categories->count()); - - $first = $categories->first(); - $this->assertEquals('Root 1', $first->name); - $this->assertEquals(3, $first->children->count()); - - $first_lvl2 = $first->children->first(); - $this->assertEquals('Child 1', $first_lvl2->name); - $this->assertEquals(0, $first_lvl2->children->count()); - } - - public function testToHierarchyNestsCorrectly() { - // Prune all categories - Cluster::query()->delete(); - - // Build a sample tree structure: - // - // - A - // |- A.1 - // |- A.2 - // - B - // |- B.1 - // |- B.2 - // |- B.2.1 - // |- B.2.2 - // |- B.2.2.1 - // |- B.2.3 - // |- B.3 - // - C - // |- C.1 - // |- C.2 - // - D - // - $a = Cluster::create(array('name' => 'A')); - $b = Cluster::create(array('name' => 'B')); - $c = Cluster::create(array('name' => 'C')); - $d = Cluster::create(array('name' => 'D')); - - $ch = Cluster::create(array('name' => 'A.1')); - $ch->makeChildOf($a); - - $ch = Cluster::create(array('name' => 'A.2')); - $ch->makeChildOf($a); - - $ch = Cluster::create(array('name' => 'B.1')); - $ch->makeChildOf($b); - - $ch = Cluster::create(array('name' => 'B.2')); - $ch->makeChildOf($b); - - $ch2 = Cluster::create(array('name' => 'B.2.1')); - $ch2->makeChildOf($ch); - - $ch2 = Cluster::create(array('name' => 'B.2.2')); - $ch2->makeChildOf($ch); - - $ch3 = Cluster::create(array('name' => 'B.2.2.1')); - $ch3->makeChildOf($ch2); - - $ch2 = Cluster::create(array('name' => 'B.2.3')); - $ch2->makeChildOf($ch); - - $ch = Cluster::create(array('name' => 'B.3')); - $ch->makeChildOf($b); - - $ch = Cluster::create(array('name' => 'C.1')); - $ch->makeChildOf($c); - - $ch = Cluster::create(array('name' => 'C.2')); - $ch->makeChildOf($c); - - $this->assertTrue(Cluster::isValidNestedSet()); - - // Build expectations (expected trees/subtrees) - $expectedWholeTree = array( - 'A' => array ( 'A.1' => null, 'A.2' => null ), - 'B' => array ( - 'B.1' => null, - 'B.2' => - array ( - 'B.2.1' => null, - 'B.2.2' => array ( 'B.2.2.1' => null ), - 'B.2.3' => null, - ), - 'B.3' => null, - ), - 'C' => array ( 'C.1' => null, 'C.2' => null ), - 'D' => null - ); - - $expectedSubtreeA = array('A' => array ( 'A.1' => null, 'A.2' => null )); - - $expectedSubtreeB = array( - 'B' => array ( - 'B.1' => null, - 'B.2' => - array ( - 'B.2.1' => null, - 'B.2.2' => array ( 'B.2.2.1' => null ), - 'B.2.3' => null - ), - 'B.3' => null - ) - ); - - $expectedSubtreeC = array( 'C.1' => null, 'C.2' => null ); - - $expectedSubtreeD = array('D' => null); - - // Perform assertions - $wholeTree = hmap(Cluster::all()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedWholeTree, $wholeTree); - - $subtreeA = hmap($this->clusters('A')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeA, $subtreeA); - - $subtreeB = hmap($this->clusters('B')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeB, $subtreeB); - - $subtreeC = hmap($this->clusters('C')->getDescendants()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeC, $subtreeC); - - $subtreeD = hmap($this->clusters('D')->getDescendantsAndSelf()->toHierarchy()->toArray()); - $this->assertArraysAreEqual($expectedSubtreeD, $subtreeD); - - $this->assertTrue($this->clusters('D')->getDescendants()->toHierarchy()->isEmpty()); - } - - public function testToHierarchyNestsCorrectlyNotSequential() { - $parent = $this->clusters('Child 1'); - - $parent->children()->create(array('name' => 'Child 1.1')); - - $parent->children()->create(array('name' => 'Child 1.2')); - - $this->assertTrue(Cluster::isValidNestedSet()); - - $expected = array( - 'Child 1' => array( - 'Child 1.1' => null, - 'Child 1.2' => null - ) - ); - - $parent->reload(); - $this->assertArraysAreEqual($expected, hmap($parent->getDescendantsAndSelf()->toHierarchy()->toArray())); - } - - public function testToHierarchyNestsCorrectlyWithOrder() { - with(new OrderedClusterSeeder)->run(); - - $expectedWhole = array( - 'Root A' => null, - 'Root Z' => array( - 'Child A' => null, - 'Child C' => null, - 'Child G' => array( 'Child G.1' => null ) - ) - ); - $this->assertArraysAreEqual($expectedWhole, hmap(OrderedCluster::all()->toHierarchy()->toArray())); - - $expectedSubtreeZ = array( - 'Root Z' => array( - 'Child A' => null, - 'Child C' => null, - 'Child G' => array( 'Child G.1' => null ) - ) - ); - $this->assertArraysAreEqual($expectedSubtreeZ, hmap($this->clusters('Root Z', 'OrderedCluster')->getDescendantsAndSelf()->toHierarchy()->toArray())); - } - - public function testGetNestedList() { - $seperator = ' '; - $nestedList = Cluster::getNestedList('name', 'id', $seperator); - - $expected = array( - '7461d8f5-2ea9-4788-99c4-9d0244f0bfb1' => str_repeat($seperator, 0). 'Root 1', - '5d7ce1fd-6151-46d3-a5b3-0ebb9988dc57' => str_repeat($seperator, 1). 'Child 1', - '07c1fc8c-53b5-4fe7-b9c4-e09f266a455c' => str_repeat($seperator, 1). 'Child 2', - '3315a297-af87-4ad3-9fa5-19785407573d' => str_repeat($seperator, 2). 'Child 2.1', - '054476d2-6830-4014-a181-4de010ef7114' => str_repeat($seperator, 1). 'Child 3', - '3bb62314-9e1e-49c6-a5cb-17a9ab9b1b9a' => str_repeat($seperator, 0). 'Root 2', - ); - - $this->assertArraysAreEqual($expected, $nestedList); - } + public function testGetDescendantsAndSelfWithLimit() + { + with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 3); + + $parent = $this->clusters('Root 1'); + + $this->assertEquals(array($parent), $parent->getDescendantsAndSelf(0)->all()); + + $this->assertEquals(array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 3') + ), $parent->getDescendantsAndSelf(1)->all()); + + $this->assertEquals(array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 3') + ), $parent->getDescendantsAndSelf(2)->all()); + + $this->assertEquals(array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendantsAndSelf(3)->all()); + + $this->assertEquals(array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 2.1.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendantsAndSelf(4)->all()); + + $this->assertEquals(array( + $parent, + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 2.1.1.1'), + $this->clusters('Child 2.1.1.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendantsAndSelf(10)->all()); + } + + public function testGetDescendants() + { + $parent = $this->clusters('Root 1'); + + $expected = array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 3') + ); + + $this->assertCount(count($expected), $parent->getDescendants()); + + $this->assertEquals($expected, $parent->getDescendants()->all()); + } + + public function testGetDescendantsWithLimit() + { + with(new ClusterSeeder)->nestUptoAt($this->clusters('Child 2.1'), 3); + + $parent = $this->clusters('Root 1'); + + $this->assertEmpty($parent->getDescendants(0)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 3') + ), $parent->getDescendants(1)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 3') + ), $parent->getDescendants(2)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendants(3)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 2.1.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendants(4)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 2.1.1.1'), + $this->clusters('Child 2.1.1.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendants(5)->all()); + + $this->assertEquals(array( + $this->clusters('Child 1'), + $this->clusters('Child 2'), + $this->clusters('Child 2.1'), + $this->clusters('Child 2.1.1'), + $this->clusters('Child 2.1.1.1'), + $this->clusters('Child 2.1.1.1.1'), + $this->clusters('Child 3') + ), $parent->getDescendants(10)->all()); + } + + public function testDescendantsRecursesChildren() + { + $a = Cluster::create(array('name' => 'A')); + $b = Cluster::create(array('name' => 'B')); + $c = Cluster::create(array('name' => 'C')); + + // a > b > c + $b->makeChildOf($a); + $c->makeChildOf($b); + + $a->reload(); + $b->reload(); + $c->reload(); + + $this->assertEquals(1, $a->children()->count()); + $this->assertEquals(1, $b->children()->count()); + $this->assertEquals(2, $a->descendants()->count()); + } + + public function testGetImmediateDescendants() + { + $expected = array($this->clusters('Child 1'), $this->clusters('Child 2'), $this->clusters('Child 3')); + + $this->assertEquals($expected, $this->clusters('Root 1')->getImmediateDescendants()->all()); + + $this->assertEquals(array($this->clusters('Child 2.1')), $this->clusters('Child 2')->getImmediateDescendants()->all()); + + $this->assertEmpty($this->clusters('Root 2')->getImmediateDescendants()->all()); + } + + public function testIsSelfOrAncestorOf() + { + $this->assertTrue($this->clusters('Root 1')->isSelfOrAncestorOf($this->clusters('Child 1'))); + $this->assertTrue($this->clusters('Root 1')->isSelfOrAncestorOf($this->clusters('Child 2.1'))); + $this->assertTrue($this->clusters('Child 2')->isSelfOrAncestorOf($this->clusters('Child 2.1'))); + $this->assertFalse($this->clusters('Child 2.1')->isSelfOrAncestorOf($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 1')->isSelfOrAncestorOf($this->clusters('Child 2'))); + $this->assertTrue($this->clusters('Child 1')->isSelfOrAncestorOf($this->clusters('Child 1'))); + } + + public function testIsAncestorOf() + { + $this->assertTrue($this->clusters('Root 1')->isAncestorOf($this->clusters('Child 1'))); + $this->assertTrue($this->clusters('Root 1')->isAncestorOf($this->clusters('Child 2.1'))); + $this->assertTrue($this->clusters('Child 2')->isAncestorOf($this->clusters('Child 2.1'))); + $this->assertFalse($this->clusters('Child 2.1')->isAncestorOf($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 1')->isAncestorOf($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 1')->isAncestorOf($this->clusters('Child 1'))); + } + + public function testIsSelfOrDescendantOf() + { + $this->assertTrue($this->clusters('Child 1')->isSelfOrDescendantOf($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2.1')->isSelfOrDescendantOf($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2.1')->isSelfOrDescendantOf($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 2')->isSelfOrDescendantOf($this->clusters('Child 2.1'))); + $this->assertFalse($this->clusters('Child 2')->isSelfOrDescendantOf($this->clusters('Child 1'))); + $this->assertTrue($this->clusters('Child 1')->isSelfOrDescendantOf($this->clusters('Child 1'))); + } + + public function testIsDescendantOf() + { + $this->assertTrue($this->clusters('Child 1')->isDescendantOf($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2.1')->isDescendantOf($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2.1')->isDescendantOf($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 2')->isDescendantOf($this->clusters('Child 2.1'))); + $this->assertFalse($this->clusters('Child 2')->isDescendantOf($this->clusters('Child 1'))); + $this->assertFalse($this->clusters('Child 1')->isDescendantOf($this->clusters('Child 1'))); + } + + public function testGetSiblingsAndSelf() + { + $child = $this->clusters('Child 2'); + + $expected = array($this->clusters('Child 1'), $child, $this->clusters('Child 3')); + $this->assertEquals($expected, $child->getSiblingsAndSelf()->all()); + + $expected = array($this->clusters('Root 1'), $this->clusters('Root 2')); + $this->assertEquals($expected, $this->clusters('Root 1')->getSiblingsAndSelf()->all()); + } + + public function testGetSiblings() + { + $child = $this->clusters('Child 2'); + + $expected = array($this->clusters('Child 1'), $this->clusters('Child 3')); + + $this->assertEquals($expected, $child->getSiblings()->all()); + } + + public function testGetLeftSibling() + { + $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 2')->getLeftSibling()); + $this->assertEquals($this->clusters('Child 2'), $this->clusters('Child 3')->getLeftSibling()); + } + + public function testGetLeftSiblingOfFirstRootIsNull() + { + $this->assertNull($this->clusters('Root 1')->getLeftSibling()); + } + + public function testGetLeftSiblingWithNoneIsNull() + { + $this->assertNull($this->clusters('Child 2.1')->getLeftSibling()); + } + + public function testGetLeftSiblingOfLeftmostNodeIsNull() + { + $this->assertNull($this->clusters('Child 1')->getLeftSibling()); + } + + public function testGetRightSibling() + { + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 2')->getRightSibling()); + $this->assertEquals($this->clusters('Child 2'), $this->clusters('Child 1')->getRightSibling()); + } + + public function testGetRightSiblingOfRoots() + { + $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getRightSibling()); + $this->assertNull($this->clusters('Root 2')->getRightSibling()); + } + + public function testGetRightSiblingWithNoneIsNull() + { + $this->assertNull($this->clusters('Child 2.1')->getRightSibling()); + } + + public function testGetRightSiblingOfRightmostNodeIsNull() + { + $this->assertNull($this->clusters('Child 3')->getRightSibling()); + } + + public function testInsideSubtree() + { + $this->assertFalse($this->clusters('Child 1')->insideSubtree($this->clusters('Root 2'))); + $this->assertFalse($this->clusters('Child 2')->insideSubtree($this->clusters('Root 2'))); + $this->assertFalse($this->clusters('Child 3')->insideSubtree($this->clusters('Root 2'))); + + $this->assertTrue($this->clusters('Child 1')->insideSubtree($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2')->insideSubtree($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 2.1')->insideSubtree($this->clusters('Root 1'))); + $this->assertTrue($this->clusters('Child 3')->insideSubtree($this->clusters('Root 1'))); + + $this->assertTrue($this->clusters('Child 2.1')->insideSubtree($this->clusters('Child 2'))); + $this->assertFalse($this->clusters('Child 2.1')->insideSubtree($this->clusters('Root 2'))); + } + + public function testGetLevel() + { + $this->assertEquals(0, $this->clusters('Root 1')->getLevel()); + $this->assertEquals(1, $this->clusters('Child 1')->getLevel()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getLevel()); + } + + public function testToHierarchyReturnsAnEloquentCollection() + { + $categories = Cluster::all()->toHierarchy(); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $categories); + } + + public function testToHierarchyReturnsHierarchicalData() + { + $categories = Cluster::all()->toHierarchy(); + + $this->assertEquals(2, $categories->count()); + + $first = $categories->first(); + $this->assertEquals('Root 1', $first->name); + $this->assertEquals(3, $first->children->count()); + + $first_lvl2 = $first->children->first(); + $this->assertEquals('Child 1', $first_lvl2->name); + $this->assertEquals(0, $first_lvl2->children->count()); + } + + public function testToHierarchyNestsCorrectly() + { + // Prune all categories + Cluster::query()->delete(); + + // Build a sample tree structure: + // + // - A + // |- A.1 + // |- A.2 + // - B + // |- B.1 + // |- B.2 + // |- B.2.1 + // |- B.2.2 + // |- B.2.2.1 + // |- B.2.3 + // |- B.3 + // - C + // |- C.1 + // |- C.2 + // - D + // + $a = Cluster::create(array('name' => 'A')); + $b = Cluster::create(array('name' => 'B')); + $c = Cluster::create(array('name' => 'C')); + $d = Cluster::create(array('name' => 'D')); + + $ch = Cluster::create(array('name' => 'A.1')); + $ch->makeChildOf($a); + + $ch = Cluster::create(array('name' => 'A.2')); + $ch->makeChildOf($a); + + $ch = Cluster::create(array('name' => 'B.1')); + $ch->makeChildOf($b); + + $ch = Cluster::create(array('name' => 'B.2')); + $ch->makeChildOf($b); + + $ch2 = Cluster::create(array('name' => 'B.2.1')); + $ch2->makeChildOf($ch); + + $ch2 = Cluster::create(array('name' => 'B.2.2')); + $ch2->makeChildOf($ch); + + $ch3 = Cluster::create(array('name' => 'B.2.2.1')); + $ch3->makeChildOf($ch2); + + $ch2 = Cluster::create(array('name' => 'B.2.3')); + $ch2->makeChildOf($ch); + + $ch = Cluster::create(array('name' => 'B.3')); + $ch->makeChildOf($b); + + $ch = Cluster::create(array('name' => 'C.1')); + $ch->makeChildOf($c); + + $ch = Cluster::create(array('name' => 'C.2')); + $ch->makeChildOf($c); + + $this->assertTrue(Cluster::isValidNestedSet()); + + // Build expectations (expected trees/subtrees) + $expectedWholeTree = array( + 'A' => array('A.1' => null, 'A.2' => null), + 'B' => array( + 'B.1' => null, + 'B.2' => + array( + 'B.2.1' => null, + 'B.2.2' => array('B.2.2.1' => null), + 'B.2.3' => null, + ), + 'B.3' => null, + ), + 'C' => array('C.1' => null, 'C.2' => null), + 'D' => null + ); + + $expectedSubtreeA = array('A' => array('A.1' => null, 'A.2' => null)); + + $expectedSubtreeB = array( + 'B' => array( + 'B.1' => null, + 'B.2' => + array( + 'B.2.1' => null, + 'B.2.2' => array('B.2.2.1' => null), + 'B.2.3' => null + ), + 'B.3' => null + ) + ); + + $expectedSubtreeC = array('C.1' => null, 'C.2' => null); + + $expectedSubtreeD = array('D' => null); + + // Perform assertions + $wholeTree = hmap(Cluster::all()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedWholeTree, $wholeTree); + + $subtreeA = hmap($this->clusters('A')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeA, $subtreeA); + + $subtreeB = hmap($this->clusters('B')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeB, $subtreeB); + + $subtreeC = hmap($this->clusters('C')->getDescendants()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeC, $subtreeC); + + $subtreeD = hmap($this->clusters('D')->getDescendantsAndSelf()->toHierarchy()->toArray()); + $this->assertArraysAreEqual($expectedSubtreeD, $subtreeD); + + $this->assertTrue($this->clusters('D')->getDescendants()->toHierarchy()->isEmpty()); + } + + public function testToHierarchyNestsCorrectlyNotSequential() + { + $parent = $this->clusters('Child 1'); + + $parent->children()->create(array('name' => 'Child 1.1')); + + $parent->children()->create(array('name' => 'Child 1.2')); + + $this->assertTrue(Cluster::isValidNestedSet()); + + $expected = array( + 'Child 1' => array( + 'Child 1.1' => null, + 'Child 1.2' => null + ) + ); + + $parent->reload(); + $this->assertArraysAreEqual($expected, hmap($parent->getDescendantsAndSelf()->toHierarchy()->toArray())); + } + + public function testToHierarchyNestsCorrectlyWithOrder() + { + with(new OrderedClusterSeeder)->run(); + + $expectedWhole = array( + 'Root A' => null, + 'Root Z' => array( + 'Child A' => null, + 'Child C' => null, + 'Child G' => array('Child G.1' => null) + ) + ); + $this->assertArraysAreEqual($expectedWhole, hmap(OrderedCluster::all()->toHierarchy()->toArray())); + + $expectedSubtreeZ = array( + 'Root Z' => array( + 'Child A' => null, + 'Child C' => null, + 'Child G' => array('Child G.1' => null) + ) + ); + $this->assertArraysAreEqual($expectedSubtreeZ, hmap($this->clusters('Root Z', 'OrderedCluster')->getDescendantsAndSelf()->toHierarchy()->toArray())); + } + + public function testGetNestedList() + { + $seperator = ' '; + $nestedList = Cluster::getNestedList('name', 'id', $seperator); + + $expected = array( + '7461d8f5-2ea9-4788-99c4-9d0244f0bfb1' => str_repeat($seperator, 0) . 'Root 1', + '5d7ce1fd-6151-46d3-a5b3-0ebb9988dc57' => str_repeat($seperator, 1) . 'Child 1', + '07c1fc8c-53b5-4fe7-b9c4-e09f266a455c' => str_repeat($seperator, 1) . 'Child 2', + '3315a297-af87-4ad3-9fa5-19785407573d' => str_repeat($seperator, 2) . 'Child 2.1', + '054476d2-6830-4014-a181-4de010ef7114' => str_repeat($seperator, 1) . 'Child 3', + '3bb62314-9e1e-49c6-a5cb-17a9ab9b1b9a' => str_repeat($seperator, 0) . 'Root 2', + ); + + $this->assertArraysAreEqual($expected, $nestedList); + } } diff --git a/tests/suite/Cluster/ClusterMovementTest.php b/tests/suite/Cluster/ClusterMovementTest.php index 967a68ea..cc06a7f0 100644 --- a/tests/suite/Cluster/ClusterMovementTest.php +++ b/tests/suite/Cluster/ClusterMovementTest.php @@ -1,528 +1,568 @@ clusters('Child 2')->moveLeft(); + public function testMoveLeft() + { + $this->clusters('Child 2')->moveLeft(); - $this->assertNull($this->clusters('Child 2')->getLeftSibling()); + $this->assertNull($this->clusters('Child 2')->getLeftSibling()); - $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 2')->getRightSibling()); + $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 2')->getRightSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveLeftRaisesAnExceptionWhenNotPossible() { - $node = $this->clusters('Child 2'); + public function testMoveLeftRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $node->moveLeft(); - $node->moveLeft(); - } + $node = $this->clusters('Child 2'); - public function testMoveLeftDoesNotChangeDepth() { - $this->clusters('Child 2')->moveLeft(); + $node->moveLeft(); + $node->moveLeft(); + } - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + public function testMoveLeftDoesNotChangeDepth() + { + $this->clusters('Child 2')->moveLeft(); - public function testMoveLeftWithSubtree() { - $this->clusters('Root 2')->moveLeft(); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertNull($this->clusters('Root 2')->getLeftSibling()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 2')->getRightSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); + public function testMoveLeftWithSubtree() + { + $this->clusters('Root 2')->moveLeft(); - $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); - $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); + $this->assertNull($this->clusters('Root 2')->getLeftSibling()); + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 2')->getRightSibling()); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); + $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); + $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); - public function testMoveToLeftOf() { - $this->clusters('Child 3')->moveToLeftOf($this->clusters('Child 1')); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertNull($this->clusters('Child 3')->getLeftSibling()); + public function testMoveToLeftOf() + { + $this->clusters('Child 3')->moveToLeftOf($this->clusters('Child 1')); - $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 3')->getRightSibling()); + $this->assertNull($this->clusters('Child 3')->getLeftSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertEquals($this->clusters('Child 1'), $this->clusters('Child 3')->getRightSibling()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveToLeftOfRaisesAnExceptionWhenNotPossible() { - $this->clusters('Child 1')->moveToLeftOf($this->clusters('Child 1')->getLeftSibling()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMoveToLeftOfDoesNotChangeDepth() { - $this->clusters('Child 2')->moveToLeftOf($this->clusters('Child 1')); + public function testMoveToLeftOfRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $this->clusters('Child 1')->moveToLeftOf($this->clusters('Child 1')->getLeftSibling()); + } - public function testMoveToLeftOfWithSubtree() { - $this->clusters('Root 2')->moveToLeftOf($this->clusters('Root 1')); + public function testMoveToLeftOfDoesNotChangeDepth() + { + $this->clusters('Child 2')->moveToLeftOf($this->clusters('Child 1')); - $this->assertNull($this->clusters('Root 2')->getLeftSibling()); - $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 2')->getRightSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); - $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); + public function testMoveToLeftOfWithSubtree() + { + $this->clusters('Root 2')->moveToLeftOf($this->clusters('Root 1')); - $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); + $this->assertNull($this->clusters('Root 2')->getLeftSibling()); + $this->assertEquals($this->clusters('Root 1'), $this->clusters('Root 2')->getRightSibling()); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); + $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); - public function testMoveRight() { - $this->clusters('Child 2')->moveRight(); + $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); - $this->assertNull($this->clusters('Child 2')->getRightSibling()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 2')->getLeftSibling()); + public function testMoveRight() + { + $this->clusters('Child 2')->moveRight(); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertNull($this->clusters('Child 2')->getRightSibling()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveRightRaisesAnExceptionWhenNotPossible() { - $node = $this->clusters('Child 2'); + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 2')->getLeftSibling()); - $node->moveRight(); - $node->moveRight(); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMoveRightDoesNotChangeDepth() { - $this->clusters('Child 2')->moveRight(); + public function testMoveRightRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $node = $this->clusters('Child 2'); - public function testMoveRightWithSubtree() { - $this->clusters('Root 1')->moveRight(); + $node->moveRight(); + $node->moveRight(); + } - $this->assertNull($this->clusters('Root 1')->getRightSibling()); - $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getLeftSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); + public function testMoveRightDoesNotChangeDepth() + { + $this->clusters('Child 2')->moveRight(); - $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); - $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); + public function testMoveRightWithSubtree() + { + $this->clusters('Root 1')->moveRight(); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $this->assertNull($this->clusters('Root 1')->getRightSibling()); + $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getLeftSibling()); + $this->assertTrue(Cluster::isValidNestedSet()); - public function testMoveToRightOf() { - $this->clusters('Child 1')->moveToRightOf($this->clusters('Child 3')); + $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); + $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); - $this->assertNull($this->clusters('Child 1')->getRightSibling()); + $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->getLeftSibling()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertTrue(Cluster::isValidNestedSet()); - } + public function testMoveToRightOf() + { + $this->clusters('Child 1')->moveToRightOf($this->clusters('Child 3')); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testMoveToRightOfRaisesAnExceptionWhenNotPossible() { - $this->clusters('Child 3')->moveToRightOf($this->clusters('Child 3')->getRightSibling()); - } + $this->assertNull($this->clusters('Child 1')->getRightSibling()); - public function testMoveToRightOfDoesNotChangeDepth() { - $this->clusters('Child 2')->moveToRightOf($this->clusters('Child 3')); + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->getLeftSibling()); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMoveToRightOfWithSubtree() { - $this->clusters('Root 1')->moveToRightOf($this->clusters('Root 2')); + public function testMoveToRightOfRaisesAnExceptionWhenNotPossible() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $this->assertNull($this->clusters('Root 1')->getRightSibling()); - $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getLeftSibling()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->clusters('Child 3')->moveToRightOf($this->clusters('Child 3')->getRightSibling()); + } - $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); - $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); + public function testMoveToRightOfDoesNotChangeDepth() + { + $this->clusters('Child 2')->moveToRightOf($this->clusters('Child 3')); - $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); - } + public function testMoveToRightOfWithSubtree() + { + $this->clusters('Root 1')->moveToRightOf($this->clusters('Root 2')); - public function testMakeRoot() { - $this->clusters('Child 2')->makeRoot(); + $this->assertNull($this->clusters('Root 1')->getRightSibling()); + $this->assertEquals($this->clusters('Root 2'), $this->clusters('Root 1')->getLeftSibling()); + $this->assertTrue(Cluster::isValidNestedSet()); - $newRoot = $this->clusters('Child 2'); + $this->assertEquals(0, $this->clusters('Root 1')->getDepth()); + $this->assertEquals(0, $this->clusters('Root 2')->getDepth()); - $this->assertNull($newRoot->parent()->first()); - $this->assertEquals(0, $newRoot->getLevel()); - $this->assertEquals(9, $newRoot->getLeft()); - $this->assertEquals(12, $newRoot->getRight()); + $this->assertEquals(1, $this->clusters('Child 1')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 2')->getDepth()); + $this->assertEquals(1, $this->clusters('Child 3')->getDepth()); - $this->assertEquals(1, $this->clusters('Child 2.1')->getLevel()); + $this->assertEquals(2, $this->clusters('Child 2.1')->getDepth()); + } - $this->assertTrue(Cluster::isValidNestedSet()); - } + public function testMakeRoot() + { + $this->clusters('Child 2')->makeRoot(); - public function testNullifyParentColumnMakesItRoot() { - $node = $this->clusters('Child 2'); + $newRoot = $this->clusters('Child 2'); - $node->parent_id = null; + $this->assertNull($newRoot->parent()->first()); + $this->assertEquals(0, $newRoot->getLevel()); + $this->assertEquals(9, $newRoot->getLeft()); + $this->assertEquals(12, $newRoot->getRight()); - $node->save(); + $this->assertEquals(1, $this->clusters('Child 2.1')->getLevel()); - $this->assertNull($node->parent()->first()); - $this->assertEquals(0, $node->getLevel()); - $this->assertEquals(9, $node->getLeft()); - $this->assertEquals(12, $node->getRight()); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertEquals(1, $this->clusters('Child 2.1')->getLevel()); + public function testNullifyParentColumnMakesItRoot() + { + $node = $this->clusters('Child 2'); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $node->parent_id = null; - public function testNullifyParentColumnOnNewNodes() { - $node = new Cluster(['name' => 'Root 3']); + $node->save(); - $node->parent_id = null; + $this->assertNull($node->parent()->first()); + $this->assertEquals(0, $node->getLevel()); + $this->assertEquals(9, $node->getLeft()); + $this->assertEquals(12, $node->getRight()); - $node->save(); + $this->assertEquals(1, $this->clusters('Child 2.1')->getLevel()); - $node->reload(); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertNull($node->parent()->first()); - $this->assertEquals(0, $node->getLevel()); - $this->assertEquals(13, $node->getLeft()); - $this->assertEquals(14, $node->getRight()); + public function testNullifyParentColumnOnNewNodes() + { + $node = new Cluster(['name' => 'Root 3']); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $node->parent_id = null; - public function testNewClusterWithNullParent() { - $node = new Cluster(['name' => 'Root 3']); - $this->assertTrue($node->isRoot()); + $node->save(); - $node->save(); - $this->assertTrue($node->isRoot()); + $node->reload(); - $node->makeRoot(); - $this->assertTrue($node->isRoot()); - } + $this->assertNull($node->parent()->first()); + $this->assertEquals(0, $node->getLevel()); + $this->assertEquals(13, $node->getLeft()); + $this->assertEquals(14, $node->getRight()); - public function testMakeChildOf() { - $this->clusters('Child 1')->makeChildOf($this->clusters('Child 3')); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); + public function testNewClusterWithNullParent() + { + $node = new Cluster(['name' => 'Root 3']); + $this->assertTrue($node->isRoot()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $node->save(); + $this->assertTrue($node->isRoot()); - public function testMakeChildOfAppendsAtTheEnd() { - $newChild = Cluster::create(array('name' => 'Child 4')); + $node->makeRoot(); + $this->assertTrue($node->isRoot()); + } - $newChild->makeChildOf($this->clusters('Root 1')); + public function testMakeChildOf() + { + $this->clusters('Child 1')->makeChildOf($this->clusters('Child 3')); - $lastChild = $this->clusters('Root 1')->children()->get()->last(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMakeChildOfMovesWithSubtree() { - $this->clusters('Child 2')->makeChildOf($this->clusters('Child 1')); + public function testMakeChildOfAppendsAtTheEnd() + { + $newChild = Cluster::create(array('name' => 'Child 4')); - $this->assertTrue(Cluster::isValidNestedSet()); + $newChild->makeChildOf($this->clusters('Root 1')); - $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); + $lastChild = $this->clusters('Root 1')->children()->get()->last(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); - $this->assertEquals(6, $this->clusters('Child 2')->getRight()); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); - $this->assertEquals(7, $this->clusters('Child 1')->getRight()); - } + public function testMakeChildOfMovesWithSubtree() + { + $this->clusters('Child 2')->makeChildOf($this->clusters('Child 1')); - public function testMakeChildOfSwappingRoots() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); - $this->clusters('Root 2')->makeChildOf($newRoot); + $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); + $this->assertEquals(6, $this->clusters('Child 2')->getRight()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); + $this->assertEquals(7, $this->clusters('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); + public function testMakeChildOfSwappingRoots() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->clusters('Root 2')->makeChildOf($newRoot); - public function testMakeChildOfSwappingRootsWithSubtrees() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->clusters('Root 1')->makeChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 1')->getRight()); + public function testMakeChildOfSwappingRootsWithSubtrees() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); - } + $this->clusters('Root 1')->makeChildOf($newRoot); - public function testMakeFirstChildOf() { - $this->clusters('Child 1')->makeFirstChildOf($this->clusters('Child 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 1')->getRight()); - public function testMakeFirstChildOfAppendsAtTheBeginning() { - $newChild = Cluster::create(array('name' => 'Child 4')); + $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); + } - $newChild->makeFirstChildOf($this->clusters('Root 1')); + public function testMakeFirstChildOf() + { + $this->clusters('Child 1')->makeFirstChildOf($this->clusters('Child 3')); - $lastChild = $this->clusters('Root 1')->children()->get()->first(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMakeFirstChildOfMovesWithSubtree() { - $this->clusters('Child 2')->makeFirstChildOf($this->clusters('Child 1')); + public function testMakeFirstChildOfAppendsAtTheBeginning() + { + $newChild = Cluster::create(array('name' => 'Child 4')); - $this->assertTrue(Cluster::isValidNestedSet()); + $newChild->makeFirstChildOf($this->clusters('Root 1')); - $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); + $lastChild = $this->clusters('Root 1')->children()->get()->first(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); - $this->assertEquals(6, $this->clusters('Child 2')->getRight()); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); - $this->assertEquals(7, $this->clusters('Child 1')->getRight()); - } + public function testMakeFirstChildOfMovesWithSubtree() + { + $this->clusters('Child 2')->makeFirstChildOf($this->clusters('Child 1')); - public function testMakeFirstChildOfSwappingRoots() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); - $this->clusters('Root 2')->makeFirstChildOf($newRoot); + $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); + $this->assertEquals(6, $this->clusters('Child 2')->getRight()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); + $this->assertEquals(7, $this->clusters('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); + public function testMakeFirstChildOfSwappingRoots() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->clusters('Root 2')->makeFirstChildOf($newRoot); - public function testMakeFirstChildOfSwappingRootsWithSubtrees() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->clusters('Root 1')->makeFirstChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 1')->getRight()); + public function testMakeFirstChildOfSwappingRootsWithSubtrees() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); - } + $this->clusters('Root 1')->makeFirstChildOf($newRoot); - public function testMakeLastChildOf() { - $this->clusters('Child 1')->makeLastChildOf($this->clusters('Child 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 1')->getRight()); - public function testMakeLastChildOfAppendsAtTheEnd() { - $newChild = Cluster::create(array('name' => 'Child 4')); + $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); + } - $newChild->makeLastChildOf($this->clusters('Root 1')); + public function testMakeLastChildOf() + { + $this->clusters('Child 1')->makeLastChildOf($this->clusters('Child 3')); - $lastChild = $this->clusters('Root 1')->children()->get()->last(); - $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); + $this->assertEquals($this->clusters('Child 3'), $this->clusters('Child 1')->parent()->first()); - $this->assertTrue(Cluster::isValidNestedSet()); - } + $this->assertTrue(Cluster::isValidNestedSet()); + } - public function testMakeLastChildOfMovesWithSubtree() { - $this->clusters('Child 2')->makeLastChildOf($this->clusters('Child 1')); + public function testMakeLastChildOfAppendsAtTheEnd() + { + $newChild = Cluster::create(array('name' => 'Child 4')); - $this->assertTrue(Cluster::isValidNestedSet()); + $newChild->makeLastChildOf($this->clusters('Root 1')); - $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); + $lastChild = $this->clusters('Root 1')->children()->get()->last(); + $this->assertEquals($newChild->getAttributes(), $lastChild->getAttributes()); - $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); - $this->assertEquals(6, $this->clusters('Child 2')->getRight()); + $this->assertTrue(Cluster::isValidNestedSet()); + } - $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); - $this->assertEquals(7, $this->clusters('Child 1')->getRight()); - } + public function testMakeLastChildOfMovesWithSubtree() + { + $this->clusters('Child 2')->makeLastChildOf($this->clusters('Child 1')); - public function testMakeLastChildOfSwappingRoots() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->assertEquals(13, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); + $this->assertEquals($this->clusters('Child 1')->getKey(), $this->clusters('Child 2')->getParentId()); - $this->clusters('Root 2')->makeLastChildOf($newRoot); + $this->assertEquals(3, $this->clusters('Child 2')->getLeft()); + $this->assertEquals(6, $this->clusters('Child 2')->getRight()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(2, $this->clusters('Child 1')->getLeft()); + $this->assertEquals(7, $this->clusters('Child 1')->getRight()); + } - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); + public function testMakeLastChildOfSwappingRoots() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 2')->getRight()); + $this->assertEquals(13, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); - $this->assertEquals(11, $newRoot->getLeft()); - $this->assertEquals(14, $newRoot->getRight()); - } + $this->clusters('Root 2')->makeLastChildOf($newRoot); - public function testMakeLastChildOfSwappingRootsWithSubtrees() { - $newRoot = Cluster::create(array('name' => 'Root 3')); + $this->assertTrue(Cluster::isValidNestedSet()); - $this->clusters('Root 1')->makeLastChildOf($newRoot); + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 2')->getParentId()); - $this->assertTrue(Cluster::isValidNestedSet()); + $this->assertEquals(12, $this->clusters('Root 2')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 2')->getRight()); - $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); + $this->assertEquals(11, $newRoot->getLeft()); + $this->assertEquals(14, $newRoot->getRight()); + } - $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); - $this->assertEquals(13, $this->clusters('Root 1')->getRight()); + public function testMakeLastChildOfSwappingRootsWithSubtrees() + { + $newRoot = Cluster::create(array('name' => 'Root 3')); - $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); - $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); - } + $this->clusters('Root 1')->makeLastChildOf($newRoot); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testUnpersistedNodeCannotBeMoved() { - $unpersisted = new Cluster(array('name' => 'Unpersisted')); + $this->assertTrue(Cluster::isValidNestedSet()); - $unpersisted->moveToRightOf($this->clusters('Root 1')); - } + $this->assertEquals($newRoot->getKey(), $this->clusters('Root 1')->getParentId()); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testUnpersistedNodeCannotBeMadeChild() { - $unpersisted = new Cluster(array('name' => 'Unpersisted')); + $this->assertEquals(4, $this->clusters('Root 1')->getLeft()); + $this->assertEquals(13, $this->clusters('Root 1')->getRight()); - $unpersisted->makeChildOf($this->clusters('Root 1')); - } + $this->assertEquals(8, $this->clusters('Child 2.1')->getLeft()); + $this->assertEquals(9, $this->clusters('Child 2.1')->getRight()); + } - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMovedToItself() { - $node = $this->clusters('Child 1'); + public function testUnpersistedNodeCannotBeMoved() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $node->moveToRightOf($node); - } + $unpersisted = new Cluster(array('name' => 'Unpersisted')); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMadeChildOfThemselves() { - $node = $this->clusters('Child 1'); + $unpersisted->moveToRightOf($this->clusters('Root 1')); + } - $node->makeChildOf($node); - } + public function testUnpersistedNodeCannotBeMadeChild() + { + $this->expectException(Baum\MoveNotPossibleException::class); - /** - * @expectedException Baum\MoveNotPossibleException - */ - public function testNodesCannotBeMovedToDescendantsOfThemselves() { - $node = $this->clusters('Root 1'); + $unpersisted = new Cluster(array('name' => 'Unpersisted')); - $node->makeChildOf($this->clusters('Child 2.1')); - } + $unpersisted->makeChildOf($this->clusters('Root 1')); + } - public function testDepthIsUpdatedWhenMadeChild() { - $a = Cluster::create(array('name' => 'A')); - $b = Cluster::create(array('name' => 'B')); - $c = Cluster::create(array('name' => 'C')); - $d = Cluster::create(array('name' => 'D')); + public function testNodesCannotBeMovedToItself() + { + $this->expectException(Baum\MoveNotPossibleException::class); - // a > b > c > d - $b->makeChildOf($a); - $c->makeChildOf($b); - $d->makeChildOf($c); + $node = $this->clusters('Child 1'); - $a->reload(); - $b->reload(); - $c->reload(); - $d->reload(); + $node->moveToRightOf($node); + } - $this->assertEquals(0, $a->getDepth()); - $this->assertEquals(1, $b->getDepth()); - $this->assertEquals(2, $c->getDepth()); - $this->assertEquals(3, $d->getDepth()); - } + public function testNodesCannotBeMadeChildOfThemselves() + { + $this->expectException(Baum\MoveNotPossibleException::class); - public function testDepthIsUpdatedOnDescendantsWhenParentMoves() { - $a = Cluster::create(array('name' => 'A')); - $b = Cluster::create(array('name' => 'B')); - $c = Cluster::create(array('name' => 'C')); - $d = Cluster::create(array('name' => 'D')); + $node = $this->clusters('Child 1'); - // a > b > c > d - $b->makeChildOf($a); - $c->makeChildOf($b); - $d->makeChildOf($c); + $node->makeChildOf($node); + } - $a->reload(); $b->reload(); $c->reload(); $d->reload(); + public function testNodesCannotBeMovedToDescendantsOfThemselves() + { + $this->expectException(Baum\MoveNotPossibleException::class); - $b->moveToRightOf($a); + $node = $this->clusters('Root 1'); - $a->reload(); $b->reload(); $c->reload(); $d->reload(); + $node->makeChildOf($this->clusters('Child 2.1')); + } - $this->assertEquals(0, $b->getDepth()); - $this->assertEquals(1, $c->getDepth()); - $this->assertEquals(2, $d->getDepth()); - } + public function testDepthIsUpdatedWhenMadeChild() + { + $a = Cluster::create(array('name' => 'A')); + $b = Cluster::create(array('name' => 'B')); + $c = Cluster::create(array('name' => 'C')); + $d = Cluster::create(array('name' => 'D')); + + // a > b > c > d + $b->makeChildOf($a); + $c->makeChildOf($b); + $d->makeChildOf($c); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $this->assertEquals(0, $a->getDepth()); + $this->assertEquals(1, $b->getDepth()); + $this->assertEquals(2, $c->getDepth()); + $this->assertEquals(3, $d->getDepth()); + } + + public function testDepthIsUpdatedOnDescendantsWhenParentMoves() + { + $a = Cluster::create(array('name' => 'A')); + $b = Cluster::create(array('name' => 'B')); + $c = Cluster::create(array('name' => 'C')); + $d = Cluster::create(array('name' => 'D')); + + // a > b > c > d + $b->makeChildOf($a); + $c->makeChildOf($b); + $d->makeChildOf($c); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $b->moveToRightOf($a); + + $a->reload(); + $b->reload(); + $c->reload(); + $d->reload(); + + $this->assertEquals(0, $b->getDepth()); + $this->assertEquals(1, $c->getDepth()); + $this->assertEquals(2, $d->getDepth()); + } } diff --git a/tests/suite/ClusterTestCase.php b/tests/suite/ClusterTestCase.php index 81038ad0..c17d8425 100644 --- a/tests/suite/ClusterTestCase.php +++ b/tests/suite/ClusterTestCase.php @@ -1,17 +1,21 @@ up(); - } + public static function setUpBeforeClass(): void + { + with(new ClusterMigrator)->up(); + } - public function setUp() { - with(new ClusterSeeder)->run(); - } + public function setUp(): void + { + with(new ClusterSeeder)->run(); + } - protected function clusters($name, $className = 'Cluster') { - return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); - } + protected function clusters($name, $className = 'Cluster') + { + return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); + } } diff --git a/tests/suite/NodeModelExtensionsTest.php b/tests/suite/NodeModelExtensionsTest.php index 0714d888..d5ff4066 100644 --- a/tests/suite/NodeModelExtensionsTest.php +++ b/tests/suite/NodeModelExtensionsTest.php @@ -4,179 +4,202 @@ use Illuminate\Database\Capsule\Manager as DB; use PHPUnit\Framework\TestCase; -class NodeModelExtensionsTest extends TestCase { +class NodeModelExtensionsTest extends TestCase +{ - public static function setUpBeforeClass() { - with(new CategoryMigrator)->up(); - } + public static function setUpBeforeClass(): void + { + with(new CategoryMigrator)->up(); + } - public function setUp() { - DB::table('categories')->delete(); - } + public function setUp(): void + { + DB::table('categories')->delete(); + } - protected function categories($name, $className = 'Category') { - return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); - } + protected function categories($name, $className = 'Category') + { + return forward_static_call_array(array($className, 'where'), array('name', '=', $name))->first(); + } - public function tearDown() { - m::close(); - } + public function tearDown(): void + { + m::close(); + } - public function testNewQueryReturnsEloquentBuilderWithExtendedQueryBuilder() { - $query = with(new Category)->newQuery()->getQuery(); + public function testNewQueryReturnsEloquentBuilderWithExtendedQueryBuilder() + { + $query = with(new Category)->newQuery()->getQuery(); - $this->assertInstanceOf('Baum\Extensions\Query\Builder', $query); - } + $this->assertInstanceOf('Baum\Extensions\Query\Builder', $query); + } - public function testNewCollectionReturnsCustomOne() { - $this->assertInstanceOf('\Baum\Extensions\Eloquent\Collection', with(new Category)->newCollection()); - } + public function testNewCollectionReturnsCustomOne() + { + $this->assertInstanceOf('\Baum\Extensions\Eloquent\Collection', with(new Category)->newCollection()); + } - public function testGetObservableEventsIncludesMovingEvents() { - $events = with(new Category)->getObservableEvents(); + public function testGetObservableEventsIncludesMovingEvents() + { + $events = with(new Category)->getObservableEvents(); - $this->assertContains('moving', $events); - $this->assertContains('moved', $events); - } + $this->assertContains('moving', $events); + $this->assertContains('moved', $events); + } - public function testAreSoftDeletesEnabled() { - $this->assertFalse(with(new Category)->areSoftDeletesEnabled()); - $this->assertTrue(with(new SoftCategory)->areSoftDeletesEnabled()); - } + public function testAreSoftDeletesEnabled() + { + $this->assertFalse(with(new Category)->areSoftDeletesEnabled()); + $this->assertTrue(with(new SoftCategory)->areSoftDeletesEnabled()); + } - public function testSoftDeletesEnabledStatic() { - $this->assertFalse(Category::softDeletesEnabled()); - $this->assertTrue(SoftCategory::softDeletesEnabled()); - } + public function testSoftDeletesEnabledStatic() + { + $this->assertFalse(Category::softDeletesEnabled()); + $this->assertTrue(SoftCategory::softDeletesEnabled()); + } - public function testMoving() { - $dispatcher = Category::getEventDispatcher(); + public function testMoving() + { + $dispatcher = Category::getEventDispatcher(); - Category::setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); + Category::setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); - $closure = function() {}; + $closure = function () { + }; - $events->shouldReceive('listen')->once()->with('eloquent.moving: ' . Category::class, $closure); + $events->shouldReceive('listen')->once()->with('eloquent.moving: ' . Category::class, $closure); - Category::moving($closure); + Category::moving($closure); - Category::unsetEventDispatcher(); + Category::unsetEventDispatcher(); - Category::setEventDispatcher($dispatcher); - } + Category::setEventDispatcher($dispatcher); + } - public function testMoved() { - $dispatcher = Category::getEventDispatcher(); + public function testMoved() + { + $dispatcher = Category::getEventDispatcher(); - Category::setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); + Category::setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); - $closure = function() {}; + $closure = function () { + }; - $events->shouldReceive('listen')->once()->with('eloquent.moved: ' . Category::class, $closure); + $events->shouldReceive('listen')->once()->with('eloquent.moved: ' . Category::class, $closure); - Category::moved($closure); + Category::moved($closure); - Category::unsetEventDispatcher(); + Category::unsetEventDispatcher(); - Category::setEventDispatcher($dispatcher); - } + Category::setEventDispatcher($dispatcher); + } - public function testReloadResetsChangesOnFreshNodes() { - $new = new Category; + public function testReloadResetsChangesOnFreshNodes() + { + $new = new Category; - $new->name = 'Some new category'; - $new->reload(); + $new->name = 'Some new category'; + $new->reload(); + + $this->assertNull($new->name); + } + + public function testReloadResetsChangesOnPersistedNodes() + { + $node = Category::create(['name' => 'Some node']); + + $node->name = 'A better node'; + $node->lft = 10; + $node->reload(); + + $this->assertEquals($this->categories('Some node')->getAttributes(), $node->getAttributes()); + } - $this->assertNull($new->name); - } + public function testReloadResetsChangesOnDeletedNodes() + { + $node = Category::create(['name' => 'Some node']); + $this->assertNotNull($node->getKey()); - public function testReloadResetsChangesOnPersistedNodes() { - $node = Category::create(['name' => 'Some node']); + $node->delete(); + $this->assertNull($this->categories('Some node')); - $node->name = 'A better node'; - $node->lft = 10; - $node->reload(); + $node->name = 'A better node'; + $node->reload(); - $this->assertEquals($this->categories('Some node')->getAttributes(), $node->getAttributes()); - } + $this->assertEquals('Some node', $node->name); + } - public function testReloadResetsChangesOnDeletedNodes() { - $node = Category::create(['name' => 'Some node']); - $this->assertNotNull($node->getKey()); + public function testReloadThrowsExceptionIfNodeCannotBeLocated() + { + $this->expectException(Illuminate\Database\Eloquent\ModelNotFoundException::class); - $node->delete(); - $this->assertNull($this->categories('Some node')); + $node = Category::create(['name' => 'Some node']); + $this->assertNotNull($node->getKey()); - $node->name = 'A better node'; - $node->reload(); + $node->delete(); + $this->assertNull($this->categories('Some node')); + $this->assertFalse($node->exists); - $this->assertEquals('Some node', $node->name); - } + // Fake persisted state, reload & expect failure + $node->exists = true; + $node->reload(); + } - /** - * @expectedException Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function testReloadThrowsExceptionIfNodeCannotBeLocated() { - $node = Category::create(['name' => 'Some node']); - $this->assertNotNull($node->getKey()); + public function testNewNestedSetQueryUsesInternalBuilder() + { + $category = new Category; + $builder = $category->newNestedSetQuery(); + $query = $builder->getQuery(); - $node->delete(); - $this->assertNull($this->categories('Some node')); - $this->assertFalse($node->exists); + $this->assertInstanceOf('Baum\Extensions\Query\Builder', $query); + } - // Fake persisted state, reload & expect failure - $node->exists = true; - $node->reload(); - } + public function testNewNestedSetQueryIsOrderedByDefault() + { + $category = new Category; + $builder = $category->newNestedSetQuery(); + $query = $builder->getQuery(); - public function testNewNestedSetQueryUsesInternalBuilder() { - $category = new Category; - $builder = $category->newNestedSetQuery(); - $query = $builder->getQuery(); + $this->assertEmpty($query->wheres); + $this->assertNotEmpty($query->orders); + $this->assertEquals($category->getLeftColumnName(), $category->getOrderColumnName()); + $this->assertEquals($category->getQualifiedLeftColumnName(), $category->getQualifiedOrderColumnName()); + $this->assertEquals($category->getQualifiedOrderColumnName(), $query->orders[0]['column']); + } - $this->assertInstanceOf('Baum\Extensions\Query\Builder', $query); - } - - public function testNewNestedSetQueryIsOrderedByDefault() { - $category = new Category; - $builder = $category->newNestedSetQuery(); - $query = $builder->getQuery(); - - $this->assertEmpty($query->wheres); - $this->assertNotEmpty($query->orders); - $this->assertEquals($category->getLeftColumnName(), $category->getOrderColumnName()); - $this->assertEquals($category->getQualifiedLeftColumnName(), $category->getQualifiedOrderColumnName()); - $this->assertEquals($category->getQualifiedOrderColumnName(), $query->orders[0]['column']); - } - - public function testNewNestedSetQueryIsOrderedByCustom() { - $category = new OrderedCategory; - $builder = $category->newNestedSetQuery(); - $query = $builder->getQuery(); - - $this->assertEmpty($query->wheres); - $this->assertNotEmpty($query->orders); - $this->assertEquals('name', $category->getOrderColumnName()); - $this->assertEquals('categories.name', $category->getQualifiedOrderColumnName()); - $this->assertEquals($category->getQualifiedOrderColumnName(), $query->orders[0]['column']); - } - - public function testNewNestedSetQueryIncludesScopedColumns() { - $category = new Category; - $simpleQuery = $category->newNestedSetQuery()->getQuery(); - $this->assertEmpty($simpleQuery->wheres); - - $scopedCategory = new ScopedCategory; - $scopedQuery = $scopedCategory->newNestedSetQuery()->getQuery(); - $this->assertCount(1, $scopedQuery->wheres); - $this->assertEquals($scopedCategory->getScopedColumns(), array_map(function($elem) { - return $elem['column']; }, $scopedQuery->wheres)); - - $multiScopedCategory = new MultiScopedCategory; - $multiScopedQuery = $multiScopedCategory->newNestedSetQuery()->getQuery(); - $this->assertCount(2, $multiScopedQuery->wheres); - $this->assertEquals($multiScopedCategory->getScopedColumns(), array_map(function($elem) { - return $elem['column']; }, $multiScopedQuery->wheres)); - } + public function testNewNestedSetQueryIsOrderedByCustom() + { + $category = new OrderedCategory; + $builder = $category->newNestedSetQuery(); + $query = $builder->getQuery(); + + $this->assertEmpty($query->wheres); + $this->assertNotEmpty($query->orders); + $this->assertEquals('name', $category->getOrderColumnName()); + $this->assertEquals('categories.name', $category->getQualifiedOrderColumnName()); + $this->assertEquals($category->getQualifiedOrderColumnName(), $query->orders[0]['column']); + } + + public function testNewNestedSetQueryIncludesScopedColumns() + { + $category = new Category; + $simpleQuery = $category->newNestedSetQuery()->getQuery(); + $this->assertEmpty($simpleQuery->wheres); + + $scopedCategory = new ScopedCategory; + $scopedQuery = $scopedCategory->newNestedSetQuery()->getQuery(); + $this->assertCount(1, $scopedQuery->wheres); + $this->assertEquals($scopedCategory->getScopedColumns(), array_map(function ($elem) { + return $elem['column']; + }, $scopedQuery->wheres)); + + $multiScopedCategory = new MultiScopedCategory; + $multiScopedQuery = $multiScopedCategory->newNestedSetQuery()->getQuery(); + $this->assertCount(2, $multiScopedQuery->wheres); + $this->assertEquals($multiScopedCategory->getScopedColumns(), array_map(function ($elem) { + return $elem['column']; + }, $multiScopedQuery->wheres)); + } } diff --git a/tests/suite/QueryBuilderExtensionTest.php b/tests/suite/QueryBuilderExtensionTest.php index 06aa2b89..e130536f 100644 --- a/tests/suite/QueryBuilderExtensionTest.php +++ b/tests/suite/QueryBuilderExtensionTest.php @@ -7,58 +7,71 @@ use Baum\Extensions\Query\Builder; use PHPUnit\Framework\TestCase; -class QueryBuilderExtensionTest extends TestCase { - - public function tearDown() { - m::close(); - } - - protected function getBuilder() { - $grammar = new Grammar; - - $processor = m::mock(Processor::class); - - return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); - } - - public function testReorderBy() { - $builder = $this->getBuilder(); - - $builder->select('*')->from('users')->orderBy('email')->orderBy('age', 'desc')->reOrderBy('full_name', 'asc'); - - $this->assertEquals('select * from "users" order by "full_name" asc', $builder->toSql()); - } - - public function testAggregatesRemoveOrderBy() { - $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); - $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); - $results = $builder->from('users')->orderBy('age', 'desc')->count(); - $this->assertEquals(1, $results); - - $builder = $this->getBuilder(); - - $builder->getConnection()->shouldReceive('select')->once()->with('select exists(select * from "users") as "exists"', [], true)->andReturn([['exists' => 1]]); - $results = $builder->from('users')->orderBy('age', 'desc')->exists(); - $this->assertTrue($results); - - $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select max("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); - $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); - $results = $builder->from('users')->orderBy('age', 'desc')->max('id'); - $this->assertEquals(1, $results); - - $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select min("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); - $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); - $results = $builder->from('users')->orderBy('age', 'desc')->min('id'); - $this->assertEquals(1, $results); - - $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); - $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); - $results = $builder->from('users')->orderBy('age', 'desc')->sum('id'); - $this->assertEquals(1, $results); - } +class QueryBuilderExtensionTest extends TestCase +{ + + public function tearDown(): void + { + m::close(); + } + + protected function getBuilder() + { + $grammar = new Grammar; + + $processor = m::mock(Processor::class); + + return new Builder(m::mock(ConnectionInterface::class), $grammar, $processor); + } + + public function testReorderBy() + { + $builder = $this->getBuilder(); + + $builder->select('*')->from('users')->orderBy('email')->orderBy('age', 'desc')->reOrderBy('full_name', 'asc'); + + $this->assertEquals('select * from "users" order by "full_name" asc', $builder->toSql()); + } + + public function testAggregatesRemoveOrderBy() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + $results = $builder->from('users')->orderBy('age', 'desc')->count(); + $this->assertEquals(1, $results); + + $builder = $this->getBuilder(); + + $builder->getConnection()->shouldReceive('select')->once()->with('select exists(select * from "users") as "exists"', [], true)->andReturn([['exists' => 1]]); + $results = $builder->from('users')->orderBy('age', 'desc')->exists(); + $this->assertTrue($results); + + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select max("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + $results = $builder->from('users')->orderBy('age', 'desc')->max('id'); + $this->assertEquals(1, $results); + + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select min("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + $results = $builder->from('users')->orderBy('age', 'desc')->min('id'); + $this->assertEquals(1, $results); + + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from "users"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + $results = $builder->from('users')->orderBy('age', 'desc')->sum('id'); + $this->assertEquals(1, $results); + } } diff --git a/tests/suite/support.php b/tests/suite/support.php index 54c7777f..ebb19cd4 100644 --- a/tests/suite/support.php +++ b/tests/suite/support.php @@ -1,59 +1,63 @@ 0 ) - $current['children'] = hmap($children, $preserve); +use Illuminate\Support\Arr; + +if (!function_exists('hmap')) { + + /** + * Simple function which aids in converting the tree hierarchy into something + * more easily testable... + * + * @param array $nodes + * @return array + */ + function hmap(array $nodes, $preserve = null) + { + $output = array(); + + foreach ($nodes as $node) { + if (is_null($preserve)) { + $output[$node['name']] = empty($node['children']) ? null : hmap($node['children']); + } else { + $preserve = is_string($preserve) ? array($preserve) : $preserve; + + $current = Arr::only($node, $preserve); + if (array_key_exists('children', $node)) { + $children = $node['children']; + + if (count($children) > 0) + $current['children'] = hmap($children, $preserve); + } + + $output[] = $current; + } } - $output[] = $current; - } + return $output; } - return $output; - } - } -if ( !function_exists('array_ints_keys') ) { - - /** - * Cast provided keys's values into ints. This is to wrestle with PDO driver - * inconsistencies. - * - * @param array $input - * @param mixed $keys - * @return array - */ - function array_ints_keys(array $input, $keys='id') { - $keys = is_string($keys) ? array($keys) : $keys; - - array_walk_recursive($input, function(&$value, $key) use ($keys) { - if ( array_search($key, $keys) !== false ) - $value = (int) $value; - }); - - return $input; - } +if (!function_exists('array_ints_keys')) { + + /** + * Cast provided keys's values into ints. This is to wrestle with PDO driver + * inconsistencies. + * + * @param array $input + * @param mixed $keys + * @return array + */ + function array_ints_keys(array $input, $keys = 'id') + { + $keys = is_string($keys) ? array($keys) : $keys; + + array_walk_recursive($input, function (&$value, $key) use ($keys) { + if (array_search($key, $keys) !== false) + $value = (int)$value; + }); + + return $input; + } }