Browse Source

Merge pull request #5864 from cakephp/3.0-tree-behavior-move-down

Accurately set the lft and rght fields for moveDown
Andy Dawson 11 years ago
parent
commit
a939a4e3e1
2 changed files with 604 additions and 222 deletions
  1. 70 65
      src/ORM/Behavior/TreeBehavior.php
  2. 534 157
      tests/TestCase/ORM/Behavior/TreeBehaviorTest.php

+ 70 - 65
src/ORM/Behavior/TreeBehavior.php

@@ -541,6 +541,10 @@ class TreeBehavior extends Behavior
      */
     public function moveUp(Entity $node, $number = 1)
     {
+        if ($number < 1) {
+            return false;
+        }
+
         return $this->_table->connection()->transactional(function () use ($node, $number) {
             $this->_ensureFields($node);
             return $this->_moveUp($node, $number);
@@ -559,49 +563,49 @@ class TreeBehavior extends Behavior
     {
         $config = $this->config();
         list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
+        list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right]));
 
-        if (!$number) {
-            return false;
-        }
-
-        $parentLeft = 0;
-        if ($node->get($parent)) {
-            $parentLeft = $this->_getNode($node->get($parent))->get($left);
+        $targetNode = null;
+        if ($number !== true) {
+            $targetNode = $this->_scope($this->_table->find())
+                ->select([$left, $right])
+                ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft])
+                ->order([$left => 'DESC'])
+                ->offset($number - 1)
+                ->limit(1)
+                ->first();
         }
-
-        $edge = $this->_getMax();
-        while ($number-- > 0) {
-            list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right]));
-
-            if ($parentLeft && ($nodeLeft - 1 == $parentLeft)) {
-                break;
-            }
-
-            $nextNode = $this->_scope($this->_table->find())
+        if (!$targetNode) {
+            $targetNode = $this->_scope($this->_table->find())
                 ->select([$left, $right])
-                ->where([$right => ($nodeLeft - 1)])
+                ->where(["$parent IS" => $nodeParent, "$right <" => $nodeLeft])
+                ->order([$left => 'ASC'])
+                ->limit(1)
                 ->first();
 
-            if (!$nextNode) {
-                break;
+            if (!$targetNode) {
+                return $node;
             }
+        }
 
-            $this->_sync($edge - $nextNode->{$left} + 1, '+', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}");
-            $this->_sync($nodeLeft - $nextNode->{$left}, '-', "BETWEEN {$nodeLeft} AND {$nodeRight}");
-            $this->_sync($edge - $nextNode->{$left} - ($nodeRight - $nodeLeft), '-', "> {$edge}");
+        list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right]));
+        $edge = $this->_getMax();
+        $leftBoundary = $targetLeft;
+        $rightBoundary = $nodeLeft - 1;
 
-            $newLeft = $nodeLeft;
-            if ($nodeLeft >= $nextNode->{$left} || $nodeLeft <= $nextNode->{$right}) {
-                $newLeft -= $edge - $nextNode->{$left} + 1;
-            }
-            $newLeft = $nodeLeft - ($nodeLeft - $nextNode->{$left});
+        $nodeToEdge = $edge - $nodeLeft + 1;
+        $shift = $nodeRight - $nodeLeft + 1;
+        $nodeToHole = $edge - $leftBoundary + 1;
+        $this->_sync($nodeToEdge, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}");
+        $this->_sync($shift, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}");
+        $this->_sync($nodeToHole, '-', "> {$edge}");
 
-            $node->set($left, $newLeft);
-            $node->set($right, $newLeft + ($nodeRight - $nodeLeft));
-        }
+        $node->set($left, $targetLeft);
+        $node->set($right, $targetLeft + ($nodeRight - $nodeLeft));
 
         $node->dirty($left, false);
         $node->dirty($right, false);
+
         return $node;
     }
 
@@ -618,6 +622,10 @@ class TreeBehavior extends Behavior
      */
     public function moveDown(Entity $node, $number = 1)
     {
+        if ($number < 1) {
+            return false;
+        }
+
         return $this->_table->connection()->transactional(function () use ($node, $number) {
             $this->_ensureFields($node);
             return $this->_moveDown($node, $number);
@@ -637,52 +645,49 @@ class TreeBehavior extends Behavior
         $config = $this->config();
         list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
 
-        if (!$number) {
-            return false;
-        }
+        list($nodeParent, $nodeLeft, $nodeRight) = array_values($node->extract([$parent, $left, $right]));
 
-        $parentRight = 0;
-        if ($node->get($parent)) {
-            $parentRight = $this->_getNode($node->get($parent))->get($right);
-        }
-
-        if ($number === true) {
-            $number = PHP_INT_MAX;
+        $targetNode = null;
+        if ($number !== true) {
+            $targetNode = $this->_scope($this->_table->find())
+                ->select([$left, $right])
+                ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight])
+                ->order([$left => 'ASC'])
+                ->offset($number - 1)
+                ->limit(1)
+                ->first();
         }
-
-        $edge = $this->_getMax();
-        while ($number-- > 0) {
-            list($nodeLeft, $nodeRight) = array_values($node->extract([$left, $right]));
-
-            if ($parentRight && ($nodeRight + 1 == $parentRight)) {
-                break;
-            }
-
-            $nextNode = $this->_scope($this->_table->find())
+        if (!$targetNode) {
+            $targetNode = $this->_scope($this->_table->find())
                 ->select([$left, $right])
-                ->where([$left => $nodeRight + 1])
+                ->where(["$parent IS" => $nodeParent, "$left >" => $nodeRight])
+                ->order([$left => 'DESC'])
+                ->limit(1)
                 ->first();
 
-            if (!$nextNode) {
-                break;
+            if (!$targetNode) {
+                return $node;
             }
+        }
 
-            $this->_sync($edge - $nodeLeft + 1, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}");
-            $this->_sync($nextNode->{$left} - $nodeLeft, '-', "BETWEEN {$nextNode->{$left}} AND {$nextNode->{$right}}");
-            $this->_sync($edge - $nodeLeft - ($nextNode->{$right} - $nextNode->{$left}), '-', "> {$edge}");
+        list($targetLeft, $targetRight) = array_values($targetNode->extract([$left, $right]));
+        $edge = $this->_getMax();
+        $leftBoundary = $nodeRight + 1;
+        $rightBoundary = $targetRight;
 
-            $newLeft = $edge + 1;
-            if ($newLeft >= $nextNode->{$left} || $newLeft <= $nextNode->{$right}) {
-                $newLeft -= $nextNode->{$left} - $nodeLeft;
-            }
-            $newLeft -= $nextNode->{$right} - $nextNode->{$left} - 1;
+        $nodeToEdge = $edge - $nodeLeft + 1;
+        $shift = $nodeRight - $nodeLeft + 1;
+        $nodeToHole = $edge - $rightBoundary + $shift;
+        $this->_sync($nodeToEdge, '+', "BETWEEN {$nodeLeft} AND {$nodeRight}");
+        $this->_sync($shift, '-', "BETWEEN {$leftBoundary} AND {$rightBoundary}");
+        $this->_sync($nodeToHole, '-', "> {$edge}");
 
-            $node->set($left, $newLeft);
-            $node->set($right, $newLeft + ($nodeRight - $nodeLeft));
-        }
+        $node->set($left, $targetRight - ($nodeRight - $nodeLeft));
+        $node->set($right, $targetRight);
 
         $node->dirty($left, false);
         $node->dirty($right, false);
+
         return $node;
     }
 

+ 534 - 157
tests/TestCase/ORM/Behavior/TreeBehaviorTest.php

@@ -52,6 +52,63 @@ class TreeBehaviorTest extends TestCase
     }
 
     /**
+     * Sanity test
+     *
+     * Make sure the assert method acts as you'd expect, this is the expected
+     * initial db state
+     *
+     * @return void
+     */
+    public function testAssertMpttValues()
+    {
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:19 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '__17:18 - 10:radios',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
+
+        $table = TableRegistry::get('MenuLinkTrees');
+        $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
+
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
+
+        $table->removeBehavior('Tree');
+        $table->addBehavior('Tree', ['scope' => ['menu' => 'categories']]);
+        $expected = [
+            ' 1:10 -  9:electronics',
+            '_ 2: 9 - 10:televisions',
+            '__ 3: 4 - 11:tube',
+            '__ 5: 8 - 12:lcd',
+            '___ 6: 7 - 13:plasma',
+            '11:20 - 14:portable',
+            '_12:15 - 15:mp3',
+            '__13:14 - 16:flash',
+            '_16:17 - 17:cd',
+            '_18:19 - 18:radios'
+        ];
+        $this->assertMpttValues($expected, $table);
+    }
+
+    /**
      * Tests the find('path') method
      *
      * @return void
@@ -217,33 +274,31 @@ class TreeBehaviorTest extends TestCase
 
         // moveUp
         $table->moveUp($table->get(3), 1);
-        $result = $table->find('treeList')->toArray();
         $expected = [
-            1 => 'Link 1',
-            3 => '_Link 3',
-            4 => '__Link 4',
-            5 => '___Link 5',
-            2 => '_Link 2',
-            6 => 'Link 6',
-            7 => '_Link 7',
-            8 => 'Link 8'
+            ' 1:10 -  1:Link 1',
+            '_ 2: 7 -  3:Link 3',
+            '__ 3: 6 -  4:Link 4',
+            '___ 4: 5 -  5:Link 5',
+            '_ 8: 9 -  2:Link 2',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
         ];
-        $this->assertSame($expected, $result);
+        $this->assertMpttValues($expected, $table);
 
         // moveDown
         $table->moveDown($table->get(6), 1);
-        $result2 = $table->find('treeList')->toArray();
-        $expected2 = [
-            1 => 'Link 1',
-            3 => '_Link 3',
-            4 => '__Link 4',
-            5 => '___Link 5',
-            2 => '_Link 2',
-            8 => 'Link 8',
-            6 => 'Link 6',
-            7 => '_Link 7',
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 7 -  3:Link 3',
+            '__ 3: 6 -  4:Link 4',
+            '___ 4: 5 -  5:Link 5',
+            '_ 8: 9 -  2:Link 2',
+            '11:12 -  8:Link 8',
+            '13:16 -  6:Link 6',
+            '_14:15 -  7:Link 7'
         ];
-        $this->assertSame($expected2, $result2);
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -284,17 +339,48 @@ class TreeBehaviorTest extends TestCase
         // top level, won't move
         $node = $this->table->moveUp($table->get(1), 10);
         $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
 
         // edge cases
         $this->assertFalse($this->table->moveUp($table->get(1), 0));
-        $node = $this->table->moveUp($table->get(1), -10);
-        $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
+        $this->assertFalse($this->table->moveUp($table->get(1), -10));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
 
         // move inner node
         $node = $table->moveUp($table->get(3), 1);
         $nodes = $table->find('children', ['for' => 1])->all();
-        $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
         $this->assertEquals(['lft' => 2, 'rght' => 7], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 7 -  3:Link 3',
+            '__ 3: 6 -  4:Link 4',
+            '___ 4: 5 -  5:Link 5',
+            '_ 8: 9 -  2:Link 2',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -308,6 +394,17 @@ class TreeBehaviorTest extends TestCase
         $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
         $node = $table->moveUp($table->get(5), 1);
         $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -320,16 +417,17 @@ class TreeBehaviorTest extends TestCase
         $table = TableRegistry::get('MenuLinkTrees');
         $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
         $node = $table->moveUp($table->get(8), true);
-        $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
-        $nodes = $table->find()
-            ->select(['id'])
-            ->where(function ($exp) {
-                return $exp->isNull('parent_id');
-            })
-            ->where(['menu' => 'main-menu'])
-            ->order(['lft' => 'ASC'])
-            ->all();
-        $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
+        $expected = [
+            ' 1: 2 -  8:Link 8',
+            ' 3:12 -  1:Link 1',
+            '_ 4: 5 -  2:Link 2',
+            '_ 6:11 -  3:Link 3',
+            '__ 7:10 -  4:Link 4',
+            '___ 8: 9 -  5:Link 5',
+            '13:16 -  6:Link 6',
+            '_14:15 -  7:Link 7'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -346,15 +444,17 @@ class TreeBehaviorTest extends TestCase
         $node->unsetProperty('rght');
         $node = $table->moveUp($node, true);
         $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
-        $nodes = $table->find()
-            ->select(['id'])
-            ->where(function ($exp) {
-                return $exp->isNull('parent_id');
-            })
-            ->where(['menu' => 'main-menu'])
-            ->order(['lft' => 'ASC'])
-            ->all();
-        $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
+        $expected = [
+            ' 1: 2 -  8:Link 8',
+            ' 3:12 -  1:Link 1',
+            '_ 4: 5 -  2:Link 2',
+            '_ 6:11 -  3:Link 3',
+            '__ 7:10 -  4:Link 4',
+            '___ 8: 9 -  5:Link 5',
+            '13:16 -  6:Link 6',
+            '_14:15 -  7:Link 7'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -367,17 +467,49 @@ class TreeBehaviorTest extends TestCase
         $table = TableRegistry::get('MenuLinkTrees');
         $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
         // latest node, won't move
-        $node = $this->table->moveDown($table->get(8), 10);
-        $this->assertEquals(['lft' => 21, 'rght' => 22], $node->extract(['lft', 'rght']));
+        $node = $table->moveDown($table->get(8), 10);
+        $this->assertEquals(['lft' => 15, 'rght' => 16], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
 
         // edge cases
         $this->assertFalse($this->table->moveDown($table->get(8), 0));
+        $this->assertFalse($this->table->moveDown($table->get(8), -10));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
 
         // move inner node
         $node = $table->moveDown($table->get(2), 1);
-        $nodes = $table->find('children', ['for' => 1])->all();
-        $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
-        $this->assertEquals(['lft' => 11, 'rght' => 12], $node->extract(['lft', 'rght']));
+        $this->assertEquals(['lft' => 8, 'rght' => 9], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 7 -  3:Link 3',
+            '__ 3: 6 -  4:Link 4',
+            '___ 4: 5 -  5:Link 5',
+            '_ 8: 9 -  2:Link 2',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -391,6 +523,17 @@ class TreeBehaviorTest extends TestCase
         $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
         $node = $table->moveDown($table->get(5), 1);
         $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -404,15 +547,17 @@ class TreeBehaviorTest extends TestCase
         $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
         $node = $table->moveDown($table->get(1), true);
         $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
-        $nodes = $table->find()
-            ->select(['id'])
-            ->where(function ($exp) {
-                return $exp->isNull('parent_id');
-            })
-            ->where(['menu' => 'main-menu'])
-            ->order(['lft' => 'ASC'])
-            ->all();
-        $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
+        $expected = [
+            ' 1: 4 -  6:Link 6',
+            '_ 2: 3 -  7:Link 7',
+            ' 5: 6 -  8:Link 8',
+            ' 7:16 -  1:Link 1',
+            '_ 8: 9 -  2:Link 2',
+            '_10:15 -  3:Link 3',
+            '__11:14 -  4:Link 4',
+            '___12:13 -  5:Link 5'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -429,15 +574,37 @@ class TreeBehaviorTest extends TestCase
         $node->unsetProperty('rght');
         $node = $table->moveDown($node, true);
         $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
-        $nodes = $table->find()
-            ->select(['id'])
-            ->where(function ($exp) {
-                return $exp->isNull('parent_id');
-            })
-            ->where(['menu' => 'main-menu'])
-            ->order(['lft' => 'ASC'])
-            ->all();
-        $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
+        $expected = [
+            ' 1: 4 -  6:Link 6',
+            '_ 2: 3 -  7:Link 7',
+            ' 5: 6 -  8:Link 8',
+            ' 7:16 -  1:Link 1',
+            '_ 8: 9 -  2:Link 2',
+            '_10:15 -  3:Link 3',
+            '__11:14 -  4:Link 4',
+            '___12:13 -  5:Link 5'
+        ];
+        $this->assertMpttValues($expected, $table);
+    }
+
+    public function testMoveDownMultiplePositions()
+    {
+        $node = $this->table->moveDown($this->table->get(3), 2);
+        $this->assertEquals(['lft' => 7, 'rght' => 8], $node->extract(['lft', 'rght']));
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  4:lcd',
+            '__ 5: 6 -  5:plasma',
+            '__ 7: 8 -  3:tube',
+            '_10:19 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '__17:18 - 10:radios',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -448,11 +615,23 @@ class TreeBehaviorTest extends TestCase
     public function testRecover()
     {
         $table = $this->table;
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
         $table->updateAll(['lft' => null, 'rght' => null], []);
         $table->recover();
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:19 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '__17:18 - 10:radios',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -470,27 +649,37 @@ class TreeBehaviorTest extends TestCase
             ->hydrate(false)
             ->toArray();
 
-        $expected2 = $table->find()
-            ->where(['menu' => 'categories'])
-            ->order('lft')
-            ->hydrate(false)
-            ->toArray();
 
         $table->updateAll(['lft' => null, 'rght' => null], ['menu' => 'main-menu']);
         $table->recover();
-        $result = $table->find()
-            ->where(['menu' => 'main-menu'])
-            ->order('lft')
-            ->hydrate(false)
-            ->toArray();
-        $this->assertEquals($expected, $result);
 
-        $result2 = $table->find()
-            ->where(['menu' => 'categories'])
-            ->order('lft')
-            ->hydrate(false)
-            ->toArray();
-        $this->assertEquals($expected2, $result2);
+        $expected = [
+            ' 1:10 -  1:Link 1',
+            '_ 2: 3 -  2:Link 2',
+            '_ 4: 9 -  3:Link 3',
+            '__ 5: 8 -  4:Link 4',
+            '___ 6: 7 -  5:Link 5',
+            '11:14 -  6:Link 6',
+            '_12:13 -  7:Link 7',
+            '15:16 -  8:Link 8'
+        ];
+        $this->assertMpttValues($expected, $table);
+
+        $table->removeBehavior('Tree');
+        $table->addBehavior('Tree', ['scope' => ['menu' => 'categories']]);
+        $expected = [
+            ' 1:10 -  9:electronics',
+            '_ 2: 9 - 10:televisions',
+            '__ 3: 4 - 11:tube',
+            '__ 5: 8 - 12:lcd',
+            '___ 6: 7 - 13:plasma',
+            '11:20 - 14:portable',
+            '_12:15 - 15:mp3',
+            '__13:14 - 16:flash',
+            '_16:17 - 17:cd',
+            '_18:19 - 18:radios'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -505,14 +694,25 @@ class TreeBehaviorTest extends TestCase
             ['name' => 'New Orphan', 'parent_id' => null, 'level' => null],
             ['markNew' => true]
         );
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
         $this->assertSame($entity, $table->save($entity));
         $this->assertEquals(23, $entity->lft);
         $this->assertEquals(24, $entity->rght);
 
-        $expected[] = $entity->toArray();
-        $results = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $results);
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:19 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '__17:18 - 10:radios',
+            '21:22 - 11:alien hardware',
+            '23:24 - 12:New Orphan',
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -531,10 +731,21 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(20, $entity->lft);
         $this->assertEquals(21, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+        $expected = [
+            ' 1:22 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:19 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '__17:18 - 10:radios',
+            '_20:21 - 12:laptops',
+            '23:24 - 11:alien hardware',
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -553,10 +764,21 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(9, $entity->lft);
         $this->assertEquals(10, $entity->rght);
 
-        $results = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $results);
+        $expected = [
+            ' 1:22 -  1:electronics',
+            '_ 2:11 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '__ 9:10 - 12:laptops',
+            '_12:21 -  6:portable',
+            '__13:16 -  7:mp3',
+            '___14:15 -  8:flash',
+            '__17:18 -  9:cd',
+            '__19:20 - 10:radios',
+            '23:24 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -573,9 +795,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(11, $entity->lft);
         $this->assertEquals(18, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 6, 7, 8, 9, 10, 2, 3, 4, 5, 11];
-        $this->assertTreeNumbers($expected, $table);
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2:19 -  6:portable',
+            '__ 3: 6 -  7:mp3',
+            '___ 4: 5 -  8:flash',
+            '__ 7: 8 -  9:cd',
+            '__ 9:10 - 10:radios',
+            '__11:18 -  2:televisions',
+            '___12:13 -  3:tube',
+            '___14:15 -  4:lcd',
+            '___16:17 -  5:plasma',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -592,10 +825,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(9, $entity->lft);
         $this->assertEquals(18, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2:19 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '__ 9:18 -  6:portable',
+            '___10:13 -  7:mp3',
+            '____11:12 -  8:flash',
+            '___14:15 -  9:cd',
+            '___16:17 - 10:radios',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -612,10 +855,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(9, $entity->lft);
         $this->assertEquals(10, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2:11 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '__ 9:10 - 10:radios',
+            '_12:19 -  6:portable',
+            '__13:16 -  7:mp3',
+            '___14:15 -  8:flash',
+            '__17:18 -  9:cd',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -633,8 +886,21 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(18, $entity->rght);
 
         $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 2, 3, 4, 6, 7, 8, 9, 10, 5, 11];
-        $this->assertTreeNumbers($expected, $table);
+
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2: 7 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '_ 8:19 -  6:portable',
+            '__ 9:12 -  7:mp3',
+            '___10:11 -  8:flash',
+            '__13:14 -  9:cd',
+            '__15:16 - 10:radios',
+            '__17:18 -  5:plasma',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -653,10 +919,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(9, $entity->lft);
         $this->assertEquals(18, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+        $expected = [
+            ' 1:20 -  1:electronics',
+            '_ 2:19 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '__ 9:18 -  6:portable',
+            '___10:13 -  7:mp3',
+            '____11:12 -  8:flash',
+            '___14:15 -  9:cd',
+            '___16:17 - 10:radios',
+            '21:22 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -673,9 +949,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(15, $entity->lft);
         $this->assertEquals(22, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
-        $this->assertTreeNumbers($expected, $table);
+        $expected = [
+            ' 1:12 -  1:electronics',
+            '_ 2:11 -  6:portable',
+            '__ 3: 6 -  7:mp3',
+            '___ 4: 5 -  8:flash',
+            '__ 7: 8 -  9:cd',
+            '__ 9:10 - 10:radios',
+            '13:14 - 11:alien hardware',
+            '15:22 -  2:televisions',
+            '_16:17 -  3:tube',
+            '_18:19 -  4:lcd',
+            '_20:21 -  5:plasma'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -694,9 +981,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(15, $entity->lft);
         $this->assertEquals(22, $entity->rght);
 
-        $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
-        $this->assertTreeNumbers($expected, $table);
+        $expected = [
+            ' 1:12 -  1:electronics',
+            '_ 2:11 -  6:portable',
+            '__ 3: 6 -  7:mp3',
+            '___ 4: 5 -  8:flash',
+            '__ 7: 8 -  9:cd',
+            '__ 9:10 - 10:radios',
+            '13:14 - 11:alien hardware',
+            '15:22 -  2:televisions',
+            '_16:17 -  3:tube',
+            '_18:19 -  4:lcd',
+            '_20:21 -  5:plasma'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -724,10 +1022,20 @@ class TreeBehaviorTest extends TestCase
         $table = $this->table;
         $entity = $table->get(4);
         $this->assertTrue($table->delete($entity));
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+
+        $expected = [
+            ' 1:18 -  1:electronics',
+            '_ 2: 7 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  5:plasma',
+            '_ 8:17 -  6:portable',
+            '__ 9:12 -  7:mp3',
+            '___10:11 -  8:flash',
+            '__13:14 -  9:cd',
+            '__15:16 - 10:radios',
+            '19:20 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -740,10 +1048,16 @@ class TreeBehaviorTest extends TestCase
         $table = $this->table;
         $entity = $table->get(6);
         $this->assertTrue($table->delete($entity));
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+
+        $expected = [
+            ' 1:10 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '11:12 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -756,10 +1070,11 @@ class TreeBehaviorTest extends TestCase
         $table = $this->table;
         $entity = $table->get(1);
         $this->assertTrue($table->delete($entity));
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+
+        $expected = [
+            ' 1: 2 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -774,10 +1089,11 @@ class TreeBehaviorTest extends TestCase
         $entity->unsetProperty('lft');
         $entity->unsetProperty('rght');
         $this->assertTrue($table->delete($entity));
-        $result = $table->find()->order('lft')->hydrate(false)->toArray();
-        $table->recover();
-        $expected = $table->find()->order('lft')->hydrate(false)->toArray();
-        $this->assertEquals($expected, $result);
+
+        $expected = [
+            ' 1: 2 - 11:alien hardware'
+        ];
+        $this->assertMpttValues($expected, $this->table);
     }
 
     /**
@@ -794,8 +1110,21 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(22, $entity->rght);
         $this->assertEquals(null, $entity->parent_id);
         $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10];
-        $this->assertTreeNumbers($expected, $table);
+        $expected = [
+            ' 1:18 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:17 -  6:portable',
+            '__11:14 -  7:mp3',
+            '___12:13 -  8:flash',
+            '__15:16 -  9:cd',
+            '19:20 - 11:alien hardware',
+            '21:22 - 10:radios'
+
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -813,8 +1142,20 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(22, $entity->rght);
         $this->assertEquals(null, $entity->parent_id);
         $result = $table->find()->order('lft')->hydrate(false);
-        $expected = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 6];
-        $this->assertTreeNumbers($expected, $table);
+        $expected = [
+            ' 1:18 -  1:electronics',
+            '_ 2: 9 -  2:televisions',
+            '__ 3: 4 -  3:tube',
+            '__ 5: 6 -  4:lcd',
+            '__ 7: 8 -  5:plasma',
+            '_10:13 -  7:mp3',
+            '__11:12 -  8:flash',
+            '_14:15 -  9:cd',
+            '_16:17 - 10:radios',
+            '19:20 - 11:alien hardware',
+            '21:22 -  6:portable'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -831,8 +1172,21 @@ class TreeBehaviorTest extends TestCase
         $this->assertEquals(21, $entity->lft);
         $this->assertEquals(22, $entity->rght);
         $this->assertEquals(null, $entity->parent_id);
-        $expected = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1];
-        $this->assertTreeNumbers($expected, $table);
+
+        $expected = [
+            ' 1: 8 -  2:televisions',
+            '_ 2: 3 -  3:tube',
+            '_ 4: 5 -  4:lcd',
+            '_ 6: 7 -  5:plasma',
+            ' 9:18 -  6:portable',
+            '_10:13 -  7:mp3',
+            '__11:12 -  8:flash',
+            '_14:15 -  9:cd',
+            '_16:17 - 10:radios',
+            '19:20 - 11:alien hardware',
+            '21:22 -  1:electronics'
+        ];
+        $this->assertMpttValues($expected, $table);
     }
 
     /**
@@ -936,23 +1290,46 @@ class TreeBehaviorTest extends TestCase
     }
 
     /**
-     * Custom assertion use to verify tha a tree is returned in the expected order
-     * and that it is still valid
+     * Assert MPTT values
      *
-     * @param array $expected The list of ids in the order they are expected
-     * @param \Cake\ORM\Table the table instance to use for comparing
+     * Custom assert method to make identifying the differences between expected
+     * and actual db state easier to identify.
+     *
+     * @param array $expected tree state to be expected
+     * @param \Cake\ORM\Table $table Table instance
+     * @param \Cake\ORM\Query $query Optional query object
      * @return void
      */
-    public function assertTreeNumbers($expected, $table)
+    public function assertMpttValues($expected, $table, $query = null)
     {
-        $result = $table->find()->order('lft')->hydrate(false);
-        $this->assertEquals($expected, $result->extract('id')->toArray());
-        $numbers = [];
-        $result->each(function ($v) use (&$numbers) {
-            $numbers[] = $v['lft'];
-            $numbers[] = $v['rght'];
-        });
-        sort($numbers);
-        $this->assertEquals(range(1, 22), $numbers);
+        $query = $query ?: $table->find();
+        $primaryKey = $table->primaryKey();
+        if (is_array($primaryKey)) {
+            $primaryKey = $primaryKey[0];
+        }
+        $displayField = $table->displayField();
+
+        $options = [
+            'valuePath' => function ($item, $key, $iterator) use ($primaryKey, $displayField) {
+                return sprintf(
+                    '%s:%s - %s:%s',
+                    str_pad($item->lft, 2, ' ', STR_PAD_LEFT),
+                    str_pad($item->rght, 2, ' ', STR_PAD_LEFT),
+                    str_pad($item->$primaryKey, 2, ' ', STR_PAD_LEFT),
+                    $item->{$displayField}
+                );
+            }
+        ];
+        $result = array_values($query->find('treeList', $options)->toArray());
+
+        if (count($result) === count($expected)) {
+            $subExpected = array_diff($expected, $result);
+            if ($subExpected) {
+                $subResult = array_intersect_key($result, $subExpected);
+                $this->assertSame($subExpected, $subResult, 'Differences in the tree were found (lft:rght id:display-name)');
+            }
+        }
+
+        $this->assertSame($expected, $result, 'The tree is not the same (lft:rght id:display-name)');
     }
 }