TreeBehaviorTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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->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\Error\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 with custom options
  175. *
  176. * @return void
  177. */
  178. public function testFindTreeListCustom() {
  179. $table = TableRegistry::get('MenuLinkTrees');
  180. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  181. $result = $table
  182. ->find('treeList', ['keyPath' => 'url', 'valuePath' => 'id', 'spacer' => ' '])
  183. ->toArray();
  184. $expected = [
  185. '/link1.html' => '1',
  186. 'http://example.com' => ' 2',
  187. '/what/even-more-links.html' => ' 3',
  188. '/lorem/ipsum.html' => ' 4',
  189. '/what/the.html' => ' 5',
  190. '/yeah/another-link.html' => '6',
  191. 'http://cakephp.org' => ' 7',
  192. '/page/who-we-are.html' => '8'
  193. ];
  194. $this->assertEquals($expected, $result);
  195. }
  196. /**
  197. * Tests the moveUp() method
  198. *
  199. * @return void
  200. */
  201. public function testMoveUp() {
  202. $table = TableRegistry::get('MenuLinkTrees');
  203. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  204. // top level, wont move
  205. $node = $this->table->moveUp($table->get(1), 10);
  206. $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
  207. // edge cases
  208. $this->assertFalse($this->table->moveUp($table->get(1), 0));
  209. $node = $this->table->moveUp($table->get(1), -10);
  210. $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
  211. // move inner node
  212. $node = $table->moveUp($table->get(3), 1);
  213. $nodes = $table->find('children', ['for' => 1])->all();
  214. $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
  215. $this->assertEquals(['lft' => 2, 'rght' => 7], $node->extract(['lft', 'rght']));
  216. }
  217. /**
  218. * Tests moving a node with no siblings
  219. *
  220. * @return void
  221. */
  222. public function testMoveLeaf() {
  223. $table = TableRegistry::get('MenuLinkTrees');
  224. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  225. $node = $table->moveUp($table->get(5), 1);
  226. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  227. }
  228. /**
  229. * Tests moving a node to the top
  230. *
  231. * @return void
  232. */
  233. public function testMoveTop() {
  234. $table = TableRegistry::get('MenuLinkTrees');
  235. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  236. $node = $table->moveUp($table->get(8), true);
  237. $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
  238. $nodes = $table->find()
  239. ->select(['id'])
  240. ->where(function($exp) {
  241. return $exp->isNull('parent_id');
  242. })
  243. ->where(['menu' => 'main-menu'])
  244. ->order(['lft' => 'ASC'])
  245. ->all();
  246. $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
  247. }
  248. /**
  249. * Tests moving a node with no lft and rght
  250. *
  251. * @return void
  252. */
  253. public function testMoveNoTreeColumns() {
  254. $table = TableRegistry::get('MenuLinkTrees');
  255. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  256. $node = $table->get(8);
  257. $node->unsetProperty('lft');
  258. $node->unsetProperty('rght');
  259. $node = $table->moveUp($node, true);
  260. $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
  261. $nodes = $table->find()
  262. ->select(['id'])
  263. ->where(function($exp) {
  264. return $exp->isNull('parent_id');
  265. })
  266. ->where(['menu' => 'main-menu'])
  267. ->order(['lft' => 'ASC'])
  268. ->all();
  269. $this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
  270. }
  271. /**
  272. * Tests the moveDown() method
  273. *
  274. * @return void
  275. */
  276. public function testMoveDown() {
  277. $table = TableRegistry::get('MenuLinkTrees');
  278. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  279. // latest node, wont move
  280. $node = $this->table->moveDown($table->get(8), 10);
  281. $this->assertEquals(['lft' => 21, 'rght' => 22], $node->extract(['lft', 'rght']));
  282. // edge cases
  283. $this->assertFalse($this->table->moveDown($table->get(8), 0));
  284. // move inner node
  285. $node = $table->moveDown($table->get(2), 1);
  286. $nodes = $table->find('children', ['for' => 1])->all();
  287. $this->assertEquals([3, 4, 5, 2], $nodes->extract('id')->toArray());
  288. $this->assertEquals(['lft' => 11, 'rght' => 12], $node->extract(['lft', 'rght']));
  289. }
  290. /**
  291. * Tests moving a node that has no siblings
  292. *
  293. * @return void
  294. */
  295. public function testMoveLeafDown() {
  296. $table = TableRegistry::get('MenuLinkTrees');
  297. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  298. $node = $table->moveDown($table->get(5), 1);
  299. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  300. }
  301. /**
  302. * Tests moving a node to the bottom
  303. *
  304. * @return void
  305. */
  306. public function testMoveToBottom() {
  307. $table = TableRegistry::get('MenuLinkTrees');
  308. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  309. $node = $table->moveDown($table->get(1), true);
  310. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  311. $nodes = $table->find()
  312. ->select(['id'])
  313. ->where(function($exp) {
  314. return $exp->isNull('parent_id');
  315. })
  316. ->where(['menu' => 'main-menu'])
  317. ->order(['lft' => 'ASC'])
  318. ->all();
  319. $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
  320. }
  321. /**
  322. * Tests moving a node with no lft and rght columns
  323. *
  324. * @return void
  325. */
  326. public function testMoveDownNoTreeColumns() {
  327. $table = TableRegistry::get('MenuLinkTrees');
  328. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  329. $node = $table->get(1);
  330. $node->unsetProperty('lft');
  331. $node->unsetProperty('rght');
  332. $node = $table->moveDown($node, true);
  333. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  334. $nodes = $table->find()
  335. ->select(['id'])
  336. ->where(function($exp) {
  337. return $exp->isNull('parent_id');
  338. })
  339. ->where(['menu' => 'main-menu'])
  340. ->order(['lft' => 'ASC'])
  341. ->all();
  342. $this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
  343. }
  344. /**
  345. * Tests the recover function
  346. *
  347. * @return void
  348. */
  349. public function testRecover() {
  350. $table = $this->table;
  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 = $this->table;
  397. $entity = new Entity(
  398. ['name' => 'New Orphan', 'parent_id' => null],
  399. ['markNew' => true]
  400. );
  401. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  402. $this->assertSame($entity, $table->save($entity));
  403. $this->assertEquals(23, $entity->lft);
  404. $this->assertEquals(24, $entity->rght);
  405. $expected[] = $entity->toArray();
  406. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  407. $this->assertEquals($expected, $results);
  408. }
  409. /**
  410. * Tests that adding a child node as a decendant of one of the roots works
  411. *
  412. * @return void
  413. */
  414. public function testAddMiddle() {
  415. $table = $this->table;
  416. $entity = new Entity(
  417. ['name' => 'laptops', 'parent_id' => 1],
  418. ['markNew' => true]
  419. );
  420. $this->assertSame($entity, $table->save($entity));
  421. $this->assertEquals(20, $entity->lft);
  422. $this->assertEquals(21, $entity->rght);
  423. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  424. $table->recover();
  425. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  426. $this->assertEquals($expected, $result);
  427. }
  428. /**
  429. * Tests adding a leaf to the tree
  430. *
  431. * @return void
  432. */
  433. public function testAddLeaf() {
  434. $table = $this->table;
  435. $entity = new Entity(
  436. ['name' => 'laptops', 'parent_id' => 2],
  437. ['markNew' => true]
  438. );
  439. $this->assertSame($entity, $table->save($entity));
  440. $this->assertEquals(9, $entity->lft);
  441. $this->assertEquals(10, $entity->rght);
  442. $results = $table->find()->order('lft')->hydrate(false)->toArray();
  443. $table->recover();
  444. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  445. $this->assertEquals($expected, $results);
  446. }
  447. /**
  448. * Tests moving a subtree to the right
  449. *
  450. * @return void
  451. */
  452. public function testReParentSubTreeRight() {
  453. $table = $this->table;
  454. $entity = $table->get(2);
  455. $entity->parent_id = 6;
  456. $this->assertSame($entity, $table->save($entity));
  457. $this->assertEquals(11, $entity->lft);
  458. $this->assertEquals(18, $entity->rght);
  459. $result = $table->find()->order('lft')->hydrate(false);
  460. $expected = [1, 6, 7, 8, 9, 10, 2, 3, 4, 5, 11];
  461. $this->assertTreeNumbers($expected, $table);
  462. }
  463. /**
  464. * Tests moving a subtree to the left
  465. *
  466. * @return void
  467. */
  468. public function testReParentSubTreeLeft() {
  469. $table = $this->table;
  470. $entity = $table->get(6);
  471. $entity->parent_id = 2;
  472. $this->assertSame($entity, $table->save($entity));
  473. $this->assertEquals(9, $entity->lft);
  474. $this->assertEquals(18, $entity->rght);
  475. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  476. $table->recover();
  477. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  478. $this->assertEquals($expected, $result);
  479. }
  480. /**
  481. * Test moving a leaft to the left
  482. *
  483. * @return void
  484. */
  485. public function testReParentLeafLeft() {
  486. $table = $this->table;
  487. $entity = $table->get(10);
  488. $entity->parent_id = 2;
  489. $this->assertSame($entity, $table->save($entity));
  490. $this->assertEquals(9, $entity->lft);
  491. $this->assertEquals(10, $entity->rght);
  492. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  493. $table->recover();
  494. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  495. $this->assertEquals($expected, $result);
  496. }
  497. /**
  498. * Test moving a leaf to the left
  499. *
  500. * @return void
  501. */
  502. public function testReParentLeafRight() {
  503. $table = $this->table;
  504. $entity = $table->get(5);
  505. $entity->parent_id = 6;
  506. $this->assertSame($entity, $table->save($entity));
  507. $this->assertEquals(17, $entity->lft);
  508. $this->assertEquals(18, $entity->rght);
  509. $result = $table->find()->order('lft')->hydrate(false);
  510. $expected = [1, 2, 3, 4, 6, 7, 8, 9, 10, 5, 11];
  511. $this->assertTreeNumbers($expected, $table);
  512. }
  513. /**
  514. * Tests moving a subtree with a node having no lft and rght columns
  515. *
  516. * @return void
  517. */
  518. public function testReParentNoTreeColumns() {
  519. $table = $this->table;
  520. $entity = $table->get(6);
  521. $entity->unsetProperty('lft');
  522. $entity->unsetProperty('rght');
  523. $entity->parent_id = 2;
  524. $this->assertSame($entity, $table->save($entity));
  525. $this->assertEquals(9, $entity->lft);
  526. $this->assertEquals(18, $entity->rght);
  527. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  528. $table->recover();
  529. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  530. $this->assertEquals($expected, $result);
  531. }
  532. /**
  533. * Tests moving a subtree as a new root
  534. *
  535. * @return void
  536. */
  537. public function testRootingSubTree() {
  538. $table = $this->table;
  539. $entity = $table->get(2);
  540. $entity->parent_id = null;
  541. $this->assertSame($entity, $table->save($entity));
  542. $this->assertEquals(15, $entity->lft);
  543. $this->assertEquals(22, $entity->rght);
  544. $result = $table->find()->order('lft')->hydrate(false);
  545. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  546. $this->assertTreeNumbers($expected, $table);
  547. }
  548. /**
  549. * Tests moving a subtree with no tree columns
  550. *
  551. * @return void
  552. */
  553. public function testRootingNoTreeColumns() {
  554. $table = $this->table;
  555. $entity = $table->get(2);
  556. $entity->unsetProperty('lft');
  557. $entity->unsetProperty('rght');
  558. $entity->parent_id = null;
  559. $this->assertSame($entity, $table->save($entity));
  560. $this->assertEquals(15, $entity->lft);
  561. $this->assertEquals(22, $entity->rght);
  562. $result = $table->find()->order('lft')->hydrate(false);
  563. $expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
  564. $this->assertTreeNumbers($expected, $table);
  565. }
  566. /**
  567. * Tests that trying to create a cycle throws an exception
  568. *
  569. * @expectedException RuntimeException
  570. * @expectedExceptionMessage Cannot use node "5" as parent for entity "2"
  571. * @return void
  572. */
  573. public function testReparentCycle() {
  574. $table = $this->table;
  575. $entity = $table->get(2);
  576. $entity->parent_id = 5;
  577. $table->save($entity);
  578. }
  579. /**
  580. * Tests deleting a leaf in the tree
  581. *
  582. * @return void
  583. */
  584. public function testDeleteLeaf() {
  585. $table = $this->table;
  586. $entity = $table->get(4);
  587. $this->assertTrue($table->delete($entity));
  588. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  589. $table->recover();
  590. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  591. $this->assertEquals($expected, $result);
  592. }
  593. /**
  594. * Tests deleting a subtree
  595. *
  596. * @return void
  597. */
  598. public function testDeleteSubTree() {
  599. $table = $this->table;
  600. $entity = $table->get(6);
  601. $this->assertTrue($table->delete($entity));
  602. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  603. $table->recover();
  604. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  605. $this->assertEquals($expected, $result);
  606. }
  607. /**
  608. * Test deleting a root node
  609. *
  610. * @return void
  611. */
  612. public function testDeleteRoot() {
  613. $table = $this->table;
  614. $entity = $table->get(1);
  615. $this->assertTrue($table->delete($entity));
  616. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  617. $table->recover();
  618. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  619. $this->assertEquals($expected, $result);
  620. }
  621. /**
  622. * Test deleting a node with no tree columns
  623. *
  624. * @return void
  625. */
  626. public function testDeleteRootNoTreeColumns() {
  627. $table = $this->table;
  628. $entity = $table->get(1);
  629. $entity->unsetProperty('lft');
  630. $entity->unsetProperty('rght');
  631. $this->assertTrue($table->delete($entity));
  632. $result = $table->find()->order('lft')->hydrate(false)->toArray();
  633. $table->recover();
  634. $expected = $table->find()->order('lft')->hydrate(false)->toArray();
  635. $this->assertEquals($expected, $result);
  636. }
  637. /**
  638. * Tests that a leaf can be taken out of the tree and put in as a root
  639. *
  640. * @return void
  641. */
  642. public function testRemoveFromLeafFromTree() {
  643. $table = $this->table;
  644. $entity = $table->get(10);
  645. $this->assertSame($entity, $table->removeFromTree($entity));
  646. $this->assertEquals(21, $entity->lft);
  647. $this->assertEquals(22, $entity->rght);
  648. $this->assertEquals(null, $entity->parent_id);
  649. $result = $table->find()->order('lft')->hydrate(false);
  650. $expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10];
  651. $this->assertTreeNumbers($expected, $table);
  652. }
  653. /**
  654. * Test removing a middle node from a tree
  655. *
  656. * @return void
  657. */
  658. public function testRemoveMiddleNodeFromTree() {
  659. $table = $this->table;
  660. $entity = $table->get(6);
  661. $this->assertSame($entity, $table->removeFromTree($entity));
  662. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  663. $this->assertEquals(21, $entity->lft);
  664. $this->assertEquals(22, $entity->rght);
  665. $this->assertEquals(null, $entity->parent_id);
  666. $result = $table->find()->order('lft')->hydrate(false);
  667. $expected = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 6];
  668. $this->assertTreeNumbers($expected, $table);
  669. }
  670. /**
  671. * Tests removing the root of a tree
  672. *
  673. * @return void
  674. */
  675. public function testRemoveRootFromTree() {
  676. $table = $this->table;
  677. $entity = $table->get(1);
  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. $expected = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1];
  684. $this->assertTreeNumbers($expected, $table);
  685. }
  686. /**
  687. * Custom assertion use to verify tha a tree is returned in the expected order
  688. * and that it is still valid
  689. *
  690. * @param array $expected The list of ids in the order they are expected
  691. * @param \Cake\ORM\Table the table instance to use for comparing
  692. * @return void
  693. */
  694. public function assertTreeNumbers($expected, $table) {
  695. $result = $table->find()->order('lft')->hydrate(false);
  696. $this->assertEquals($expected, $result->extract('id')->toArray());
  697. $numbers = [];
  698. $result->each(function($v) use (&$numbers) {
  699. $numbers[] = $v['lft'];
  700. $numbers[] = $v['rght'];
  701. });
  702. sort($numbers);
  703. $this->assertEquals(range(1, 22), $numbers);
  704. }
  705. }