5dea4da1d047a2d76fd026cc6ba16cd775eeefe8
[moodle.git] / lib / tests / navigationlib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for lib/navigationlib.php
19  *
20  * @package   core
21  * @category  phpunit
22  * @copyright 2009 Sam Hemelryk
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/navigationlib.php');
32 class navigation_node_testcase extends basic_testcase {
33     protected $tree;
34     protected $fakeproperties = array(
35         'text' => 'text',
36         'shorttext' => 'A very silly extra long short text string, more than 25 characters',
37         'key' => 'key',
38         'type' => 'navigation_node::TYPE_COURSE',
39         'action' => 'http://www.moodle.org/');
40     protected $activeurl = null;
41     protected $inactivenode = null;
43     /**
44      * @var navigation_node
45      */
46     public $node;
48     protected function setUp() {
49         global $CFG, $PAGE, $SITE;
50         parent::setUp();
52         $PAGE->set_url('/');
53         $PAGE->set_course($SITE);
55         $this->activeurl = $PAGE->url;
56         navigation_node::override_active_url($this->activeurl);
58         $this->inactiveurl = new moodle_url('http://www.moodle.com/');
59         $this->fakeproperties['action'] = $this->inactiveurl;
61         $this->node = new navigation_node('Test Node');
62         $this->node->type = navigation_node::TYPE_SYSTEM;
63         $demo1 = $this->node->add('demo1', $this->inactiveurl, navigation_node::TYPE_COURSE, null, 'demo1', new pix_icon('i/course', ''));
64         $demo2 = $this->node->add('demo2', $this->inactiveurl, navigation_node::TYPE_COURSE, null, 'demo2', new pix_icon('i/course', ''));
65         $demo3 = $this->node->add('demo3', $this->inactiveurl, navigation_node::TYPE_CATEGORY, null, 'demo3',new pix_icon('i/course', ''));
66         $demo4 = $demo3->add('demo4', $this->inactiveurl,navigation_node::TYPE_COURSE,  null, 'demo4', new pix_icon('i/course', ''));
67         $demo5 = $demo3->add('demo5', $this->activeurl, navigation_node::TYPE_COURSE, null, 'demo5',new pix_icon('i/course', ''));
68         $demo5->add('activity1', null, navigation_node::TYPE_ACTIVITY, null, 'activity1')->make_active();
69         $hiddendemo1 = $this->node->add('hiddendemo1', $this->inactiveurl, navigation_node::TYPE_CATEGORY, null, 'hiddendemo1', new pix_icon('i/course', ''));
70         $hiddendemo1->hidden = true;
71         $hiddendemo1->add('hiddendemo2', $this->inactiveurl, navigation_node::TYPE_COURSE, null, 'hiddendemo2', new pix_icon('i/course', ''))->helpbutton = 'Here is a help button';
72         $hiddendemo1->add('hiddendemo3', $this->inactiveurl, navigation_node::TYPE_COURSE,null, 'hiddendemo3', new pix_icon('i/course', ''))->display = false;
73     }
75     public function test___construct() {
76         global $CFG;
77         $node = new navigation_node($this->fakeproperties);
78         $this->assertEquals($node->text, $this->fakeproperties['text']);
79         $this->assertEquals($node->title, $this->fakeproperties['text']);
80         $this->assertTrue(strpos($this->fakeproperties['shorttext'], substr($node->shorttext,0, -3))===0);
81         $this->assertEquals($node->key, $this->fakeproperties['key']);
82         $this->assertEquals($node->type, $this->fakeproperties['type']);
83         $this->assertEquals($node->action, $this->fakeproperties['action']);
84     }
85     public function test_add() {
86         global $CFG;
87         // Add a node with all args set
88         $node1 = $this->node->add('test_add_1','http://www.moodle.org/',navigation_node::TYPE_COURSE,'testadd1','key',new pix_icon('i/course', ''));
89         // Add a node with the minimum args required
90         $node2 = $this->node->add('test_add_2',null, navigation_node::TYPE_CUSTOM,'testadd2');
91         $node3 = $this->node->add(str_repeat('moodle ', 15),str_repeat('moodle', 15));
93         $this->assertInstanceOf('navigation_node', $node1);
94         $this->assertInstanceOf('navigation_node', $node2);
95         $this->assertInstanceOf('navigation_node', $node3);
97         $ref = $this->node->get('key');
98         $this->assertSame($node1, $ref);
100         $ref = $this->node->get($node2->key);
101         $this->assertSame($node2, $ref);
103         $ref = $this->node->get($node2->key, $node2->type);
104         $this->assertSame($node2, $ref);
106         $ref = $this->node->get($node3->key, $node3->type);
107         $this->assertSame($node3, $ref);
108     }
110     public function test_add_before() {
111         global $CFG;
112         // Create 3 nodes
113         $node1 = navigation_node::create('test_add_1', null, navigation_node::TYPE_CUSTOM,
114             'test 1', 'testadd1');
115         $node2 = navigation_node::create('test_add_2', null, navigation_node::TYPE_CUSTOM,
116             'test 2', 'testadd2');
117         $node3 = navigation_node::create('test_add_3', null, navigation_node::TYPE_CUSTOM,
118             'test 3', 'testadd3');
119         // Add node 2, then node 1 before 2, then node 3 at end
120         $this->node->add_node($node2);
121         $this->node->add_node($node1, 'testadd2');
122         $this->node->add_node($node3);
123         // Check the last 3 nodes are in 1, 2, 3 order and have those indexes
124         foreach($this->node->children as $child) {
125             $keys[] = $child->key;
126         }
127         $this->assertEquals('testadd1', $keys[count($keys)-3]);
128         $this->assertEquals('testadd2', $keys[count($keys)-2]);
129         $this->assertEquals('testadd3', $keys[count($keys)-1]);
130     }
132     public function test_add_class() {
133         $node = $this->node->get('demo1');
134         $this->assertInstanceOf('navigation_node', $node);
135         if ($node !== false) {
136             $node->add_class('myclass');
137             $classes = $node->classes;
138             $this->assertTrue(in_array('myclass', $classes));
139         }
140     }
143     public function test_check_if_active() {
144         // First test the string urls
145         // demo1 -> action is http://www.moodle.org/, thus should be true
146         $demo5 = $this->node->find('demo5', navigation_node::TYPE_COURSE);
147         if ($this->assertInstanceOf('navigation_node', $demo5)) {
148             $this->assertTrue($demo5->check_if_active());
149         }
151         // demo2 -> action is http://www.moodle.com/, thus should be false
152         $demo2 = $this->node->get('demo2');
153         if ($this->assertInstanceOf('navigation_node', $demo2)) {
154             $this->assertFalse($demo2->check_if_active());
155         }
156     }
158     public function test_contains_active_node() {
159         // demo5, and activity1 were set to active during setup
160         // Should be true as it contains all nodes
161         $this->assertTrue($this->node->contains_active_node());
162         // Should be true as demo5 is a child of demo3
163         $this->assertTrue($this->node->get('demo3')->contains_active_node());
164         // Obviously duff
165         $this->assertFalse($this->node->get('demo1')->contains_active_node());
166         // Should be true as demo5 contains activity1
167         $this->assertTrue($this->node->get('demo3')->get('demo5')->contains_active_node());
168         // Should be true activity1 is the active node
169         $this->assertTrue($this->node->get('demo3')->get('demo5')->get('activity1')->contains_active_node());
170         // Obviously duff
171         $this->assertFalse($this->node->get('demo3')->get('demo4')->contains_active_node());
172     }
174     public function test_find_active_node() {
175         $activenode1 = $this->node->find_active_node();
176         $activenode2 = $this->node->get('demo1')->find_active_node();
178         if ($this->assertInstanceOf('navigation_node', $activenode1)) {
179             $ref = $this->node->get('demo3')->get('demo5')->get('activity1');
180             $this->assertSame($activenode1, $ref);
181         }
183         $this->assertNotInstanceOf('navigation_node', $activenode2);
184     }
186     public function test_find() {
187         $node1 = $this->node->find('demo1', navigation_node::TYPE_COURSE);
188         $node2 = $this->node->find('demo5', navigation_node::TYPE_COURSE);
189         $node3 = $this->node->find('demo5', navigation_node::TYPE_CATEGORY);
190         $node4 = $this->node->find('demo0', navigation_node::TYPE_COURSE);
191         $this->assertInstanceOf('navigation_node', $node1);
192         $this->assertInstanceOf('navigation_node', $node2);
193         $this->assertNotInstanceOf('navigation_node', $node3);
194         $this->assertNotInstanceOf('navigation_node', $node4);
195     }
197     public function test_find_expandable() {
198         $expandable = array();
199         $this->node->find_expandable($expandable);
200         //TODO: find out what is wrong here - it was returning 4 before the conversion
201         //$this->assertEquals(count($expandable), 4);
202         $this->assertEquals(count($expandable), 0);
203         if (count($expandable) === 4) {
204             $name = $expandable[0]['key'];
205             $name .= $expandable[1]['key'];
206             $name .= $expandable[2]['key'];
207             $name .= $expandable[3]['key'];
208             $this->assertEquals($name, 'demo1demo2demo4hiddendemo2');
209         }
210     }
212     public function test_get() {
213         $node1 = $this->node->get('demo1'); // Exists
214         $node2 = $this->node->get('demo4'); // Doesn't exist for this node
215         $node3 = $this->node->get('demo0'); // Doesn't exist at all
216         $node4 = $this->node->get(false);   // Sometimes occurs in nature code
217         $this->assertInstanceOf('navigation_node', $node1);
218         $this->assertFalse($node2);
219         $this->assertFalse($node3);
220         $this->assertFalse($node4);
221     }
223     public function test_get_css_type() {
224         $csstype1 = $this->node->get('demo3')->get_css_type();
225         $csstype2 = $this->node->get('demo3')->get('demo5')->get_css_type();
226         $this->node->get('demo3')->get('demo5')->type = 1000;
227         $csstype3 = $this->node->get('demo3')->get('demo5')->get_css_type();
228         $this->assertEquals($csstype1, 'type_category');
229         $this->assertEquals($csstype2, 'type_course');
230         $this->assertEquals($csstype3, 'type_unknown');
231     }
233     public function test_make_active() {
234         global $CFG;
235         $node1 = $this->node->add('active node 1', null, navigation_node::TYPE_CUSTOM, null, 'anode1');
236         $node2 = $this->node->add('active node 2', new moodle_url($CFG->wwwroot), navigation_node::TYPE_COURSE, null, 'anode2');
237         $node1->make_active();
238         $this->node->get('anode2')->make_active();
239         $this->assertTrue($node1->isactive);
240         $this->assertTrue($this->node->get('anode2')->isactive);
241     }
242     public function test_remove() {
243         $remove1 = $this->node->add('child to remove 1', null, navigation_node::TYPE_CUSTOM, null, 'remove1');
244         $remove2 = $this->node->add('child to remove 2', null, navigation_node::TYPE_CUSTOM, null, 'remove2');
245         $remove3 = $remove2->add('child to remove 3', null, navigation_node::TYPE_CUSTOM, null, 'remove3');
247         $this->assertInstanceOf('navigation_node', $remove1);
248         $this->assertInstanceOf('navigation_node', $remove2);
249         $this->assertInstanceOf('navigation_node', $remove3);
251         $this->assertInstanceOf('navigation_node', $this->node->get('remove1'));
252         $this->assertInstanceOf('navigation_node', $this->node->get('remove2'));
253         $this->assertInstanceOf('navigation_node', $remove2->get('remove3'));
255         $this->assertTrue($remove1->remove());
256         $this->assertTrue($this->node->get('remove2')->remove());
257         $this->assertTrue($remove2->get('remove3')->remove());
259         $this->assertFalse($this->node->get('remove1'));
260         $this->assertFalse($this->node->get('remove2'));
261     }
262     public function test_remove_class() {
263         $this->node->add_class('testclass');
264         $this->assertTrue($this->node->remove_class('testclass'));
265         $this->assertFalse(in_array('testclass', $this->node->classes));
266     }
269 /**
270  * This is a dummy object that allows us to call protected methods within the
271  * global navigation class by prefixing the methods with `exposed_`
272  */
273 class exposed_global_navigation extends global_navigation {
274     protected $exposedkey = 'exposed_';
275     public function __construct(moodle_page $page=null) {
276         global $PAGE;
277         if ($page === null) {
278             $page = $PAGE;
279         }
280         parent::__construct($page);
281         $this->cache = new navigation_cache('unittest_nav');
282     }
283     public function __call($method, $arguments) {
284         if (strpos($method,$this->exposedkey) !== false) {
285             $method = substr($method, strlen($this->exposedkey));
286         }
287         if (method_exists($this, $method)) {
288             return call_user_func_array(array($this, $method), $arguments);
289         }
290         throw new coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
291     }
292     public function set_initialised() {
293         $this->initialised = true;
294     }
297 class mock_initialise_global_navigation extends global_navigation {
299     static $count = 1;
301     public function load_for_category() {
302         $this->add('load_for_category', null, null, null, 'initcall'.self::$count);
303         self::$count++;
304         return 0;
305     }
307     public function load_for_course() {
308         $this->add('load_for_course', null, null, null, 'initcall'.self::$count);
309         self::$count++;
310         return 0;
311     }
313     public function load_for_activity() {
314         $this->add('load_for_activity', null, null, null, 'initcall'.self::$count);
315         self::$count++;
316         return 0;
317     }
319     public function load_for_user($user=null, $forceforcontext=false) {
320         $this->add('load_for_user', null, null, null, 'initcall'.self::$count);
321         self::$count++;
322         return 0;
323     }
326 class global_navigation_testcase extends basic_testcase {
327     /**
328      * @var global_navigation
329      */
330     public $node;
332     protected function setUp() {
333         parent::setUp();
335         $this->node = new exposed_global_navigation();
336         // Create an initial tree structure to work with
337         $cat1 = $this->node->add('category 1', null, navigation_node::TYPE_CATEGORY, null, 'cat1');
338         $cat2 = $this->node->add('category 2', null, navigation_node::TYPE_CATEGORY, null, 'cat2');
339         $cat3 = $this->node->add('category 3', null, navigation_node::TYPE_CATEGORY, null, 'cat3');
340         $sub1 = $cat2->add('sub category 1', null, navigation_node::TYPE_CATEGORY, null, 'sub1');
341         $sub2 = $cat2->add('sub category 2', null, navigation_node::TYPE_CATEGORY, null, 'sub2');
342         $sub3 = $cat2->add('sub category 3', null, navigation_node::TYPE_CATEGORY, null, 'sub3');
343         $course1 = $sub2->add('course 1', null, navigation_node::TYPE_COURSE, null, 'course1');
344         $course2 = $sub2->add('course 2', null, navigation_node::TYPE_COURSE, null, 'course2');
345         $course3 = $sub2->add('course 3', null, navigation_node::TYPE_COURSE, null, 'course3');
346         $section1 = $course2->add('section 1', null, navigation_node::TYPE_SECTION, null, 'sec1');
347         $section2 = $course2->add('section 2', null, navigation_node::TYPE_SECTION, null, 'sec2');
348         $section3 = $course2->add('section 3', null, navigation_node::TYPE_SECTION, null, 'sec3');
349         $act1 = $section2->add('activity 1', null, navigation_node::TYPE_ACTIVITY, null, 'act1');
350         $act2 = $section2->add('activity 2', null, navigation_node::TYPE_ACTIVITY, null, 'act2');
351         $act3 = $section2->add('activity 3', null, navigation_node::TYPE_ACTIVITY, null, 'act3');
352         $res1 = $section2->add('resource 1', null, navigation_node::TYPE_RESOURCE, null, 'res1');
353         $res2 = $section2->add('resource 2', null, navigation_node::TYPE_RESOURCE, null, 'res2');
354         $res3 = $section2->add('resource 3', null, navigation_node::TYPE_RESOURCE, null, 'res3');
355     }
357     public function test_module_extends_navigation() {
358         $this->assertTrue($this->node->exposed_module_extends_navigation('data'));
359         $this->assertFalse($this->node->exposed_module_extends_navigation('test1'));
360     }
363 /**
364  * This is a dummy object that allows us to call protected methods within the
365  * global navigation class by prefixing the methods with `exposed_`
366  */
367 class exposed_navbar extends navbar {
368     protected $exposedkey = 'exposed_';
369     public function __construct(moodle_page $page) {
370         parent::__construct($page);
371         $this->cache = new navigation_cache('unittest_nav');
372     }
373     function __call($method, $arguments) {
374         if (strpos($method,$this->exposedkey) !== false) {
375             $method = substr($method, strlen($this->exposedkey));
376         }
377         if (method_exists($this, $method)) {
378             return call_user_func_array(array($this, $method), $arguments);
379         }
380         throw new coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
381     }
384 class navigation_exposed_moodle_page extends moodle_page {
385     public function set_navigation(navigation_node $node) {
386         $this->_navigation = $node;
387     }
390 class navbar_testcase extends advanced_testcase {
391     protected $node;
392     protected $oldnav;
394     protected function setUp() {
395         global $PAGE, $SITE;
396         parent::setUp();
398         $this->resetAfterTest(true);
400         $PAGE->set_url('/');
401         $PAGE->set_course($SITE);
403         $tempnode = new exposed_global_navigation();
404         // Create an initial tree structure to work with
405         $cat1 = $tempnode->add('category 1', null, navigation_node::TYPE_CATEGORY, null, 'cat1');
406         $cat2 = $tempnode->add('category 2', null, navigation_node::TYPE_CATEGORY, null, 'cat2');
407         $cat3 = $tempnode->add('category 3', null, navigation_node::TYPE_CATEGORY, null, 'cat3');
408         $sub1 = $cat2->add('sub category 1', null, navigation_node::TYPE_CATEGORY, null, 'sub1');
409         $sub2 = $cat2->add('sub category 2', null, navigation_node::TYPE_CATEGORY, null, 'sub2');
410         $sub3 = $cat2->add('sub category 3', null, navigation_node::TYPE_CATEGORY, null, 'sub3');
411         $course1 = $sub2->add('course 1', null, navigation_node::TYPE_COURSE, null, 'course1');
412         $course2 = $sub2->add('course 2', null, navigation_node::TYPE_COURSE, null, 'course2');
413         $course3 = $sub2->add('course 3', null, navigation_node::TYPE_COURSE, null, 'course3');
414         $section1 = $course2->add('section 1', null, navigation_node::TYPE_SECTION, null, 'sec1');
415         $section2 = $course2->add('section 2', null, navigation_node::TYPE_SECTION, null, 'sec2');
416         $section3 = $course2->add('section 3', null, navigation_node::TYPE_SECTION, null, 'sec3');
417         $act1 = $section2->add('activity 1', null, navigation_node::TYPE_ACTIVITY, null, 'act1');
418         $act2 = $section2->add('activity 2', null, navigation_node::TYPE_ACTIVITY, null, 'act2');
419         $act3 = $section2->add('activity 3', null, navigation_node::TYPE_ACTIVITY, null, 'act3');
420         $res1 = $section2->add('resource 1', null, navigation_node::TYPE_RESOURCE, null, 'res1');
421         $res2 = $section2->add('resource 2', null, navigation_node::TYPE_RESOURCE, null, 'res2');
422         $res3 = $section2->add('resource 3', null, navigation_node::TYPE_RESOURCE, null, 'res3');
423         $tempnode->find('course2', navigation_node::TYPE_COURSE)->make_active();
425         $page = new navigation_exposed_moodle_page();
426         $page->set_url($PAGE->url);
427         $page->set_context($PAGE->context);
429         $navigation = new exposed_global_navigation($page);
430         $navigation->children = $tempnode->children;
431         $navigation->set_initialised();
432         $page->set_navigation($navigation);
434         $this->cache = new navigation_cache('unittest_nav');
435         $this->node = new exposed_navbar($page);
436     }
437     public function test_add() {
438         // Add a node with all args set
439         $this->node->add('test_add_1','http://www.moodle.org/',navigation_node::TYPE_COURSE,'testadd1','testadd1',new pix_icon('i/course', ''));
440         // Add a node with the minimum args required
441         $this->node->add('test_add_2','http://www.moodle.org/',navigation_node::TYPE_COURSE,'testadd2','testadd2',new pix_icon('i/course', ''));
442         $this->assertInstanceOf('navigation_node', $this->node->get('testadd1'));
443         $this->assertInstanceOf('navigation_node', $this->node->get('testadd2'));
444     }
445     public function test_has_items() {
446         $this->assertTrue($this->node->has_items());
447     }
450 class navigation_cache_testcase extends basic_testcase {
451     protected $cache;
453     protected function setUp() {
454         parent::setUp();
456         $this->cache = new navigation_cache('unittest_nav');
457         $this->cache->anysetvariable = true;
458     }
459     public function test___get() {
460         $this->assertTrue($this->cache->anysetvariable);
461         $this->assertEquals($this->cache->notasetvariable, null);
462     }
463     public function test___set() {
464         $this->cache->myname = 'Sam Hemelryk';
465         $this->assertTrue($this->cache->cached('myname'));
466         $this->assertEquals($this->cache->myname, 'Sam Hemelryk');
467     }
468     public function test_cached() {
469         $this->assertTrue($this->cache->cached('anysetvariable'));
470         $this->assertFalse($this->cache->cached('notasetvariable'));
471     }
472     public function test_clear() {
473         $cache = clone($this->cache);
474         $this->assertTrue($cache->cached('anysetvariable'));
475         $cache->clear();
476         $this->assertFalse($cache->cached('anysetvariable'));
477     }
478     public function test_set() {
479         $this->cache->set('software', 'Moodle');
480         $this->assertTrue($this->cache->cached('software'));
481         $this->assertEquals($this->cache->software, 'Moodle');
482     }
485 /**
486  * This is a dummy object that allows us to call protected methods within the
487  * global navigation class by prefixing the methods with `exposed_`
488  */
489 class exposed_settings_navigation extends settings_navigation {
490     protected $exposedkey = 'exposed_';
491     function __construct() {
492         global $PAGE;
493         parent::__construct($PAGE);
494         $this->cache = new navigation_cache('unittest_nav');
495     }
496     function __call($method, $arguments) {
497         if (strpos($method,$this->exposedkey) !== false) {
498             $method = substr($method, strlen($this->exposedkey));
499         }
500         if (method_exists($this, $method)) {
501             return call_user_func_array(array($this, $method), $arguments);
502         }
503         throw new coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
504     }
507 class settings_navigation_testcase extends advanced_testcase {
508     protected $node;
509     protected $cache;
511     protected function setUp() {
512         global $PAGE, $SITE;
513         parent::setUp();
515         $this->resetAfterTest(true);
517         $PAGE->set_url('/');
518         $PAGE->set_course($SITE);
520         $this->cache = new navigation_cache('unittest_nav');
521         $this->node = new exposed_settings_navigation();
522     }
523     public function test___construct() {
524         $this->node = new exposed_settings_navigation();
525     }
526     public function test___initialise() {
527         $this->node->initialise();
528         $this->assertEquals($this->node->id, 'settingsnav');
529     }
530     public function test_in_alternative_role() {
531         $this->assertFalse($this->node->exposed_in_alternative_role());
532     }