TreeBehaviorTest.php 23 KB

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