TreeBehaviorTest.php 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  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.menu_link_trees',
  34. 'core.number_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. * Sanity test
  50. *
  51. * Make sure the assert method acts as you'd expect, this is the expected
  52. * initial db state
  53. *
  54. * @return void
  55. */
  56. public function testAssertMpttValues()
  57. {
  58. $expected = [
  59. ' 1:20 - 1:electronics',
  60. '_ 2: 9 - 2:televisions',
  61. '__ 3: 4 - 3:tube',
  62. '__ 5: 6 - 4:lcd',
  63. '__ 7: 8 - 5:plasma',
  64. '_10:19 - 6:portable',
  65. '__11:14 - 7:mp3',
  66. '___12:13 - 8:flash',
  67. '__15:16 - 9:cd',
  68. '__17:18 - 10:radios',
  69. '21:22 - 11:alien hardware'
  70. ];
  71. $this->assertMpttValues($expected, $this->table);
  72. $table = TableRegistry::get('MenuLinkTrees');
  73. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  74. $expected = [
  75. ' 1:10 - 1:Link 1',
  76. '_ 2: 3 - 2:Link 2',
  77. '_ 4: 9 - 3:Link 3',
  78. '__ 5: 8 - 4:Link 4',
  79. '___ 6: 7 - 5:Link 5',
  80. '11:14 - 6:Link 6',
  81. '_12:13 - 7:Link 7',
  82. '15:16 - 8:Link 8'
  83. ];
  84. $this->assertMpttValues($expected, $table);
  85. $table->removeBehavior('Tree');
  86. $table->addBehavior('Tree', ['scope' => ['menu' => 'categories']]);
  87. $expected = [
  88. ' 1:10 - 9:electronics',
  89. '_ 2: 9 - 10:televisions',
  90. '__ 3: 4 - 11:tube',
  91. '__ 5: 8 - 12:lcd',
  92. '___ 6: 7 - 13:plasma',
  93. '11:20 - 14:portable',
  94. '_12:15 - 15:mp3',
  95. '__13:14 - 16:flash',
  96. '_16:17 - 17:cd',
  97. '_18:19 - 18:radios'
  98. ];
  99. $this->assertMpttValues($expected, $table);
  100. }
  101. /**
  102. * Tests the find('path') method
  103. *
  104. * @return void
  105. */
  106. public function testFindPath()
  107. {
  108. $nodes = $this->table->find('path', ['for' => 9]);
  109. $this->assertEquals([1, 6, 9], $nodes->extract('id')->toArray());
  110. $nodes = $this->table->find('path', ['for' => 10]);
  111. $this->assertSame([1, 6, 10], $nodes->extract('id')->toArray());
  112. $nodes = $this->table->find('path', ['for' => 5]);
  113. $this->assertSame([1, 2, 5], $nodes->extract('id')->toArray());
  114. $nodes = $this->table->find('path', ['for' => 1]);
  115. $this->assertSame([1], $nodes->extract('id')->toArray());
  116. $entity = $this->table->newEntity(['name' => 'odd one', 'parent_id' => 1]);
  117. $entity = $this->table->save($entity);
  118. $newId = $entity->id;
  119. $entity = $this->table->get(2);
  120. $entity->parent_id = $newId;
  121. $this->table->save($entity);
  122. $nodes = $this->table->find('path', ['for' => 4]);
  123. $this->assertSame([1, $newId, 2, 4], $nodes->extract('id')->toArray());
  124. // find path with scope
  125. $table = TableRegistry::get('MenuLinkTrees');
  126. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  127. $nodes = $table->find('path', ['for' => 5]);
  128. $this->assertSame([1, 3, 4, 5], $nodes->extract('id')->toArray());
  129. }
  130. /**
  131. * Tests the childCount() method
  132. *
  133. * @return void
  134. */
  135. public function testChildCount()
  136. {
  137. // direct children for the root node
  138. $table = $this->table;
  139. $countDirect = $this->table->childCount($table->get(1), true);
  140. $this->assertEquals(2, $countDirect);
  141. // counts all the children of root
  142. $count = $this->table->childCount($table->get(1), false);
  143. $this->assertEquals(9, $count);
  144. // counts direct children
  145. $count = $this->table->childCount($table->get(2), false);
  146. $this->assertEquals(3, $count);
  147. // count children for a middle-node
  148. $count = $this->table->childCount($table->get(6), false);
  149. $this->assertEquals(4, $count);
  150. // count leaf children
  151. $count = $this->table->childCount($table->get(10), false);
  152. $this->assertEquals(0, $count);
  153. // test scoping
  154. $table = TableRegistry::get('MenuLinkTrees');
  155. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  156. $count = $table->childCount($table->get(3), false);
  157. $this->assertEquals(2, $count);
  158. }
  159. /**
  160. * Tests that childCount will provide the correct lft and rght values
  161. *
  162. * @return void
  163. */
  164. public function testChildCountNoTreeColumns()
  165. {
  166. $table = $this->table;
  167. $node = $table->get(6);
  168. $node->unsetProperty('lft');
  169. $node->unsetProperty('rght');
  170. $count = $this->table->childCount($node, false);
  171. $this->assertEquals(4, $count);
  172. }
  173. /**
  174. * Tests the childCount() plus callable scoping
  175. *
  176. * @return void
  177. */
  178. public function testCallableScoping()
  179. {
  180. $table = TableRegistry::get('MenuLinkTrees');
  181. $table->addBehavior('Tree', [
  182. 'scope' => function ($query) {
  183. return $query->where(['menu' => 'main-menu']);
  184. }
  185. ]);
  186. $count = $table->childCount($table->get(1), false);
  187. $this->assertEquals(4, $count);
  188. }
  189. /**
  190. * Tests the find('children') method
  191. *
  192. * @return void
  193. */
  194. public function testFindChildren()
  195. {
  196. $table = TableRegistry::get('MenuLinkTrees');
  197. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  198. // root
  199. $nodeIds = [];
  200. $nodes = $table->find('children', ['for' => 1])->all();
  201. $this->assertEquals([2, 3, 4, 5], $nodes->extract('id')->toArray());
  202. // leaf
  203. $nodeIds = [];
  204. $nodes = $table->find('children', ['for' => 5])->all();
  205. $this->assertEquals(0, count($nodes->extract('id')->toArray()));
  206. // direct children
  207. $nodes = $table->find('children', ['for' => 1, 'direct' => true])->all();
  208. $this->assertEquals([2, 3], $nodes->extract('id')->toArray());
  209. }
  210. /**
  211. * Tests that find('children') will throw an exception if the node was not found
  212. *
  213. * @expectedException \Cake\Datasource\Exception\RecordNotFoundException
  214. * @return void
  215. */
  216. public function testFindChildrenException()
  217. {
  218. $table = TableRegistry::get('MenuLinkTrees');
  219. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  220. $query = $table->find('children', ['for' => 500]);
  221. }
  222. /**
  223. * Tests the find('treeList') method
  224. *
  225. * @return void
  226. */
  227. public function testFindTreeList()
  228. {
  229. $table = TableRegistry::get('MenuLinkTrees');
  230. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  231. $result = $table->find('treeList')->toArray();
  232. $expected = [
  233. 1 => 'Link 1',
  234. 2 => '_Link 2',
  235. 3 => '_Link 3',
  236. 4 => '__Link 4',
  237. 5 => '___Link 5',
  238. 6 => 'Link 6',
  239. 7 => '_Link 7',
  240. 8 => 'Link 8'
  241. ];
  242. $this->assertEquals($expected, $result);
  243. }
  244. /**
  245. * Tests the find('treeList') method after moveUp, moveDown
  246. *
  247. * @return void
  248. */
  249. public function testFindTreeListAfterMove()
  250. {
  251. $table = TableRegistry::get('MenuLinkTrees');
  252. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  253. // moveUp
  254. $table->moveUp($table->get(3), 1);
  255. $expected = [
  256. ' 1:10 - 1:Link 1',
  257. '_ 2: 7 - 3:Link 3',
  258. '__ 3: 6 - 4:Link 4',
  259. '___ 4: 5 - 5:Link 5',
  260. '_ 8: 9 - 2:Link 2',
  261. '11:14 - 6:Link 6',
  262. '_12:13 - 7:Link 7',
  263. '15:16 - 8:Link 8'
  264. ];
  265. $this->assertMpttValues($expected, $table);
  266. // moveDown
  267. $table->moveDown($table->get(6), 1);
  268. $expected = [
  269. ' 1:10 - 1:Link 1',
  270. '_ 2: 7 - 3:Link 3',
  271. '__ 3: 6 - 4:Link 4',
  272. '___ 4: 5 - 5:Link 5',
  273. '_ 8: 9 - 2:Link 2',
  274. '11:12 - 8:Link 8',
  275. '13:16 - 6:Link 6',
  276. '_14:15 - 7:Link 7'
  277. ];
  278. $this->assertMpttValues($expected, $table);
  279. }
  280. /**
  281. * Tests the find('treeList') method with custom options
  282. *
  283. * @return void
  284. */
  285. public function testFindTreeListCustom()
  286. {
  287. $table = TableRegistry::get('MenuLinkTrees');
  288. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  289. $result = $table
  290. ->find('treeList', ['keyPath' => 'url', 'valuePath' => 'id', 'spacer' => ' '])
  291. ->toArray();
  292. $expected = [
  293. '/link1.html' => '1',
  294. 'http://example.com' => ' 2',
  295. '/what/even-more-links.html' => ' 3',
  296. '/lorem/ipsum.html' => ' 4',
  297. '/what/the.html' => ' 5',
  298. '/yeah/another-link.html' => '6',
  299. 'http://cakephp.org' => ' 7',
  300. '/page/who-we-are.html' => '8'
  301. ];
  302. $this->assertEquals($expected, $result);
  303. }
  304. /**
  305. * Tests the testFormatTreeListCustom() method.
  306. *
  307. * @return void
  308. */
  309. public function testFormatTreeListCustom()
  310. {
  311. $table = TableRegistry::get('MenuLinkTrees');
  312. $table->addBehavior('Tree');
  313. $query = $table
  314. ->find('threaded')
  315. ->where(['menu' => 'main-menu']);
  316. $options = ['keyPath' => 'url', 'valuePath' => 'id', 'spacer' => ' '];
  317. $result = $table->formatTreeList($query, $options)->toArray();
  318. $expected = [
  319. '/link1.html' => '1',
  320. 'http://example.com' => ' 2',
  321. '/what/even-more-links.html' => ' 3',
  322. '/lorem/ipsum.html' => ' 4',
  323. '/what/the.html' => ' 5',
  324. '/yeah/another-link.html' => '6',
  325. 'http://cakephp.org' => ' 7',
  326. '/page/who-we-are.html' => '8'
  327. ];
  328. $this->assertEquals($expected, $result);
  329. }
  330. /**
  331. * Tests the moveUp() method
  332. *
  333. * @return void
  334. */
  335. public function testMoveUp()
  336. {
  337. $table = TableRegistry::get('MenuLinkTrees');
  338. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  339. // top level, won't move
  340. $node = $this->table->moveUp($table->get(1), 10);
  341. $this->assertEquals(['lft' => 1, 'rght' => 10], $node->extract(['lft', 'rght']));
  342. $expected = [
  343. ' 1:10 - 1:Link 1',
  344. '_ 2: 3 - 2:Link 2',
  345. '_ 4: 9 - 3:Link 3',
  346. '__ 5: 8 - 4:Link 4',
  347. '___ 6: 7 - 5:Link 5',
  348. '11:14 - 6:Link 6',
  349. '_12:13 - 7:Link 7',
  350. '15:16 - 8:Link 8'
  351. ];
  352. $this->assertMpttValues($expected, $table);
  353. // edge cases
  354. $this->assertFalse($this->table->moveUp($table->get(1), 0));
  355. $this->assertFalse($this->table->moveUp($table->get(1), -10));
  356. $expected = [
  357. ' 1:10 - 1:Link 1',
  358. '_ 2: 3 - 2:Link 2',
  359. '_ 4: 9 - 3:Link 3',
  360. '__ 5: 8 - 4:Link 4',
  361. '___ 6: 7 - 5:Link 5',
  362. '11:14 - 6:Link 6',
  363. '_12:13 - 7:Link 7',
  364. '15:16 - 8:Link 8'
  365. ];
  366. $this->assertMpttValues($expected, $table);
  367. // move inner node
  368. $node = $table->moveUp($table->get(3), 1);
  369. $nodes = $table->find('children', ['for' => 1])->all();
  370. $this->assertEquals(['lft' => 2, 'rght' => 7], $node->extract(['lft', 'rght']));
  371. $expected = [
  372. ' 1:10 - 1:Link 1',
  373. '_ 2: 7 - 3:Link 3',
  374. '__ 3: 6 - 4:Link 4',
  375. '___ 4: 5 - 5:Link 5',
  376. '_ 8: 9 - 2:Link 2',
  377. '11:14 - 6:Link 6',
  378. '_12:13 - 7:Link 7',
  379. '15:16 - 8:Link 8'
  380. ];
  381. $this->assertMpttValues($expected, $table);
  382. }
  383. /**
  384. * Tests moving a node with no siblings
  385. *
  386. * @return void
  387. */
  388. public function testMoveLeaf()
  389. {
  390. $table = TableRegistry::get('MenuLinkTrees');
  391. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  392. $node = $table->moveUp($table->get(5), 1);
  393. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  394. $expected = [
  395. ' 1:10 - 1:Link 1',
  396. '_ 2: 3 - 2:Link 2',
  397. '_ 4: 9 - 3:Link 3',
  398. '__ 5: 8 - 4:Link 4',
  399. '___ 6: 7 - 5:Link 5',
  400. '11:14 - 6:Link 6',
  401. '_12:13 - 7:Link 7',
  402. '15:16 - 8:Link 8'
  403. ];
  404. $this->assertMpttValues($expected, $table);
  405. }
  406. /**
  407. * Tests moving a node to the top
  408. *
  409. * @return void
  410. */
  411. public function testMoveTop()
  412. {
  413. $table = TableRegistry::get('MenuLinkTrees');
  414. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  415. $node = $table->moveUp($table->get(8), true);
  416. $expected = [
  417. ' 1: 2 - 8:Link 8',
  418. ' 3:12 - 1:Link 1',
  419. '_ 4: 5 - 2:Link 2',
  420. '_ 6:11 - 3:Link 3',
  421. '__ 7:10 - 4:Link 4',
  422. '___ 8: 9 - 5:Link 5',
  423. '13:16 - 6:Link 6',
  424. '_14:15 - 7:Link 7'
  425. ];
  426. $this->assertMpttValues($expected, $table);
  427. }
  428. /**
  429. * Tests moving a node with no lft and rght
  430. *
  431. * @return void
  432. */
  433. public function testMoveNoTreeColumns()
  434. {
  435. $table = TableRegistry::get('MenuLinkTrees');
  436. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  437. $node = $table->get(8);
  438. $node->unsetProperty('lft');
  439. $node->unsetProperty('rght');
  440. $node = $table->moveUp($node, true);
  441. $this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
  442. $expected = [
  443. ' 1: 2 - 8:Link 8',
  444. ' 3:12 - 1:Link 1',
  445. '_ 4: 5 - 2:Link 2',
  446. '_ 6:11 - 3:Link 3',
  447. '__ 7:10 - 4:Link 4',
  448. '___ 8: 9 - 5:Link 5',
  449. '13:16 - 6:Link 6',
  450. '_14:15 - 7:Link 7'
  451. ];
  452. $this->assertMpttValues($expected, $table);
  453. }
  454. /**
  455. * Tests the moveDown() method
  456. *
  457. * @return void
  458. */
  459. public function testMoveDown()
  460. {
  461. $table = TableRegistry::get('MenuLinkTrees');
  462. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  463. // latest node, won't move
  464. $node = $table->moveDown($table->get(8), 10);
  465. $this->assertEquals(['lft' => 15, 'rght' => 16], $node->extract(['lft', 'rght']));
  466. $expected = [
  467. ' 1:10 - 1:Link 1',
  468. '_ 2: 3 - 2:Link 2',
  469. '_ 4: 9 - 3:Link 3',
  470. '__ 5: 8 - 4:Link 4',
  471. '___ 6: 7 - 5:Link 5',
  472. '11:14 - 6:Link 6',
  473. '_12:13 - 7:Link 7',
  474. '15:16 - 8:Link 8'
  475. ];
  476. $this->assertMpttValues($expected, $table);
  477. // edge cases
  478. $this->assertFalse($this->table->moveDown($table->get(8), 0));
  479. $this->assertFalse($this->table->moveDown($table->get(8), -10));
  480. $expected = [
  481. ' 1:10 - 1:Link 1',
  482. '_ 2: 3 - 2:Link 2',
  483. '_ 4: 9 - 3:Link 3',
  484. '__ 5: 8 - 4:Link 4',
  485. '___ 6: 7 - 5:Link 5',
  486. '11:14 - 6:Link 6',
  487. '_12:13 - 7:Link 7',
  488. '15:16 - 8:Link 8'
  489. ];
  490. $this->assertMpttValues($expected, $table);
  491. // move inner node
  492. $node = $table->moveDown($table->get(2), 1);
  493. $this->assertEquals(['lft' => 8, 'rght' => 9], $node->extract(['lft', 'rght']));
  494. $expected = [
  495. ' 1:10 - 1:Link 1',
  496. '_ 2: 7 - 3:Link 3',
  497. '__ 3: 6 - 4:Link 4',
  498. '___ 4: 5 - 5:Link 5',
  499. '_ 8: 9 - 2:Link 2',
  500. '11:14 - 6:Link 6',
  501. '_12:13 - 7:Link 7',
  502. '15:16 - 8:Link 8'
  503. ];
  504. $this->assertMpttValues($expected, $table);
  505. }
  506. /**
  507. * Tests moving a node that has no siblings
  508. *
  509. * @return void
  510. */
  511. public function testMoveLeafDown()
  512. {
  513. $table = TableRegistry::get('MenuLinkTrees');
  514. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  515. $node = $table->moveDown($table->get(5), 1);
  516. $this->assertEquals(['lft' => 6, 'rght' => 7], $node->extract(['lft', 'rght']));
  517. $expected = [
  518. ' 1:10 - 1:Link 1',
  519. '_ 2: 3 - 2:Link 2',
  520. '_ 4: 9 - 3:Link 3',
  521. '__ 5: 8 - 4:Link 4',
  522. '___ 6: 7 - 5:Link 5',
  523. '11:14 - 6:Link 6',
  524. '_12:13 - 7:Link 7',
  525. '15:16 - 8:Link 8'
  526. ];
  527. $this->assertMpttValues($expected, $table);
  528. }
  529. /**
  530. * Tests moving a node to the bottom
  531. *
  532. * @return void
  533. */
  534. public function testMoveToBottom()
  535. {
  536. $table = TableRegistry::get('MenuLinkTrees');
  537. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  538. $node = $table->moveDown($table->get(1), true);
  539. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  540. $expected = [
  541. ' 1: 4 - 6:Link 6',
  542. '_ 2: 3 - 7:Link 7',
  543. ' 5: 6 - 8:Link 8',
  544. ' 7:16 - 1:Link 1',
  545. '_ 8: 9 - 2:Link 2',
  546. '_10:15 - 3:Link 3',
  547. '__11:14 - 4:Link 4',
  548. '___12:13 - 5:Link 5'
  549. ];
  550. $this->assertMpttValues($expected, $table);
  551. }
  552. /**
  553. * Tests moving a node with no lft and rght columns
  554. *
  555. * @return void
  556. */
  557. public function testMoveDownNoTreeColumns()
  558. {
  559. $table = TableRegistry::get('MenuLinkTrees');
  560. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  561. $node = $table->get(1);
  562. $node->unsetProperty('lft');
  563. $node->unsetProperty('rght');
  564. $node = $table->moveDown($node, true);
  565. $this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
  566. $expected = [
  567. ' 1: 4 - 6:Link 6',
  568. '_ 2: 3 - 7:Link 7',
  569. ' 5: 6 - 8:Link 8',
  570. ' 7:16 - 1:Link 1',
  571. '_ 8: 9 - 2:Link 2',
  572. '_10:15 - 3:Link 3',
  573. '__11:14 - 4:Link 4',
  574. '___12:13 - 5:Link 5'
  575. ];
  576. $this->assertMpttValues($expected, $table);
  577. }
  578. public function testMoveDownMultiplePositions()
  579. {
  580. $node = $this->table->moveDown($this->table->get(3), 2);
  581. $this->assertEquals(['lft' => 7, 'rght' => 8], $node->extract(['lft', 'rght']));
  582. $expected = [
  583. ' 1:20 - 1:electronics',
  584. '_ 2: 9 - 2:televisions',
  585. '__ 3: 4 - 4:lcd',
  586. '__ 5: 6 - 5:plasma',
  587. '__ 7: 8 - 3:tube',
  588. '_10:19 - 6:portable',
  589. '__11:14 - 7:mp3',
  590. '___12:13 - 8:flash',
  591. '__15:16 - 9:cd',
  592. '__17:18 - 10:radios',
  593. '21:22 - 11:alien hardware'
  594. ];
  595. $this->assertMpttValues($expected, $this->table);
  596. }
  597. /**
  598. * Tests the recover function
  599. *
  600. * @return void
  601. */
  602. public function testRecover()
  603. {
  604. $table = $this->table;
  605. $expectedLevels = $table
  606. ->find('list', ['valueField' => 'depth'])
  607. ->order('lft')
  608. ->toArray();
  609. $table->updateAll(['lft' => null, 'rght' => null, 'depth' => null], []);
  610. $table->behaviors()->Tree->config('level', 'depth');
  611. $table->recover();
  612. $expected = [
  613. ' 1:20 - 1:electronics',
  614. '_ 2: 9 - 2:televisions',
  615. '__ 3: 4 - 3:tube',
  616. '__ 5: 6 - 4:lcd',
  617. '__ 7: 8 - 5:plasma',
  618. '_10:19 - 6:portable',
  619. '__11:14 - 7:mp3',
  620. '___12:13 - 8:flash',
  621. '__15:16 - 9:cd',
  622. '__17:18 - 10:radios',
  623. '21:22 - 11:alien hardware'
  624. ];
  625. $this->assertMpttValues($expected, $table);
  626. $result = $table
  627. ->find('list', ['valueField' => 'depth'])
  628. ->order('lft')
  629. ->toArray();
  630. $this->assertSame($expectedLevels, $result);
  631. }
  632. /**
  633. * Tests the recover function with a custom scope
  634. *
  635. * @return void
  636. */
  637. public function testRecoverScoped()
  638. {
  639. $table = TableRegistry::get('MenuLinkTrees');
  640. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
  641. $table->updateAll(['lft' => null, 'rght' => null], ['menu' => 'main-menu']);
  642. $table->recover();
  643. $expected = [
  644. ' 1:10 - 1:Link 1',
  645. '_ 2: 3 - 2:Link 2',
  646. '_ 4: 9 - 3:Link 3',
  647. '__ 5: 8 - 4:Link 4',
  648. '___ 6: 7 - 5:Link 5',
  649. '11:14 - 6:Link 6',
  650. '_12:13 - 7:Link 7',
  651. '15:16 - 8:Link 8'
  652. ];
  653. $this->assertMpttValues($expected, $table);
  654. $table->removeBehavior('Tree');
  655. $table->addBehavior('Tree', ['scope' => ['menu' => 'categories']]);
  656. $expected = [
  657. ' 1:10 - 9:electronics',
  658. '_ 2: 9 - 10:televisions',
  659. '__ 3: 4 - 11:tube',
  660. '__ 5: 8 - 12:lcd',
  661. '___ 6: 7 - 13:plasma',
  662. '11:20 - 14:portable',
  663. '_12:15 - 15:mp3',
  664. '__13:14 - 16:flash',
  665. '_16:17 - 17:cd',
  666. '_18:19 - 18:radios'
  667. ];
  668. $this->assertMpttValues($expected, $table);
  669. }
  670. /**
  671. * Test recover function with a custom order clause
  672. *
  673. * @return void
  674. */
  675. public function testRecoverWithCustomOrder()
  676. {
  677. $table = TableRegistry::get('MenuLinkTrees');
  678. $table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu'], 'recoverOrder' => ['MenuLinkTrees.title' => 'desc']]);
  679. $table->updateAll(['lft' => null, 'rght' => null], ['menu' => 'main-menu']);
  680. $table->recover();
  681. $expected = [
  682. ' 1: 2 - 8:Link 8',
  683. ' 3: 6 - 6:Link 6',
  684. '_ 4: 5 - 7:Link 7',
  685. ' 7:16 - 1:Link 1',
  686. '_ 8:13 - 3:Link 3',
  687. '__ 9:12 - 4:Link 4',
  688. '___10:11 - 5:Link 5',
  689. '_14:15 - 2:Link 2'
  690. ];
  691. $this->assertMpttValues($expected, $table);
  692. }
  693. /**
  694. * Tests adding a new orphan node
  695. *
  696. * @return void
  697. */
  698. public function testAddOrphan()
  699. {
  700. $table = $this->table;
  701. $entity = new Entity(
  702. ['name' => 'New Orphan', 'parent_id' => null, 'level' => null],
  703. ['markNew' => true]
  704. );
  705. $this->assertSame($entity, $table->save($entity));
  706. $this->assertEquals(23, $entity->lft);
  707. $this->assertEquals(24, $entity->rght);
  708. $expected = [
  709. ' 1:20 - 1:electronics',
  710. '_ 2: 9 - 2:televisions',
  711. '__ 3: 4 - 3:tube',
  712. '__ 5: 6 - 4:lcd',
  713. '__ 7: 8 - 5:plasma',
  714. '_10:19 - 6:portable',
  715. '__11:14 - 7:mp3',
  716. '___12:13 - 8:flash',
  717. '__15:16 - 9:cd',
  718. '__17:18 - 10:radios',
  719. '21:22 - 11:alien hardware',
  720. '23:24 - 12:New Orphan',
  721. ];
  722. $this->assertMpttValues($expected, $this->table);
  723. }
  724. /**
  725. * Tests that adding a child node as a decendant of one of the roots works
  726. *
  727. * @return void
  728. */
  729. public function testAddMiddle()
  730. {
  731. $table = $this->table;
  732. $entity = new Entity(
  733. ['name' => 'laptops', 'parent_id' => 1],
  734. ['markNew' => true]
  735. );
  736. $this->assertSame($entity, $table->save($entity));
  737. $this->assertEquals(20, $entity->lft);
  738. $this->assertEquals(21, $entity->rght);
  739. $expected = [
  740. ' 1:22 - 1:electronics',
  741. '_ 2: 9 - 2:televisions',
  742. '__ 3: 4 - 3:tube',
  743. '__ 5: 6 - 4:lcd',
  744. '__ 7: 8 - 5:plasma',
  745. '_10:19 - 6:portable',
  746. '__11:14 - 7:mp3',
  747. '___12:13 - 8:flash',
  748. '__15:16 - 9:cd',
  749. '__17:18 - 10:radios',
  750. '_20:21 - 12:laptops',
  751. '23:24 - 11:alien hardware',
  752. ];
  753. $this->assertMpttValues($expected, $this->table);
  754. }
  755. /**
  756. * Tests adding a leaf to the tree
  757. *
  758. * @return void
  759. */
  760. public function testAddLeaf()
  761. {
  762. $table = $this->table;
  763. $entity = new Entity(
  764. ['name' => 'laptops', 'parent_id' => 2],
  765. ['markNew' => true]
  766. );
  767. $this->assertSame($entity, $table->save($entity));
  768. $this->assertEquals(9, $entity->lft);
  769. $this->assertEquals(10, $entity->rght);
  770. $expected = [
  771. ' 1:22 - 1:electronics',
  772. '_ 2:11 - 2:televisions',
  773. '__ 3: 4 - 3:tube',
  774. '__ 5: 6 - 4:lcd',
  775. '__ 7: 8 - 5:plasma',
  776. '__ 9:10 - 12:laptops',
  777. '_12:21 - 6:portable',
  778. '__13:16 - 7:mp3',
  779. '___14:15 - 8:flash',
  780. '__17:18 - 9:cd',
  781. '__19:20 - 10:radios',
  782. '23:24 - 11:alien hardware'
  783. ];
  784. $this->assertMpttValues($expected, $this->table);
  785. }
  786. /**
  787. * Tests adding a root element to the tree when all other root elements have children
  788. *
  789. * @return void
  790. */
  791. public function testAddRoot()
  792. {
  793. $table = $this->table;
  794. //First add a child to the empty root element
  795. $alien = $table->find()->where(['name' => 'alien hardware'])->first();
  796. $entity = new Entity(['name' => 'plasma rifle', 'parent_id' => $alien->id], ['markNew' => true]);
  797. $table->save($entity);
  798. $entity = new Entity(['name' => 'carpentry', 'parent_id' => null], ['markNew' => true]);
  799. $this->assertSame($entity, $table->save($entity));
  800. $this->assertEquals(25, $entity->lft);
  801. $this->assertEquals(26, $entity->rght);
  802. $expected = [
  803. ' 1:20 - 1:electronics',
  804. '_ 2: 9 - 2:televisions',
  805. '__ 3: 4 - 3:tube',
  806. '__ 5: 6 - 4:lcd',
  807. '__ 7: 8 - 5:plasma',
  808. '_10:19 - 6:portable',
  809. '__11:14 - 7:mp3',
  810. '___12:13 - 8:flash',
  811. '__15:16 - 9:cd',
  812. '__17:18 - 10:radios',
  813. '21:24 - 11:alien hardware',
  814. '_22:23 - 12:plasma rifle',
  815. '25:26 - 13:carpentry',
  816. ];
  817. $this->assertMpttValues($expected, $this->table);
  818. }
  819. /**
  820. * Tests moving a subtree to the right
  821. *
  822. * @return void
  823. */
  824. public function testReParentSubTreeRight()
  825. {
  826. $table = $this->table;
  827. $entity = $table->get(2);
  828. $entity->parent_id = 6;
  829. $this->assertSame($entity, $table->save($entity));
  830. $this->assertEquals(11, $entity->lft);
  831. $this->assertEquals(18, $entity->rght);
  832. $expected = [
  833. ' 1:20 - 1:electronics',
  834. '_ 2:19 - 6:portable',
  835. '__ 3: 6 - 7:mp3',
  836. '___ 4: 5 - 8:flash',
  837. '__ 7: 8 - 9:cd',
  838. '__ 9:10 - 10:radios',
  839. '__11:18 - 2:televisions',
  840. '___12:13 - 3:tube',
  841. '___14:15 - 4:lcd',
  842. '___16:17 - 5:plasma',
  843. '21:22 - 11:alien hardware'
  844. ];
  845. $this->assertMpttValues($expected, $table);
  846. }
  847. /**
  848. * Tests moving a subtree to the left
  849. *
  850. * @return void
  851. */
  852. public function testReParentSubTreeLeft()
  853. {
  854. $table = $this->table;
  855. $entity = $table->get(6);
  856. $entity->parent_id = 2;
  857. $this->assertSame($entity, $table->save($entity));
  858. $this->assertEquals(9, $entity->lft);
  859. $this->assertEquals(18, $entity->rght);
  860. $expected = [
  861. ' 1:20 - 1:electronics',
  862. '_ 2:19 - 2:televisions',
  863. '__ 3: 4 - 3:tube',
  864. '__ 5: 6 - 4:lcd',
  865. '__ 7: 8 - 5:plasma',
  866. '__ 9:18 - 6:portable',
  867. '___10:13 - 7:mp3',
  868. '____11:12 - 8:flash',
  869. '___14:15 - 9:cd',
  870. '___16:17 - 10:radios',
  871. '21:22 - 11:alien hardware'
  872. ];
  873. $this->assertMpttValues($expected, $this->table);
  874. }
  875. /**
  876. * Test moving a leaft to the left
  877. *
  878. * @return void
  879. */
  880. public function testReParentLeafLeft()
  881. {
  882. $table = $this->table;
  883. $entity = $table->get(10);
  884. $entity->parent_id = 2;
  885. $this->assertSame($entity, $table->save($entity));
  886. $this->assertEquals(9, $entity->lft);
  887. $this->assertEquals(10, $entity->rght);
  888. $expected = [
  889. ' 1:20 - 1:electronics',
  890. '_ 2:11 - 2:televisions',
  891. '__ 3: 4 - 3:tube',
  892. '__ 5: 6 - 4:lcd',
  893. '__ 7: 8 - 5:plasma',
  894. '__ 9:10 - 10:radios',
  895. '_12:19 - 6:portable',
  896. '__13:16 - 7:mp3',
  897. '___14:15 - 8:flash',
  898. '__17:18 - 9:cd',
  899. '21:22 - 11:alien hardware'
  900. ];
  901. $this->assertMpttValues($expected, $this->table);
  902. }
  903. /**
  904. * Test moving a leaf to the left
  905. *
  906. * @return void
  907. */
  908. public function testReParentLeafRight()
  909. {
  910. $table = $this->table;
  911. $entity = $table->get(5);
  912. $entity->parent_id = 6;
  913. $this->assertSame($entity, $table->save($entity));
  914. $this->assertEquals(17, $entity->lft);
  915. $this->assertEquals(18, $entity->rght);
  916. $result = $table->find()->order('lft')->hydrate(false);
  917. $expected = [
  918. ' 1:20 - 1:electronics',
  919. '_ 2: 7 - 2:televisions',
  920. '__ 3: 4 - 3:tube',
  921. '__ 5: 6 - 4:lcd',
  922. '_ 8:19 - 6:portable',
  923. '__ 9:12 - 7:mp3',
  924. '___10:11 - 8:flash',
  925. '__13:14 - 9:cd',
  926. '__15:16 - 10:radios',
  927. '__17:18 - 5:plasma',
  928. '21:22 - 11:alien hardware'
  929. ];
  930. $this->assertMpttValues($expected, $table);
  931. }
  932. /**
  933. * Tests moving a subtree with a node having no lft and rght columns
  934. *
  935. * @return void
  936. */
  937. public function testReParentNoTreeColumns()
  938. {
  939. $table = $this->table;
  940. $entity = $table->get(6);
  941. $entity->unsetProperty('lft');
  942. $entity->unsetProperty('rght');
  943. $entity->parent_id = 2;
  944. $this->assertSame($entity, $table->save($entity));
  945. $this->assertEquals(9, $entity->lft);
  946. $this->assertEquals(18, $entity->rght);
  947. $expected = [
  948. ' 1:20 - 1:electronics',
  949. '_ 2:19 - 2:televisions',
  950. '__ 3: 4 - 3:tube',
  951. '__ 5: 6 - 4:lcd',
  952. '__ 7: 8 - 5:plasma',
  953. '__ 9:18 - 6:portable',
  954. '___10:13 - 7:mp3',
  955. '____11:12 - 8:flash',
  956. '___14:15 - 9:cd',
  957. '___16:17 - 10:radios',
  958. '21:22 - 11:alien hardware'
  959. ];
  960. $this->assertMpttValues($expected, $this->table);
  961. }
  962. /**
  963. * Tests moving a subtree as a new root
  964. *
  965. * @return void
  966. */
  967. public function testRootingSubTree()
  968. {
  969. $table = $this->table;
  970. $entity = $table->get(2);
  971. $entity->parent_id = null;
  972. $this->assertSame($entity, $table->save($entity));
  973. $this->assertEquals(15, $entity->lft);
  974. $this->assertEquals(22, $entity->rght);
  975. $expected = [
  976. ' 1:12 - 1:electronics',
  977. '_ 2:11 - 6:portable',
  978. '__ 3: 6 - 7:mp3',
  979. '___ 4: 5 - 8:flash',
  980. '__ 7: 8 - 9:cd',
  981. '__ 9:10 - 10:radios',
  982. '13:14 - 11:alien hardware',
  983. '15:22 - 2:televisions',
  984. '_16:17 - 3:tube',
  985. '_18:19 - 4:lcd',
  986. '_20:21 - 5:plasma'
  987. ];
  988. $this->assertMpttValues($expected, $table);
  989. }
  990. /**
  991. * Tests moving a subtree with no tree columns
  992. *
  993. * @return void
  994. */
  995. public function testRootingNoTreeColumns()
  996. {
  997. $table = $this->table;
  998. $entity = $table->get(2);
  999. $entity->unsetProperty('lft');
  1000. $entity->unsetProperty('rght');
  1001. $entity->parent_id = null;
  1002. $this->assertSame($entity, $table->save($entity));
  1003. $this->assertEquals(15, $entity->lft);
  1004. $this->assertEquals(22, $entity->rght);
  1005. $expected = [
  1006. ' 1:12 - 1:electronics',
  1007. '_ 2:11 - 6:portable',
  1008. '__ 3: 6 - 7:mp3',
  1009. '___ 4: 5 - 8:flash',
  1010. '__ 7: 8 - 9:cd',
  1011. '__ 9:10 - 10:radios',
  1012. '13:14 - 11:alien hardware',
  1013. '15:22 - 2:televisions',
  1014. '_16:17 - 3:tube',
  1015. '_18:19 - 4:lcd',
  1016. '_20:21 - 5:plasma'
  1017. ];
  1018. $this->assertMpttValues($expected, $table);
  1019. }
  1020. /**
  1021. * Tests that trying to create a cycle throws an exception
  1022. *
  1023. * @expectedException \RuntimeException
  1024. * @expectedExceptionMessage Cannot use node "5" as parent for entity "2"
  1025. * @return void
  1026. */
  1027. public function testReparentCycle()
  1028. {
  1029. $table = $this->table;
  1030. $entity = $table->get(2);
  1031. $entity->parent_id = 5;
  1032. $table->save($entity);
  1033. }
  1034. /**
  1035. * Tests deleting a leaf in the tree
  1036. *
  1037. * @return void
  1038. */
  1039. public function testDeleteLeaf()
  1040. {
  1041. $table = $this->table;
  1042. $entity = $table->get(4);
  1043. $this->assertTrue($table->delete($entity));
  1044. $expected = [
  1045. ' 1:18 - 1:electronics',
  1046. '_ 2: 7 - 2:televisions',
  1047. '__ 3: 4 - 3:tube',
  1048. '__ 5: 6 - 5:plasma',
  1049. '_ 8:17 - 6:portable',
  1050. '__ 9:12 - 7:mp3',
  1051. '___10:11 - 8:flash',
  1052. '__13:14 - 9:cd',
  1053. '__15:16 - 10:radios',
  1054. '19:20 - 11:alien hardware'
  1055. ];
  1056. $this->assertMpttValues($expected, $this->table);
  1057. }
  1058. /**
  1059. * Tests deleting a subtree
  1060. *
  1061. * @return void
  1062. */
  1063. public function testDeleteSubTree()
  1064. {
  1065. $table = $this->table;
  1066. $entity = $table->get(6);
  1067. $this->assertTrue($table->delete($entity));
  1068. $expected = [
  1069. ' 1:10 - 1:electronics',
  1070. '_ 2: 9 - 2:televisions',
  1071. '__ 3: 4 - 3:tube',
  1072. '__ 5: 6 - 4:lcd',
  1073. '__ 7: 8 - 5:plasma',
  1074. '11:12 - 11:alien hardware'
  1075. ];
  1076. $this->assertMpttValues($expected, $this->table);
  1077. }
  1078. /**
  1079. * Test deleting a root node
  1080. *
  1081. * @return void
  1082. */
  1083. public function testDeleteRoot()
  1084. {
  1085. $table = $this->table;
  1086. $entity = $table->get(1);
  1087. $this->assertTrue($table->delete($entity));
  1088. $expected = [
  1089. ' 1: 2 - 11:alien hardware'
  1090. ];
  1091. $this->assertMpttValues($expected, $this->table);
  1092. }
  1093. /**
  1094. * Test deleting a node with no tree columns
  1095. *
  1096. * @return void
  1097. */
  1098. public function testDeleteRootNoTreeColumns()
  1099. {
  1100. $table = $this->table;
  1101. $entity = $table->get(1);
  1102. $entity->unsetProperty('lft');
  1103. $entity->unsetProperty('rght');
  1104. $this->assertTrue($table->delete($entity));
  1105. $expected = [
  1106. ' 1: 2 - 11:alien hardware'
  1107. ];
  1108. $this->assertMpttValues($expected, $this->table);
  1109. }
  1110. /**
  1111. * Tests that a leaf can be taken out of the tree and put in as a root
  1112. *
  1113. * @return void
  1114. */
  1115. public function testRemoveFromLeafFromTree()
  1116. {
  1117. $table = $this->table;
  1118. $entity = $table->get(10);
  1119. $this->assertSame($entity, $table->removeFromTree($entity));
  1120. $this->assertEquals(21, $entity->lft);
  1121. $this->assertEquals(22, $entity->rght);
  1122. $this->assertEquals(null, $entity->parent_id);
  1123. $result = $table->find()->order('lft')->hydrate(false);
  1124. $expected = [
  1125. ' 1:18 - 1:electronics',
  1126. '_ 2: 9 - 2:televisions',
  1127. '__ 3: 4 - 3:tube',
  1128. '__ 5: 6 - 4:lcd',
  1129. '__ 7: 8 - 5:plasma',
  1130. '_10:17 - 6:portable',
  1131. '__11:14 - 7:mp3',
  1132. '___12:13 - 8:flash',
  1133. '__15:16 - 9:cd',
  1134. '19:20 - 11:alien hardware',
  1135. '21:22 - 10:radios'
  1136. ];
  1137. $this->assertMpttValues($expected, $table);
  1138. }
  1139. /**
  1140. * Test removing a middle node from a tree
  1141. *
  1142. * @return void
  1143. */
  1144. public function testRemoveMiddleNodeFromTree()
  1145. {
  1146. $table = $this->table;
  1147. $entity = $table->get(6);
  1148. $this->assertSame($entity, $table->removeFromTree($entity));
  1149. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  1150. $this->assertEquals(21, $entity->lft);
  1151. $this->assertEquals(22, $entity->rght);
  1152. $this->assertEquals(null, $entity->parent_id);
  1153. $result = $table->find()->order('lft')->hydrate(false);
  1154. $expected = [
  1155. ' 1:18 - 1:electronics',
  1156. '_ 2: 9 - 2:televisions',
  1157. '__ 3: 4 - 3:tube',
  1158. '__ 5: 6 - 4:lcd',
  1159. '__ 7: 8 - 5:plasma',
  1160. '_10:13 - 7:mp3',
  1161. '__11:12 - 8:flash',
  1162. '_14:15 - 9:cd',
  1163. '_16:17 - 10:radios',
  1164. '19:20 - 11:alien hardware',
  1165. '21:22 - 6:portable'
  1166. ];
  1167. $this->assertMpttValues($expected, $table);
  1168. }
  1169. /**
  1170. * Tests removing the root of a tree
  1171. *
  1172. * @return void
  1173. */
  1174. public function testRemoveRootFromTree()
  1175. {
  1176. $table = $this->table;
  1177. $entity = $table->get(1);
  1178. $this->assertSame($entity, $table->removeFromTree($entity));
  1179. $result = $table->find('threaded')->order('lft')->hydrate(false)->toArray();
  1180. $this->assertEquals(21, $entity->lft);
  1181. $this->assertEquals(22, $entity->rght);
  1182. $this->assertEquals(null, $entity->parent_id);
  1183. $expected = [
  1184. ' 1: 8 - 2:televisions',
  1185. '_ 2: 3 - 3:tube',
  1186. '_ 4: 5 - 4:lcd',
  1187. '_ 6: 7 - 5:plasma',
  1188. ' 9:18 - 6:portable',
  1189. '_10:13 - 7:mp3',
  1190. '__11:12 - 8:flash',
  1191. '_14:15 - 9:cd',
  1192. '_16:17 - 10:radios',
  1193. '19:20 - 11:alien hardware',
  1194. '21:22 - 1:electronics'
  1195. ];
  1196. $this->assertMpttValues($expected, $table);
  1197. }
  1198. /**
  1199. * Tests that using associations having tree fields in the schema
  1200. * does not generate SQL errors
  1201. *
  1202. * @return void
  1203. */
  1204. public function testFindPathWithAssociation()
  1205. {
  1206. $table = $this->table;
  1207. $other = TableRegistry::get('FriendlyTrees', [
  1208. 'table' => $table->table()
  1209. ]);
  1210. $table->hasOne('FriendlyTrees', [
  1211. 'foreignKey' => 'id'
  1212. ]);
  1213. $result = $table
  1214. ->find('children', ['for' => 1])
  1215. ->contain('FriendlyTrees')
  1216. ->toArray();
  1217. $this->assertCount(9, $result);
  1218. }
  1219. /**
  1220. * Tests getting the depth level of a node in the tree.
  1221. *
  1222. * @return void
  1223. */
  1224. public function testGetLevel()
  1225. {
  1226. $entity = $this->table->get(8);
  1227. $result = $this->table->getLevel($entity);
  1228. $this->assertEquals(3, $result);
  1229. $result = $this->table->getLevel($entity->id);
  1230. $this->assertEquals(3, $result);
  1231. $result = $this->table->getLevel(5);
  1232. $this->assertEquals(2, $result);
  1233. $result = $this->table->getLevel(99999);
  1234. $this->assertFalse($result);
  1235. }
  1236. /**
  1237. * Test setting level for new nodes
  1238. *
  1239. * @return void
  1240. */
  1241. public function testSetLevelNewNode()
  1242. {
  1243. $this->table->behaviors()->Tree->config('level', 'depth');
  1244. $entity = new Entity(['parent_id' => null, 'name' => 'Depth 0']);
  1245. $this->table->save($entity);
  1246. $entity = $this->table->get(12);
  1247. $this->assertEquals(0, $entity->depth);
  1248. $entity = new Entity(['parent_id' => 1, 'name' => 'Depth 1']);
  1249. $this->table->save($entity);
  1250. $entity = $this->table->get(13);
  1251. $this->assertEquals(1, $entity->depth);
  1252. $entity = new Entity(['parent_id' => 8, 'name' => 'Depth 4']);
  1253. $this->table->save($entity);
  1254. $entity = $this->table->get(14);
  1255. $this->assertEquals(4, $entity->depth);
  1256. }
  1257. /**
  1258. * Test setting level for existing nodes
  1259. *
  1260. * @return void
  1261. */
  1262. public function testSetLevelExistingNode()
  1263. {
  1264. $this->table->behaviors()->Tree->config('level', 'depth');
  1265. // Leaf node
  1266. $entity = $this->table->get(4);
  1267. $this->assertEquals(2, $entity->depth);
  1268. $this->table->save($entity);
  1269. $entity = $this->table->get(4);
  1270. $this->assertEquals(2, $entity->depth);
  1271. // Non leaf node so depth of descendents will also change
  1272. $entity = $this->table->get(6);
  1273. $this->assertEquals(1, $entity->depth);
  1274. $entity->parent_id = null;
  1275. $this->table->save($entity);
  1276. $entity = $this->table->get(6);
  1277. $this->assertEquals(0, $entity->depth);
  1278. $entity = $this->table->get(7);
  1279. $this->assertEquals(1, $entity->depth);
  1280. $entity = $this->table->get(8);
  1281. $this->assertEquals(2, $entity->depth);
  1282. $entity->parent_id = 6;
  1283. $this->table->save($entity);
  1284. $entity = $this->table->get(8);
  1285. $this->assertEquals(1, $entity->depth);
  1286. }
  1287. /**
  1288. * Assert MPTT values
  1289. *
  1290. * Custom assert method to make identifying the differences between expected
  1291. * and actual db state easier to identify.
  1292. *
  1293. * @param array $expected tree state to be expected
  1294. * @param \Cake\ORM\Table $table Table instance
  1295. * @param \Cake\ORM\Query $query Optional query object
  1296. * @return void
  1297. */
  1298. public function assertMpttValues($expected, $table, $query = null)
  1299. {
  1300. $query = $query ?: $table->find();
  1301. $primaryKey = $table->primaryKey();
  1302. if (is_array($primaryKey)) {
  1303. $primaryKey = $primaryKey[0];
  1304. }
  1305. $displayField = $table->displayField();
  1306. $options = [
  1307. 'valuePath' => function ($item, $key, $iterator) use ($primaryKey, $displayField) {
  1308. return sprintf(
  1309. '%s:%s - %s:%s',
  1310. str_pad($item->lft, 2, ' ', STR_PAD_LEFT),
  1311. str_pad($item->rght, 2, ' ', STR_PAD_LEFT),
  1312. str_pad($item->$primaryKey, 2, ' ', STR_PAD_LEFT),
  1313. $item->{$displayField}
  1314. );
  1315. }
  1316. ];
  1317. $result = array_values($query->find('treeList', $options)->toArray());
  1318. if (count($result) === count($expected)) {
  1319. $subExpected = array_diff($expected, $result);
  1320. if ($subExpected) {
  1321. $subResult = array_intersect_key($result, $subExpected);
  1322. $this->assertSame($subExpected, $subResult, 'Differences in the tree were found (lft:rght id:display-name)');
  1323. }
  1324. }
  1325. $this->assertSame($expected, $result, 'The tree is not the same (lft:rght id:display-name)');
  1326. }
  1327. }