TreeBehaviorTest.php 23 KB

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