TreeBehaviorTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  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. public function testMoveDownMultiplePositions()
  402. {
  403. $node = $this->table->moveDown($this->table->get(3), 2);
  404. $result = $this->table
  405. ->find('children', ['for' => 2, 'direct' => true])
  406. ->order(['lft' => 'ASC'])
  407. ->extract('id')
  408. ->toArray();
  409. $this->assertEquals([4,5,3], $result);
  410. }
  411. /**
  412. * Tests the recover function
  413. *
  414. * @return void
  415. */
  416. public function testRecover()
  417. {
  418. $table = $this->table;
  419. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  420. $table->updateAll(['lft' => null, 'rght' => null], []);
  421. $table->recover();
  422. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  423. $this->assertEquals($expected, $result);
  424. }
  425. /**
  426. * Tests the recover function with a custom scope
  427. *
  428. * @return void
  429. */
  430. public function testRecoverScoped()
  431. {
  432. $table = TableRegistry::get('MenuLinkTrees');
  433. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  434. $expected = $table->find()
  435. ->where(['menu' => 'main-menu'])
  436. ->order('lft')
  437. ->hydrate(false)
  438. ->toArray();
  439. $expected2 = $table->find()
  440. ->where(['menu' => 'categories'])
  441. ->order('lft')
  442. ->hydrate(false)
  443. ->toArray();
  444. $table->updateAll(['lft' => null, 'rght' => null], ['menu' => 'main-menu']);
  445. $table->recover();
  446. $result = $table->find()
  447. ->where(['menu' => 'main-menu'])
  448. ->order('lft')
  449. ->hydrate(false)
  450. ->toArray();
  451. $this->assertEquals($expected, $result);
  452. $result2 = $table->find()
  453. ->where(['menu' => 'categories'])
  454. ->order('lft')
  455. ->hydrate(false)
  456. ->toArray();
  457. $this->assertEquals($expected2, $result2);
  458. }
  459. /**
  460. * Tests adding a new orphan node
  461. *
  462. * @return void
  463. */
  464. public function testAddOrphan()
  465. {
  466. $table = $this->table;
  467. $entity = new Entity(
  468. ['name' => 'New Orphan', 'parent_id' => null, 'level' => null],
  469. ['markNew' => true]
  470. );
  471. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  472. $this->assertSame($entity, $table->save($entity));
  473. $this->assertEquals(23, $entity->lft);
  474. $this->assertEquals(24, $entity->rght);
  475. $expected[] = $entity->toArray();
  476. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  477. $this->assertEquals($expected, $results);
  478. }
  479. /**
  480. * Tests that adding a child node as a decendant of one of the roots works
  481. *
  482. * @return void
  483. */
  484. public function testAddMiddle()
  485. {
  486. $table = $this->table;
  487. $entity = new Entity(
  488. ['name' => 'laptops', 'parent_id' => 1],
  489. ['markNew' => true]
  490. );
  491. $this->assertSame($entity, $table->save($entity));
  492. $this->assertEquals(20, $entity->lft);
  493. $this->assertEquals(21, $entity->rght);
  494. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  495. $table->recover();
  496. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  497. $this->assertEquals($expected, $result);
  498. }
  499. /**
  500. * Tests adding a leaf to the tree
  501. *
  502. * @return void
  503. */
  504. public function testAddLeaf()
  505. {
  506. $table = $this->table;
  507. $entity = new Entity(
  508. ['name' => 'laptops', 'parent_id' => 2],
  509. ['markNew' => true]
  510. );
  511. $this->assertSame($entity, $table->save($entity));
  512. $this->assertEquals(9, $entity->lft);
  513. $this->assertEquals(10, $entity->rght);
  514. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  515. $table->recover();
  516. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  517. $this->assertEquals($expected, $results);
  518. }
  519. /**
  520. * Tests moving a subtree to the right
  521. *
  522. * @return void
  523. */
  524. public function testReParentSubTreeRight()
  525. {
  526. $table = $this->table;
  527. $entity = $table->get(2);
  528. $entity->parent_id = 6;
  529. $this->assertSame($entity, $table->save($entity));
  530. $this->assertEquals(11, $entity->lft);
  531. $this->assertEquals(18, $entity->rght);
  532. $result = $table->find()->order('lft')->hydrate(false);
  533. $expected = [1, 6, 7, 8, 9, 10, 2, 3, 4, 5, 11];
  534. $this->assertTreeNumbers($expected, $table);
  535. }
  536. /**
  537. * Tests moving a subtree to the left
  538. *
  539. * @return void
  540. */
  541. public function testReParentSubTreeLeft()
  542. {
  543. $table = $this->table;
  544. $entity = $table->get(6);
  545. $entity->parent_id = 2;
  546. $this->assertSame($entity, $table->save($entity));
  547. $this->assertEquals(9, $entity->lft);
  548. $this->assertEquals(18, $entity->rght);
  549. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  550. $table->recover();
  551. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  552. $this->assertEquals($expected, $result);
  553. }
  554. /**
  555. * Test moving a leaft to the left
  556. *
  557. * @return void
  558. */
  559. public function testReParentLeafLeft()
  560. {
  561. $table = $this->table;
  562. $entity = $table->get(10);
  563. $entity->parent_id = 2;
  564. $this->assertSame($entity, $table->save($entity));
  565. $this->assertEquals(9, $entity->lft);
  566. $this->assertEquals(10, $entity->rght);
  567. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  568. $table->recover();
  569. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  570. $this->assertEquals($expected, $result);
  571. }
  572. /**
  573. * Test moving a leaf to the left
  574. *
  575. * @return void
  576. */
  577. public function testReParentLeafRight()
  578. {
  579. $table = $this->table;
  580. $entity = $table->get(5);
  581. $entity->parent_id = 6;
  582. $this->assertSame($entity, $table->save($entity));
  583. $this->assertEquals(17, $entity->lft);
  584. $this->assertEquals(18, $entity->rght);
  585. $result = $table->find()->order('lft')->hydrate(false);
  586. $expected = [1, 2, 3, 4, 6, 7, 8, 9, 10, 5, 11];
  587. $this->assertTreeNumbers($expected, $table);
  588. }
  589. /**
  590. * Tests moving a subtree with a node having no lft and rght columns
  591. *
  592. * @return void
  593. */
  594. public function testReParentNoTreeColumns()
  595. {
  596. $table = $this->table;
  597. $entity = $table->get(6);
  598. $entity->unsetProperty('lft');
  599. $entity->unsetProperty('rght');
  600. $entity->parent_id = 2;
  601. $this->assertSame($entity, $table->save($entity));
  602. $this->assertEquals(9, $entity->lft);
  603. $this->assertEquals(18, $entity->rght);
  604. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  605. $table->recover();
  606. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  607. $this->assertEquals($expected, $result);
  608. }
  609. /**
  610. * Tests moving a subtree as a new root
  611. *
  612. * @return void
  613. */
  614. public function testRootingSubTree()
  615. {
  616. $table = $this->table;
  617. $entity = $table->get(2);
  618. $entity->parent_id = null;
  619. $this->assertSame($entity, $table->save($entity));
  620. $this->assertEquals(15, $entity->lft);
  621. $this->assertEquals(22, $entity->rght);
  622. $result = $table->find()->order('lft')->hydrate(false);
  623. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  624. $this->assertTreeNumbers($expected, $table);
  625. }
  626. /**
  627. * Tests moving a subtree with no tree columns
  628. *
  629. * @return void
  630. */
  631. public function testRootingNoTreeColumns()
  632. {
  633. $table = $this->table;
  634. $entity = $table->get(2);
  635. $entity->unsetProperty('lft');
  636. $entity->unsetProperty('rght');
  637. $entity->parent_id = null;
  638. $this->assertSame($entity, $table->save($entity));
  639. $this->assertEquals(15, $entity->lft);
  640. $this->assertEquals(22, $entity->rght);
  641. $result = $table->find()->order('lft')->hydrate(false);
  642. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  643. $this->assertTreeNumbers($expected, $table);
  644. }
  645. /**
  646. * Tests that trying to create a cycle throws an exception
  647. *
  648. * @expectedException \RuntimeException
  649. * @expectedExceptionMessage Cannot use node "5" as parent for entity "2"
  650. * @return void
  651. */
  652. public function testReparentCycle()
  653. {
  654. $table = $this->table;
  655. $entity = $table->get(2);
  656. $entity->parent_id = 5;
  657. $table->save($entity);
  658. }
  659. /**
  660. * Tests deleting a leaf in the tree
  661. *
  662. * @return void
  663. */
  664. public function testDeleteLeaf()
  665. {
  666. $table = $this->table;
  667. $entity = $table->get(4);
  668. $this->assertTrue($table->delete($entity));
  669. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  670. $table->recover();
  671. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  672. $this->assertEquals($expected, $result);
  673. }
  674. /**
  675. * Tests deleting a subtree
  676. *
  677. * @return void
  678. */
  679. public function testDeleteSubTree()
  680. {
  681. $table = $this->table;
  682. $entity = $table->get(6);
  683. $this->assertTrue($table->delete($entity));
  684. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  685. $table->recover();
  686. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  687. $this->assertEquals($expected, $result);
  688. }
  689. /**
  690. * Test deleting a root node
  691. *
  692. * @return void
  693. */
  694. public function testDeleteRoot()
  695. {
  696. $table = $this->table;
  697. $entity = $table->get(1);
  698. $this->assertTrue($table->delete($entity));
  699. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  700. $table->recover();
  701. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  702. $this->assertEquals($expected, $result);
  703. }
  704. /**
  705. * Test deleting a node with no tree columns
  706. *
  707. * @return void
  708. */
  709. public function testDeleteRootNoTreeColumns()
  710. {
  711. $table = $this->table;
  712. $entity = $table->get(1);
  713. $entity->unsetProperty('lft');
  714. $entity->unsetProperty('rght');
  715. $this->assertTrue($table->delete($entity));
  716. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  717. $table->recover();
  718. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  719. $this->assertEquals($expected, $result);
  720. }
  721. /**
  722. * Tests that a leaf can be taken out of the tree and put in as a root
  723. *
  724. * @return void
  725. */
  726. public function testRemoveFromLeafFromTree()
  727. {
  728. $table = $this->table;
  729. $entity = $table->get(10);
  730. $this->assertSame($entity, $table->removeFromTree($entity));
  731. $this->assertEquals(21, $entity->lft);
  732. $this->assertEquals(22, $entity->rght);
  733. $this->assertEquals(null, $entity->parent_id);
  734. $result = $table->find()->order('lft')->hydrate(false);
  735. $expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10];
  736. $this->assertTreeNumbers($expected, $table);
  737. }
  738. /**
  739. * Test removing a middle node from a tree
  740. *
  741. * @return void
  742. */
  743. public function testRemoveMiddleNodeFromTree()
  744. {
  745. $table = $this->table;
  746. $entity = $table->get(6);
  747. $this->assertSame($entity, $table->removeFromTree($entity));
  748. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  749. $this->assertEquals(21, $entity->lft);
  750. $this->assertEquals(22, $entity->rght);
  751. $this->assertEquals(null, $entity->parent_id);
  752. $result = $table->find()->order('lft')->hydrate(false);
  753. $expected = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 6];
  754. $this->assertTreeNumbers($expected, $table);
  755. }
  756. /**
  757. * Tests removing the root of a tree
  758. *
  759. * @return void
  760. */
  761. public function testRemoveRootFromTree()
  762. {
  763. $table = $this->table;
  764. $entity = $table->get(1);
  765. $this->assertSame($entity, $table->removeFromTree($entity));
  766. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  767. $this->assertEquals(21, $entity->lft);
  768. $this->assertEquals(22, $entity->rght);
  769. $this->assertEquals(null, $entity->parent_id);
  770. $expected = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1];
  771. $this->assertTreeNumbers($expected, $table);
  772. }
  773. /**
  774. * Tests that using associations having tree fields in the schema
  775. * does not generate SQL errors
  776. *
  777. * @return void
  778. */
  779. public function testFindPathWithAssociation()
  780. {
  781. $table = $this->table;
  782. $other = TableRegistry::get('FriendlyTrees', [
  783. 'table' => $table->table()
  784. ]);
  785. $table->hasOne('FriendlyTrees', [
  786. 'foreignKey' => 'id'
  787. ]);
  788. $result = $table
  789. ->find('children', ['for' => 1])
  790. ->contain('FriendlyTrees')
  791. ->toArray();
  792. $this->assertCount(9, $result);
  793. }
  794. /**
  795. * Tests getting the depth level of a node in the tree.
  796. *
  797. * @return void
  798. */
  799. public function testGetLevel()
  800. {
  801. $entity = $this->table->get(8);
  802. $result = $this->table->getLevel($entity);
  803. $this->assertEquals(3, $result);
  804. $result = $this->table->getLevel($entity->id);
  805. $this->assertEquals(3, $result);
  806. $result = $this->table->getLevel(5);
  807. $this->assertEquals(2, $result);
  808. $result = $this->table->getLevel(99999);
  809. $this->assertFalse($result);
  810. }
  811. /**
  812. * Test setting level for new nodes
  813. *
  814. * @return void
  815. */
  816. public function testSetLevelNewNode()
  817. {
  818. $this->table->behaviors()->Tree->config('level', 'level');
  819. $entity = new Entity(['parent_id' => null, 'name' => 'Depth 0']);
  820. $this->table->save($entity);
  821. $entity = $this->table->get(12);
  822. $this->assertEquals(0, $entity->level);
  823. $entity = new Entity(['parent_id' => 1, 'name' => 'Depth 1']);
  824. $this->table->save($entity);
  825. $entity = $this->table->get(13);
  826. $this->assertEquals(1, $entity->level);
  827. $entity = new Entity(['parent_id' => 8, 'name' => 'Depth 4']);
  828. $this->table->save($entity);
  829. $entity = $this->table->get(14);
  830. $this->assertEquals(4, $entity->level);
  831. }
  832. /**
  833. * Test setting level for existing nodes
  834. *
  835. * @return void
  836. */
  837. public function testSetLevelExistingNode()
  838. {
  839. $this->table->behaviors()->Tree->config('level', 'level');
  840. // Leaf node
  841. $entity = $this->table->get(4);
  842. $this->assertEquals(2, $entity->level);
  843. $this->table->save($entity);
  844. $entity = $this->table->get(4);
  845. $this->assertEquals(2, $entity->level);
  846. // Non leaf node so depth of descendents will also change
  847. $entity = $this->table->get(6);
  848. $this->assertEquals(1, $entity->level);
  849. $entity->parent_id = null;
  850. $this->table->save($entity);
  851. $entity = $this->table->get(6);
  852. $this->assertEquals(0, $entity->level);
  853. $entity = $this->table->get(7);
  854. $this->assertEquals(1, $entity->level);
  855. $entity = $this->table->get(8);
  856. $this->assertEquals(2, $entity->level);
  857. }
  858. /**
  859. * Custom assertion use to verify tha a tree is returned in the expected order
  860. * and that it is still valid
  861. *
  862. * @param array $expected The list of ids in the order they are expected
  863. * @param \Cake\ORM\Table the table instance to use for comparing
  864. * @return void
  865. */
  866. public function assertTreeNumbers($expected, $table)
  867. {
  868. $result = $table->find()->order('lft')->hydrate(false);
  869. $this->assertEquals($expected, $result->extract('id')->toArray());
  870. $numbers = [];
  871. $result->each(function ($v) use (&$numbers) {
  872. $numbers[] = $v['lft'];
  873. $numbers[] = $v['rght'];
  874. });
  875. sort($numbers);
  876. $this->assertEquals(range(1, 22), $numbers);
  877. }
  878. }