TreeBehaviorTest.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM\Behavior;
  16. use Cake\Collection\Collection;
  17. use Cake\Event\Event;
  18. use Cake\ORM\Behavior\TranslateBehavior;
  19. use Cake\ORM\Entity;
  20. use Cake\ORM\TableRegistry;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * Translate behavior test case
  24. */
  25. class TreeBehaviorTest extends TestCase
  26. {
  27. /**
  28. * fixtures
  29. *
  30. * @var array
  31. */
  32. public $fixtures = [
  33. 'core.number_trees',
  34. 'core.menu_link_trees'
  35. ];
  36. public function setUp()
  37. {
  38. parent::setUp();
  39. $this->table = TableRegistry::get('NumberTrees');
  40. $this->table->primaryKey(['id']);
  41. $this->table->addBehavior('Tree');
  42. }
  43. public function tearDown()
  44. {
  45. parent::tearDown();
  46. TableRegistry::clear();
  47. }
  48. /**
  49. * Tests the find('path') method
  50. *
  51. * @return void
  52. */
  53. public function testFindPath()
  54. {
  55. $nodes = $this->table->find('path', ['for' => 9]);
  56. $this->assertEquals([1, 6, 9], $nodes->extract('id')->toArray());
  57. $nodes = $this->table->find('path', ['for' => 10]);
  58. $this->assertEquals([1, 6, 10], $nodes->extract('id')->toArray());
  59. $nodes = $this->table->find('path', ['for' => 5]);
  60. $this->assertEquals([1, 2, 5], $nodes->extract('id')->toArray());
  61. $nodes = $this->table->find('path', ['for' => 1]);
  62. $this->assertEquals([1], $nodes->extract('id')->toArray());
  63. // find path with scope
  64. $table = TableRegistry::get('MenuLinkTrees');
  65. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  66. $nodes = $table->find('path', ['for' => 5]);
  67. $this->assertEquals([1, 3, 4, 5], $nodes->extract('id')->toArray());
  68. }
  69. /**
  70. * Tests the childCount() method
  71. *
  72. * @return void
  73. */
  74. public function testChildCount()
  75. {
  76. // direct children for the root node
  77. $table = $this->table;
  78. $countDirect = $this->table->childCount($table->get(1), true);
  79. $this->assertEquals(2, $countDirect);
  80. // counts all the children of root
  81. $count = $this->table->childCount($table->get(1), false);
  82. $this->assertEquals(9, $count);
  83. // counts direct children
  84. $count = $this->table->childCount($table->get(2), false);
  85. $this->assertEquals(3, $count);
  86. // count children for a middle-node
  87. $count = $this->table->childCount($table->get(6), false);
  88. $this->assertEquals(4, $count);
  89. // count leaf children
  90. $count = $this->table->childCount($table->get(10), false);
  91. $this->assertEquals(0, $count);
  92. // test scoping
  93. $table = TableRegistry::get('MenuLinkTrees');
  94. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  95. $count = $table->childCount($table->get(3), false);
  96. $this->assertEquals(2, $count);
  97. }
  98. /**
  99. * Tests that childCount will provide the correct lft and rght values
  100. *
  101. * @return void
  102. */
  103. public function testChildCountNoTreeColumns()
  104. {
  105. $table = $this->table;
  106. $node = $table->get(6);
  107. $node->unsetProperty('lft');
  108. $node->unsetProperty('rght');
  109. $count = $this->table->childCount($node, false);
  110. $this->assertEquals(4, $count);
  111. }
  112. /**
  113. * Tests the childCount() plus callable scoping
  114. *
  115. * @return void
  116. */
  117. public function testCallableScoping()
  118. {
  119. $table = TableRegistry::get('MenuLinkTrees');
  120. $table->addBehavior('Tree', [
  121. 'scope' => function ($query) {
  122. return $query->where(['menu' => 'main-menu']);
  123. }
  124. ]);
  125. $count = $table->childCount($table->get(1), false);
  126. $this->assertEquals(4, $count);
  127. }
  128. /**
  129. * Tests the find('children') method
  130. *
  131. * @return void
  132. */
  133. public function testFindChildren()
  134. {
  135. $table = TableRegistry::get('MenuLinkTrees');
  136. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  137. // root
  138. $nodeIds = [];
  139. $nodes = $table->find('children', ['for' => 1])->all();
  140. $this->assertEquals([2, 3, 4, 5], $nodes->extract('id')->toArray());
  141. // leaf
  142. $nodeIds = [];
  143. $nodes = $table->find('children', ['for' => 5])->all();
  144. $this->assertEquals(0, count($nodes->extract('id')->toArray()));
  145. // direct children
  146. $nodes = $table->find('children', ['for' => 1, 'direct' => true])->all();
  147. $this->assertEquals([2, 3], $nodes->extract('id')->toArray());
  148. }
  149. /**
  150. * Tests that find('children') will throw an exception if the node was not found
  151. *
  152. * @expectedException \Cake\Datasource\Exception\RecordNotFoundException
  153. * @return void
  154. */
  155. public function testFindChildrenException()
  156. {
  157. $table = TableRegistry::get('MenuLinkTrees');
  158. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  159. $query = $table->find('children', ['for' => 500]);
  160. }
  161. /**
  162. * Tests the find('treeList') method
  163. *
  164. * @return void
  165. */
  166. public function testFindTreeList()
  167. {
  168. $table = TableRegistry::get('MenuLinkTrees');
  169. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  170. $result = $table->find('treeList')->toArray();
  171. $expected = [
  172. 1 => 'Link 1',
  173. 2 => '_Link 2',
  174. 3 => '_Link 3',
  175. 4 => '__Link 4',
  176. 5 => '___Link 5',
  177. 6 => 'Link 6',
  178. 7 => '_Link 7',
  179. 8 => 'Link 8'
  180. ];
  181. $this->assertEquals($expected, $result);
  182. }
  183. /**
  184. * Tests the find('treeList') method after moveUp, moveDown
  185. *
  186. * @return void
  187. */
  188. public function testFindTreeListAfterMove()
  189. {
  190. $table = TableRegistry::get('MenuLinkTrees');
  191. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  192. // moveUp
  193. $table->moveUp($table->get(3), 1);
  194. $result = $table->find('treeList')->toArray();
  195. $expected = [
  196. 1 => 'Link 1',
  197. 3 => '_Link 3',
  198. 4 => '__Link 4',
  199. 5 => '___Link 5',
  200. 2 => '_Link 2',
  201. 6 => 'Link 6',
  202. 7 => '_Link 7',
  203. 8 => 'Link 8'
  204. ];
  205. $this->assertSame($expected, $result);
  206. // moveDown
  207. $table->moveDown($table->get(6), 1);
  208. $result2 = $table->find('treeList')->toArray();
  209. $expected2 = [
  210. 1 => 'Link 1',
  211. 3 => '_Link 3',
  212. 4 => '__Link 4',
  213. 5 => '___Link 5',
  214. 2 => '_Link 2',
  215. 8 => 'Link 8',
  216. 6 => 'Link 6',
  217. 7 => '_Link 7',
  218. ];
  219. $this->assertSame($expected2, $result2);
  220. }
  221. /**
  222. * Tests the find('treeList') method with custom options
  223. *
  224. * @return void
  225. */
  226. public function testFindTreeListCustom()
  227. {
  228. $table = TableRegistry::get('MenuLinkTrees');
  229. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  230. $result = $table
  231. ->find('treeList', ['keyPath' => 'url', 'valuePath' => 'id', 'spacer' => ' '])
  232. ->toArray();
  233. $expected = [
  234. '/link1.html' => '1',
  235. 'http://example.com' => ' 2',
  236. '/what/even-more-links.html' => ' 3',
  237. '/lorem/ipsum.html' => ' 4',
  238. '/what/the.html' => ' 5',
  239. '/yeah/another-link.html' => '6',
  240. 'http://cakephp.org' => ' 7',
  241. '/page/who-we-are.html' => '8'
  242. ];
  243. $this->assertEquals($expected, $result);
  244. }
  245. /**
  246. * Tests the moveUp() method
  247. *
  248. * @return void
  249. */
  250. public function testMoveUp()
  251. {
  252. $table = TableRegistry::get('MenuLinkTrees');
  253. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  254. // top level, won't move
  255. $node = $this->table->moveUp($table->get(1), 10);
  256. $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
  257. // edge cases
  258. $this->assertFalse($this->table->moveUp($table->get(1), 0));
  259. $node = $this->table->moveUp($table->get(1), -10);
  260. $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
  261. // move inner node
  262. $node = $table->moveUp($table->get(3), 1);
  263. $nodes = $table->find('children', ['for' => 1])->all();
  264. $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
  265. $this->assertEquals(['lft' => 2, 'rght' => 7], $node->extract(['lft', 'rght']));
  266. }
  267. /**
  268. * Tests moving a node with no siblings
  269. *
  270. * @return void
  271. */
  272. public function testMoveLeaf()
  273. {
  274. $table = TableRegistry::get('MenuLinkTrees');
  275. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  276. $node = $table->moveUp($table->get(5), 1);
  277. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  278. }
  279. /**
  280. * Tests moving a node to the top
  281. *
  282. * @return void
  283. */
  284. public function testMoveTop()
  285. {
  286. $table = TableRegistry::get('MenuLinkTrees');
  287. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  288. $node = $table->moveUp($table->get(8), true);
  289. $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
  290. $nodes = $table->find()
  291. ->select(['id'])
  292. ->where(function ($exp) {
  293. return $exp->isNull('parent_id');
  294. })
  295. ->where(['menu' => 'main-menu'])
  296. ->order(['lft' => 'ASC'])
  297. ->all();
  298. $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
  299. }
  300. /**
  301. * Tests moving a node with no lft and rght
  302. *
  303. * @return void
  304. */
  305. public function testMoveNoTreeColumns()
  306. {
  307. $table = TableRegistry::get('MenuLinkTrees');
  308. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  309. $node = $table->get(8);
  310. $node->unsetProperty('lft');
  311. $node->unsetProperty('rght');
  312. $node = $table->moveUp($node, true);
  313. $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
  314. $nodes = $table->find()
  315. ->select(['id'])
  316. ->where(function ($exp) {
  317. return $exp->isNull('parent_id');
  318. })
  319. ->where(['menu' => 'main-menu'])
  320. ->order(['lft' => 'ASC'])
  321. ->all();
  322. $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
  323. }
  324. /**
  325. * Tests the moveDown() method
  326. *
  327. * @return void
  328. */
  329. public function testMoveDown()
  330. {
  331. $table = TableRegistry::get('MenuLinkTrees');
  332. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  333. // latest node, won't move
  334. $node = $this->table->moveDown($table->get(8), 10);
  335. $this->assertEquals(['lft' => 21, 'rght' => 22], $node->extract(['lft', 'rght']));
  336. // edge cases
  337. $this->assertFalse($this->table->moveDown($table->get(8), 0));
  338. // move inner node
  339. $node = $table->moveDown($table->get(2), 1);
  340. $nodes = $table->find('children', ['for' => 1])->all();
  341. $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
  342. $this->assertEquals(['lft' => 11, 'rght' => 12], $node->extract(['lft', 'rght']));
  343. }
  344. /**
  345. * Tests moving a node that has no siblings
  346. *
  347. * @return void
  348. */
  349. public function testMoveLeafDown()
  350. {
  351. $table = TableRegistry::get('MenuLinkTrees');
  352. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  353. $node = $table->moveDown($table->get(5), 1);
  354. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  355. }
  356. /**
  357. * Tests moving a node to the bottom
  358. *
  359. * @return void
  360. */
  361. public function testMoveToBottom()
  362. {
  363. $table = TableRegistry::get('MenuLinkTrees');
  364. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  365. $node = $table->moveDown($table->get(1), true);
  366. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  367. $nodes = $table->find()
  368. ->select(['id'])
  369. ->where(function ($exp) {
  370. return $exp->isNull('parent_id');
  371. })
  372. ->where(['menu' => 'main-menu'])
  373. ->order(['lft' => 'ASC'])
  374. ->all();
  375. $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
  376. }
  377. /**
  378. * Tests moving a node with no lft and rght columns
  379. *
  380. * @return void
  381. */
  382. public function testMoveDownNoTreeColumns()
  383. {
  384. $table = TableRegistry::get('MenuLinkTrees');
  385. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  386. $node = $table->get(1);
  387. $node->unsetProperty('lft');
  388. $node->unsetProperty('rght');
  389. $node = $table->moveDown($node, true);
  390. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  391. $nodes = $table->find()
  392. ->select(['id'])
  393. ->where(function ($exp) {
  394. return $exp->isNull('parent_id');
  395. })
  396. ->where(['menu' => 'main-menu'])
  397. ->order(['lft' => 'ASC'])
  398. ->all();
  399. $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
  400. }
  401. /**
  402. * Tests the recover function
  403. *
  404. * @return void
  405. */
  406. public function testRecover()
  407. {
  408. $table = $this->table;
  409. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  410. $table->updateAll(['lft' => null, 'rght' => null], []);
  411. $table->recover();
  412. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  413. $this->assertEquals($expected, $result);
  414. }
  415. /**
  416. * Tests the recover function with a custom scope
  417. *
  418. * @return void
  419. */
  420. public function testRecoverScoped()
  421. {
  422. $table = TableRegistry::get('MenuLinkTrees');
  423. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  424. $expected = $table->find()
  425. ->where(['menu' => 'main-menu'])
  426. ->order('lft')
  427. ->hydrate(false)
  428. ->toArray();
  429. $expected2 = $table->find()
  430. ->where(['menu' => 'categories'])
  431. ->order('lft')
  432. ->hydrate(false)
  433. ->toArray();
  434. $table->updateAll(['lft' => null, 'rght' => null], ['menu' => 'main-menu']);
  435. $table->recover();
  436. $result = $table->find()
  437. ->where(['menu' => 'main-menu'])
  438. ->order('lft')
  439. ->hydrate(false)
  440. ->toArray();
  441. $this->assertEquals($expected, $result);
  442. $result2 = $table->find()
  443. ->where(['menu' => 'categories'])
  444. ->order('lft')
  445. ->hydrate(false)
  446. ->toArray();
  447. $this->assertEquals($expected2, $result2);
  448. }
  449. /**
  450. * Tests adding a new orphan node
  451. *
  452. * @return void
  453. */
  454. public function testAddOrphan()
  455. {
  456. $table = $this->table;
  457. $entity = new Entity(
  458. ['name' => 'New Orphan', 'parent_id' => null, 'level' => null],
  459. ['markNew' => true]
  460. );
  461. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  462. $this->assertSame($entity, $table->save($entity));
  463. $this->assertEquals(23, $entity->lft);
  464. $this->assertEquals(24, $entity->rght);
  465. $expected[] = $entity->toArray();
  466. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  467. $this->assertEquals($expected, $results);
  468. }
  469. /**
  470. * Tests that adding a child node as a decendant of one of the roots works
  471. *
  472. * @return void
  473. */
  474. public function testAddMiddle()
  475. {
  476. $table = $this->table;
  477. $entity = new Entity(
  478. ['name' => 'laptops', 'parent_id' => 1],
  479. ['markNew' => true]
  480. );
  481. $this->assertSame($entity, $table->save($entity));
  482. $this->assertEquals(20, $entity->lft);
  483. $this->assertEquals(21, $entity->rght);
  484. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  485. $table->recover();
  486. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  487. $this->assertEquals($expected, $result);
  488. }
  489. /**
  490. * Tests adding a leaf to the tree
  491. *
  492. * @return void
  493. */
  494. public function testAddLeaf()
  495. {
  496. $table = $this->table;
  497. $entity = new Entity(
  498. ['name' => 'laptops', 'parent_id' => 2],
  499. ['markNew' => true]
  500. );
  501. $this->assertSame($entity, $table->save($entity));
  502. $this->assertEquals(9, $entity->lft);
  503. $this->assertEquals(10, $entity->rght);
  504. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  505. $table->recover();
  506. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  507. $this->assertEquals($expected, $results);
  508. }
  509. /**
  510. * Tests moving a subtree to the right
  511. *
  512. * @return void
  513. */
  514. public function testReParentSubTreeRight()
  515. {
  516. $table = $this->table;
  517. $entity = $table->get(2);
  518. $entity->parent_id = 6;
  519. $this->assertSame($entity, $table->save($entity));
  520. $this->assertEquals(11, $entity->lft);
  521. $this->assertEquals(18, $entity->rght);
  522. $result = $table->find()->order('lft')->hydrate(false);
  523. $expected = [1, 6, 7, 8, 9, 10, 2, 3, 4, 5, 11];
  524. $this->assertTreeNumbers($expected, $table);
  525. }
  526. /**
  527. * Tests moving a subtree to the left
  528. *
  529. * @return void
  530. */
  531. public function testReParentSubTreeLeft()
  532. {
  533. $table = $this->table;
  534. $entity = $table->get(6);
  535. $entity->parent_id = 2;
  536. $this->assertSame($entity, $table->save($entity));
  537. $this->assertEquals(9, $entity->lft);
  538. $this->assertEquals(18, $entity->rght);
  539. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  540. $table->recover();
  541. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  542. $this->assertEquals($expected, $result);
  543. }
  544. /**
  545. * Test moving a leaft to the left
  546. *
  547. * @return void
  548. */
  549. public function testReParentLeafLeft()
  550. {
  551. $table = $this->table;
  552. $entity = $table->get(10);
  553. $entity->parent_id = 2;
  554. $this->assertSame($entity, $table->save($entity));
  555. $this->assertEquals(9, $entity->lft);
  556. $this->assertEquals(10, $entity->rght);
  557. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  558. $table->recover();
  559. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  560. $this->assertEquals($expected, $result);
  561. }
  562. /**
  563. * Test moving a leaf to the left
  564. *
  565. * @return void
  566. */
  567. public function testReParentLeafRight()
  568. {
  569. $table = $this->table;
  570. $entity = $table->get(5);
  571. $entity->parent_id = 6;
  572. $this->assertSame($entity, $table->save($entity));
  573. $this->assertEquals(17, $entity->lft);
  574. $this->assertEquals(18, $entity->rght);
  575. $result = $table->find()->order('lft')->hydrate(false);
  576. $expected = [1, 2, 3, 4, 6, 7, 8, 9, 10, 5, 11];
  577. $this->assertTreeNumbers($expected, $table);
  578. }
  579. /**
  580. * Tests moving a subtree with a node having no lft and rght columns
  581. *
  582. * @return void
  583. */
  584. public function testReParentNoTreeColumns()
  585. {
  586. $table = $this->table;
  587. $entity = $table->get(6);
  588. $entity->unsetProperty('lft');
  589. $entity->unsetProperty('rght');
  590. $entity->parent_id = 2;
  591. $this->assertSame($entity, $table->save($entity));
  592. $this->assertEquals(9, $entity->lft);
  593. $this->assertEquals(18, $entity->rght);
  594. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  595. $table->recover();
  596. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  597. $this->assertEquals($expected, $result);
  598. }
  599. /**
  600. * Tests moving a subtree as a new root
  601. *
  602. * @return void
  603. */
  604. public function testRootingSubTree()
  605. {
  606. $table = $this->table;
  607. $entity = $table->get(2);
  608. $entity->parent_id = null;
  609. $this->assertSame($entity, $table->save($entity));
  610. $this->assertEquals(15, $entity->lft);
  611. $this->assertEquals(22, $entity->rght);
  612. $result = $table->find()->order('lft')->hydrate(false);
  613. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  614. $this->assertTreeNumbers($expected, $table);
  615. }
  616. /**
  617. * Tests moving a subtree with no tree columns
  618. *
  619. * @return void
  620. */
  621. public function testRootingNoTreeColumns()
  622. {
  623. $table = $this->table;
  624. $entity = $table->get(2);
  625. $entity->unsetProperty('lft');
  626. $entity->unsetProperty('rght');
  627. $entity->parent_id = null;
  628. $this->assertSame($entity, $table->save($entity));
  629. $this->assertEquals(15, $entity->lft);
  630. $this->assertEquals(22, $entity->rght);
  631. $result = $table->find()->order('lft')->hydrate(false);
  632. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  633. $this->assertTreeNumbers($expected, $table);
  634. }
  635. /**
  636. * Tests that trying to create a cycle throws an exception
  637. *
  638. * @expectedException \RuntimeException
  639. * @expectedExceptionMessage Cannot use node "5" as parent for entity "2"
  640. * @return void
  641. */
  642. public function testReparentCycle()
  643. {
  644. $table = $this->table;
  645. $entity = $table->get(2);
  646. $entity->parent_id = 5;
  647. $table->save($entity);
  648. }
  649. /**
  650. * Tests deleting a leaf in the tree
  651. *
  652. * @return void
  653. */
  654. public function testDeleteLeaf()
  655. {
  656. $table = $this->table;
  657. $entity = $table->get(4);
  658. $this->assertTrue($table->delete($entity));
  659. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  660. $table->recover();
  661. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  662. $this->assertEquals($expected, $result);
  663. }
  664. /**
  665. * Tests deleting a subtree
  666. *
  667. * @return void
  668. */
  669. public function testDeleteSubTree()
  670. {
  671. $table = $this->table;
  672. $entity = $table->get(6);
  673. $this->assertTrue($table->delete($entity));
  674. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  675. $table->recover();
  676. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  677. $this->assertEquals($expected, $result);
  678. }
  679. /**
  680. * Test deleting a root node
  681. *
  682. * @return void
  683. */
  684. public function testDeleteRoot()
  685. {
  686. $table = $this->table;
  687. $entity = $table->get(1);
  688. $this->assertTrue($table->delete($entity));
  689. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  690. $table->recover();
  691. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  692. $this->assertEquals($expected, $result);
  693. }
  694. /**
  695. * Test deleting a node with no tree columns
  696. *
  697. * @return void
  698. */
  699. public function testDeleteRootNoTreeColumns()
  700. {
  701. $table = $this->table;
  702. $entity = $table->get(1);
  703. $entity->unsetProperty('lft');
  704. $entity->unsetProperty('rght');
  705. $this->assertTrue($table->delete($entity));
  706. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  707. $table->recover();
  708. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  709. $this->assertEquals($expected, $result);
  710. }
  711. /**
  712. * Tests that a leaf can be taken out of the tree and put in as a root
  713. *
  714. * @return void
  715. */
  716. public function testRemoveFromLeafFromTree()
  717. {
  718. $table = $this->table;
  719. $entity = $table->get(10);
  720. $this->assertSame($entity, $table->removeFromTree($entity));
  721. $this->assertEquals(21, $entity->lft);
  722. $this->assertEquals(22, $entity->rght);
  723. $this->assertEquals(null, $entity->parent_id);
  724. $result = $table->find()->order('lft')->hydrate(false);
  725. $expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10];
  726. $this->assertTreeNumbers($expected, $table);
  727. }
  728. /**
  729. * Test removing a middle node from a tree
  730. *
  731. * @return void
  732. */
  733. public function testRemoveMiddleNodeFromTree()
  734. {
  735. $table = $this->table;
  736. $entity = $table->get(6);
  737. $this->assertSame($entity, $table->removeFromTree($entity));
  738. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  739. $this->assertEquals(21, $entity->lft);
  740. $this->assertEquals(22, $entity->rght);
  741. $this->assertEquals(null, $entity->parent_id);
  742. $result = $table->find()->order('lft')->hydrate(false);
  743. $expected = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 6];
  744. $this->assertTreeNumbers($expected, $table);
  745. }
  746. /**
  747. * Tests removing the root of a tree
  748. *
  749. * @return void
  750. */
  751. public function testRemoveRootFromTree()
  752. {
  753. $table = $this->table;
  754. $entity = $table->get(1);
  755. $this->assertSame($entity, $table->removeFromTree($entity));
  756. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  757. $this->assertEquals(21, $entity->lft);
  758. $this->assertEquals(22, $entity->rght);
  759. $this->assertEquals(null, $entity->parent_id);
  760. $expected = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1];
  761. $this->assertTreeNumbers($expected, $table);
  762. }
  763. /**
  764. * Tests that using associations having tree fields in the schema
  765. * does not generate SQL errors
  766. *
  767. * @return void
  768. */
  769. public function testFindPathWithAssociation()
  770. {
  771. $table = $this->table;
  772. $other = TableRegistry::get('FriendlyTrees', [
  773. 'table' => $table->table()
  774. ]);
  775. $table->hasOne('FriendlyTrees', [
  776. 'foreignKey' => 'id'
  777. ]);
  778. $result = $table
  779. ->find('children', ['for' => 1])
  780. ->contain('FriendlyTrees')
  781. ->toArray();
  782. $this->assertCount(9, $result);
  783. }
  784. /**
  785. * Tests getting the depth level of a node in the tree.
  786. *
  787. * @return void
  788. */
  789. public function testGetLevel()
  790. {
  791. $entity = $this->table->get(8);
  792. $result = $this->table->getLevel($entity);
  793. $this->assertEquals(3, $result);
  794. $result = $this->table->getLevel($entity->id);
  795. $this->assertEquals(3, $result);
  796. $result = $this->table->getLevel(5);
  797. $this->assertEquals(2, $result);
  798. $result = $this->table->getLevel(99999);
  799. $this->assertFalse($result);
  800. }
  801. /**
  802. * Test setting level for new nodes
  803. *
  804. * @return void
  805. */
  806. public function testSetLevelNewNode()
  807. {
  808. $this->table->behaviors()->Tree->config('level', 'level');
  809. $entity = new Entity(['parent_id' => null, 'name' => 'Depth 0']);
  810. $this->table->save($entity);
  811. $entity = $this->table->get(12);
  812. $this->assertEquals(0, $entity->level);
  813. $entity = new Entity(['parent_id' => 1, 'name' => 'Depth 1']);
  814. $this->table->save($entity);
  815. $entity = $this->table->get(13);
  816. $this->assertEquals(1, $entity->level);
  817. $entity = new Entity(['parent_id' => 8, 'name' => 'Depth 4']);
  818. $this->table->save($entity);
  819. $entity = $this->table->get(14);
  820. $this->assertEquals(4, $entity->level);
  821. }
  822. /**
  823. * Test setting level for existing nodes
  824. *
  825. * @return void
  826. */
  827. public function testSetLevelExistingNode()
  828. {
  829. $this->table->behaviors()->Tree->config('level', 'level');
  830. // Leaf node
  831. $entity = $this->table->get(4);
  832. $this->assertEquals(2, $entity->level);
  833. $this->table->save($entity);
  834. $entity = $this->table->get(4);
  835. $this->assertEquals(2, $entity->level);
  836. // Non leaf node so depth of descendents will also change
  837. $entity = $this->table->get(6);
  838. $this->assertEquals(1, $entity->level);
  839. $entity->parent_id = null;
  840. $this->table->save($entity);
  841. $entity = $this->table->get(6);
  842. $this->assertEquals(0, $entity->level);
  843. $entity = $this->table->get(7);
  844. $this->assertEquals(1, $entity->level);
  845. $entity = $this->table->get(8);
  846. $this->assertEquals(2, $entity->level);
  847. }
  848. /**
  849. * Custom assertion use to verify tha a tree is returned in the expected order
  850. * and that it is still valid
  851. *
  852. * @param array $expected The list of ids in the order they are expected
  853. * @param \Cake\ORM\Table the table instance to use for comparing
  854. * @return void
  855. */
  856. public function assertTreeNumbers($expected, $table)
  857. {
  858. $result = $table->find()->order('lft')->hydrate(false);
  859. $this->assertEquals($expected, $result->extract('id')->toArray());
  860. $numbers = [];
  861. $result->each(function ($v) use (&$numbers) {
  862. $numbers[] = $v['lft'];
  863. $numbers[] = $v['rght'];
  864. });
  865. sort($numbers);
  866. $this->assertEquals(range(1, 22), $numbers);
  867. }
  868. }