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/>. 16 17/** 18 * Tests for step. 19 * 20 * @package tool_usertours 21 * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25defined('MOODLE_INTERNAL') || die(); 26 27global $CFG; 28require_once($CFG->libdir . '/formslib.php'); 29 30/** 31 * Tests for step. 32 * 33 * @package tool_usertours 34 * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37class step_testcase extends advanced_testcase { 38 39 /** 40 * @var moodle_database 41 */ 42 protected $db; 43 44 /** 45 * Setup to store the DB reference. 46 */ 47 public function setUp(): void { 48 global $DB; 49 50 $this->db = $DB; 51 } 52 53 /** 54 * Tear down to restore the original DB reference. 55 */ 56 public function tearDown(): void { 57 global $DB; 58 59 $DB = $this->db; 60 } 61 62 /** 63 * Helper to mock the database. 64 * 65 * @return moodle_database 66 */ 67 public function mock_database() { 68 global $DB; 69 70 $DB = $this->getMockBuilder('moodle_database') 71 ->getMock() 72 ; 73 74 return $DB; 75 } 76 77 /** 78 * Data provider for the dirty value tester. 79 * 80 * @return array 81 */ 82 public function dirty_value_provider() { 83 return [ 84 'tourid' => [ 85 'tourid', 86 [1], 87 ], 88 'title' => [ 89 'title', 90 ['Lorem'], 91 ], 92 'content' => [ 93 'content', 94 ['Lorem'], 95 ], 96 'targettype' => [ 97 'targettype', 98 ['Lorem'], 99 ], 100 'targetvalue' => [ 101 'targetvalue', 102 ['Lorem'], 103 ], 104 'sortorder' => [ 105 'sortorder', 106 [1], 107 ], 108 'config' => [ 109 'config', 110 ['key', 'value'], 111 ], 112 ]; 113 } 114 115 /** 116 * Test the fetch function. 117 */ 118 public function test_fetch() { 119 $step = $this->getMockBuilder(\tool_usertours\step::class) 120 ->setMethods(['reload_from_record']) 121 ->getMock() 122 ; 123 124 $idretval = rand(1, 100); 125 $DB = $this->mock_database(); 126 $DB->method('get_record') 127 ->willReturn($idretval) 128 ; 129 130 $retval = rand(1, 100); 131 $step->expects($this->once()) 132 ->method('reload_from_record') 133 ->with($this->equalTo($idretval)) 134 ->wilLReturn($retval) 135 ; 136 137 $rc = new \ReflectionClass(\tool_usertours\step::class); 138 $rcm = $rc->getMethod('fetch'); 139 $rcm->setAccessible(true); 140 141 $id = rand(1, 100); 142 $this->assertEquals($retval, $rcm->invoke($step, 'fetch', $id)); 143 } 144 145 /** 146 * Test that setters mark things as dirty. 147 * 148 * @dataProvider dirty_value_provider 149 * @param string $name The key to update 150 * @param string $value The value to set 151 */ 152 public function test_dirty_values($name, $value) { 153 $step = new \tool_usertours\step(); 154 $method = 'set_' . $name; 155 call_user_func_array([$step, $method], $value); 156 157 $rc = new \ReflectionClass(\tool_usertours\step::class); 158 $rcp = $rc->getProperty('dirty'); 159 $rcp->setAccessible(true); 160 161 $this->assertTrue($rcp->getValue($step)); 162 } 163 164 /** 165 * Provider for is_first_step. 166 * 167 * @return array 168 */ 169 public function step_sortorder_provider() { 170 return [ 171 [0, 5, true, false], 172 [1, 5, false, false], 173 [4, 5, false, true], 174 ]; 175 } 176 177 /** 178 * Test is_first_step. 179 * 180 * @dataProvider step_sortorder_provider 181 * @param int $sortorder The sortorder to check 182 * @param int $count Unused in this function 183 * @param bool $isfirst Whether this is the first step 184 * @param bool $islast Whether this is the last step 185 */ 186 public function test_is_first_step($sortorder, $count, $isfirst, $islast) { 187 $step = $this->getMockBuilder(\tool_usertours\step::class) 188 ->setMethods(['get_sortorder']) 189 ->getMock(); 190 191 $step->expects($this->once()) 192 ->method('get_sortorder') 193 ->willReturn($sortorder) 194 ; 195 196 $this->assertEquals($isfirst, $step->is_first_step()); 197 } 198 199 /** 200 * Test is_last_step. 201 * 202 * @dataProvider step_sortorder_provider 203 * @param int $sortorder The sortorder to check 204 * @param int $count Total number of steps for this test 205 * @param bool $isfirst Whether this is the first step 206 * @param bool $islast Whether this is the last step 207 */ 208 public function test_is_last_step($sortorder, $count, $isfirst, $islast) { 209 $step = $this->getMockBuilder(\tool_usertours\step::class) 210 ->setMethods(['get_sortorder', 'get_tour']) 211 ->getMock(); 212 213 $tour = $this->getMockBuilder(\tool_usertours\tour::class) 214 ->setMethods(['count_steps']) 215 ->getMock(); 216 217 $step->expects($this->once()) 218 ->method('get_tour') 219 ->willReturn($tour) 220 ; 221 222 $tour->expects($this->once()) 223 ->method('count_steps') 224 ->willReturn($count) 225 ; 226 227 $step->expects($this->once()) 228 ->method('get_sortorder') 229 ->willReturn($sortorder) 230 ; 231 232 $this->assertEquals($islast, $step->is_last_step()); 233 } 234 235 /** 236 * Test get_config with no keys provided. 237 */ 238 public function test_get_config_no_keys() { 239 $step = new \tool_usertours\step(); 240 241 $rc = new \ReflectionClass(\tool_usertours\step::class); 242 $rcp = $rc->getProperty('config'); 243 $rcp->setAccessible(true); 244 245 $allvalues = (object) [ 246 'some' => 'value', 247 'another' => 42, 248 'key' => [ 249 'somethingelse', 250 ], 251 ]; 252 253 $rcp->setValue($step, $allvalues); 254 255 $this->assertEquals($allvalues, $step->get_config()); 256 } 257 258 /** 259 * Data provider for get_config. 260 * 261 * @return array 262 */ 263 public function get_config_provider() { 264 $allvalues = (object) [ 265 'some' => 'value', 266 'another' => 42, 267 'key' => [ 268 'somethingelse', 269 ], 270 ]; 271 272 $tourconfig = rand(1, 100); 273 $forcedconfig = rand(1, 100); 274 275 return [ 276 'No initial config' => [ 277 null, 278 null, 279 null, 280 $tourconfig, 281 false, 282 $forcedconfig, 283 (object) [], 284 ], 285 'All values' => [ 286 $allvalues, 287 null, 288 null, 289 $tourconfig, 290 false, 291 $forcedconfig, 292 $allvalues, 293 ], 294 'Valid string value' => [ 295 $allvalues, 296 'some', 297 null, 298 $tourconfig, 299 false, 300 $forcedconfig, 301 'value', 302 ], 303 'Valid array value' => [ 304 $allvalues, 305 'key', 306 null, 307 $tourconfig, 308 false, 309 $forcedconfig, 310 ['somethingelse'], 311 ], 312 'Invalid value' => [ 313 $allvalues, 314 'notavalue', 315 null, 316 $tourconfig, 317 false, 318 $forcedconfig, 319 $tourconfig, 320 ], 321 'Configuration value' => [ 322 $allvalues, 323 'placement', 324 null, 325 $tourconfig, 326 false, 327 $forcedconfig, 328 $tourconfig, 329 ], 330 'Invalid value with default' => [ 331 $allvalues, 332 'notavalue', 333 'somedefault', 334 $tourconfig, 335 false, 336 $forcedconfig, 337 'somedefault', 338 ], 339 'Value forced at target' => [ 340 $allvalues, 341 'somevalue', 342 'somedefault', 343 $tourconfig, 344 true, 345 $forcedconfig, 346 $forcedconfig, 347 ], 348 ]; 349 } 350 351 /** 352 * Test get_config with valid keys provided. 353 * 354 * @dataProvider get_config_provider 355 * @param object $values The config values 356 * @param string $key The key 357 * @param mixed $default The default value 358 * @param mixed $tourconfig The tour config 359 * @param bool $isforced Whether the setting is forced 360 * @param mixed $forcedvalue The example value 361 * @param mixed $expected The expected value 362 */ 363 public function test_get_config_valid_keys($values, $key, $default, $tourconfig, $isforced, $forcedvalue, $expected) { 364 $step = $this->getMockBuilder(\tool_usertours\step::class) 365 ->setMethods(['get_target', 'get_targettype', 'get_tour']) 366 ->getMock(); 367 368 $rc = new \ReflectionClass(\tool_usertours\step::class); 369 $rcp = $rc->getProperty('config'); 370 $rcp->setAccessible(true); 371 $rcp->setValue($step, $values); 372 373 $target = $this->getMockBuilder(\tool_usertours\local\target\base::class) 374 ->disableOriginalConstructor() 375 ->getMock() 376 ; 377 378 $target->expects($this->any()) 379 ->method('is_setting_forced') 380 ->willReturn($isforced) 381 ; 382 383 $target->expects($this->any()) 384 ->method('get_forced_setting_value') 385 ->with($this->equalTo($key)) 386 ->willReturn($forcedvalue) 387 ; 388 389 $step->expects($this->any()) 390 ->method('get_targettype') 391 ->willReturn('type') 392 ; 393 394 $step->expects($this->any()) 395 ->method('get_target') 396 ->willReturn($target) 397 ; 398 399 $tour = $this->getMockBuilder(\tool_usertours\tour::class) 400 ->getMock() 401 ; 402 403 $tour->expects($this->any()) 404 ->method('get_config') 405 ->willReturn($tourconfig) 406 ; 407 408 $step->expects($this->any()) 409 ->method('get_tour') 410 ->willReturn($tour) 411 ; 412 413 $this->assertEquals($expected, $step->get_config($key, $default)); 414 } 415 416 /** 417 * Data provider for set_config. 418 */ 419 public function set_config_provider() { 420 $allvalues = (object) [ 421 'some' => 'value', 422 'another' => 42, 423 'key' => [ 424 'somethingelse', 425 ], 426 ]; 427 428 $randvalue = rand(1, 100); 429 430 $provider = []; 431 432 $newvalues = $allvalues; 433 $newvalues->some = 'unset'; 434 $provider['Unset an existing value'] = [ 435 $allvalues, 436 'some', 437 null, 438 $newvalues, 439 ]; 440 441 $newvalues = $allvalues; 442 $newvalues->some = $randvalue; 443 $provider['Set an existing value'] = [ 444 $allvalues, 445 'some', 446 $randvalue, 447 $newvalues, 448 ]; 449 450 $provider['Set a new value'] = [ 451 $allvalues, 452 'newkey', 453 $randvalue, 454 (object) array_merge((array) $allvalues, ['newkey' => $randvalue]), 455 ]; 456 457 return $provider; 458 } 459 460 /** 461 * Test that set_config works in the anticipated fashion. 462 * 463 * @dataProvider set_config_provider 464 * @param mixed $initialvalues The inital value to set 465 * @param string $key The key to test 466 * @param mixed $newvalue The new value to set 467 * @param mixed $expected The expected value 468 */ 469 public function test_set_config($initialvalues, $key, $newvalue, $expected) { 470 $step = new \tool_usertours\step(); 471 472 $rc = new \ReflectionClass(\tool_usertours\step::class); 473 $rcp = $rc->getProperty('config'); 474 $rcp->setAccessible(true); 475 $rcp->setValue($step, $initialvalues); 476 477 $target = $this->getMockBuilder(\tool_usertours\local\target\base::class) 478 ->disableOriginalConstructor() 479 ->getMock() 480 ; 481 482 $target->expects($this->any()) 483 ->method('is_setting_forced') 484 ->willReturn(false) 485 ; 486 487 $step->set_config($key, $newvalue); 488 489 $this->assertEquals($expected, $rcp->getValue($step)); 490 } 491 492 /** 493 * Ensure that non-dirty tours are not persisted. 494 */ 495 public function test_persist_non_dirty() { 496 $step = $this->getMockBuilder(\tool_usertours\step::class) 497 ->setMethods([ 498 'to_record', 499 'reload', 500 ]) 501 ->getMock() 502 ; 503 504 $step->expects($this->never()) 505 ->method('to_record') 506 ; 507 508 $step->expects($this->never()) 509 ->method('reload') 510 ; 511 512 $this->assertSame($step, $step->persist()); 513 } 514 515 /** 516 * Ensure that new dirty steps are persisted. 517 */ 518 public function test_persist_dirty_new() { 519 // Mock the database. 520 $DB = $this->mock_database(); 521 $DB->expects($this->once()) 522 ->method('insert_record') 523 ->willReturn(42) 524 ; 525 526 // Mock the tour. 527 $step = $this->getMockBuilder(\tool_usertours\step::class) 528 ->setMethods([ 529 'to_record', 530 'calculate_sortorder', 531 'reload', 532 ]) 533 ->getMock() 534 ; 535 536 $step->expects($this->once()) 537 ->method('to_record') 538 ->willReturn((object)['id' => 42]); 539 ; 540 541 $step->expects($this->once()) 542 ->method('calculate_sortorder') 543 ; 544 545 $step->expects($this->once()) 546 ->method('reload') 547 ; 548 549 $rc = new \ReflectionClass(\tool_usertours\step::class); 550 $rcp = $rc->getProperty('dirty'); 551 $rcp->setAccessible(true); 552 $rcp->setValue($step, true); 553 554 $tour = $this->createMock(\tool_usertours\tour::class); 555 $rcp = $rc->getProperty('tour'); 556 $rcp->setAccessible(true); 557 $rcp->setValue($step, $tour); 558 559 $this->assertSame($step, $step->persist()); 560 } 561 562 /** 563 * Ensure that new non-dirty, forced steps are persisted. 564 */ 565 public function test_persist_force_new() { 566 global $DB; 567 568 // Mock the database. 569 $DB = $this->mock_database(); 570 $DB->expects($this->once()) 571 ->method('insert_record') 572 ->willReturn(42) 573 ; 574 575 // Mock the tour. 576 $step = $this->getMockBuilder(\tool_usertours\step::class) 577 ->setMethods([ 578 'to_record', 579 'calculate_sortorder', 580 'reload', 581 ]) 582 ->getMock() 583 ; 584 585 $step->expects($this->once()) 586 ->method('to_record') 587 ->willReturn((object)['id' => 42]); 588 ; 589 590 $step->expects($this->once()) 591 ->method('calculate_sortorder') 592 ; 593 594 $step->expects($this->once()) 595 ->method('reload') 596 ; 597 598 $tour = $this->createMock(\tool_usertours\tour::class); 599 $rc = new \ReflectionClass(\tool_usertours\step::class); 600 $rcp = $rc->getProperty('tour'); 601 $rcp->setAccessible(true); 602 $rcp->setValue($step, $tour); 603 604 $this->assertSame($step, $step->persist(true)); 605 } 606 607 /** 608 * Ensure that existing dirty steps are persisted. 609 */ 610 public function test_persist_dirty_existing() { 611 // Mock the database. 612 $DB = $this->mock_database(); 613 $DB->expects($this->once()) 614 ->method('update_record') 615 ; 616 617 // Mock the tour. 618 $step = $this->getMockBuilder(\tool_usertours\step::class) 619 ->setMethods([ 620 'to_record', 621 'calculate_sortorder', 622 'reload', 623 ]) 624 ->getMock() 625 ; 626 627 $step->expects($this->once()) 628 ->method('to_record') 629 ->willReturn((object)['id' => 42]); 630 ; 631 632 $step->expects($this->never()) 633 ->method('calculate_sortorder') 634 ; 635 636 $step->expects($this->once()) 637 ->method('reload') 638 ; 639 640 $rc = new \ReflectionClass(\tool_usertours\step::class); 641 $rcp = $rc->getProperty('id'); 642 $rcp->setAccessible(true); 643 $rcp->setValue($step, 42); 644 645 $rcp = $rc->getProperty('dirty'); 646 $rcp->setAccessible(true); 647 $rcp->setValue($step, true); 648 649 $tour = $this->createMock(\tool_usertours\tour::class); 650 $rcp = $rc->getProperty('tour'); 651 $rcp->setAccessible(true); 652 $rcp->setValue($step, $tour); 653 654 $this->assertSame($step, $step->persist()); 655 } 656 657 /** 658 * Ensure that existing non-dirty, forced steps are persisted. 659 */ 660 public function test_persist_force_existing() { 661 global $DB; 662 663 // Mock the database. 664 $DB = $this->mock_database(); 665 $DB->expects($this->once()) 666 ->method('update_record') 667 ; 668 669 // Mock the tour. 670 $step = $this->getMockBuilder(\tool_usertours\step::class) 671 ->setMethods([ 672 'to_record', 673 'calculate_sortorder', 674 'reload', 675 ]) 676 ->getMock() 677 ; 678 679 $step->expects($this->once()) 680 ->method('to_record') 681 ->willReturn((object)['id' => 42]); 682 ; 683 684 $step->expects($this->never()) 685 ->method('calculate_sortorder') 686 ; 687 688 $step->expects($this->once()) 689 ->method('reload') 690 ; 691 692 $rc = new \ReflectionClass(\tool_usertours\step::class); 693 $rcp = $rc->getProperty('id'); 694 $rcp->setAccessible(true); 695 $rcp->setValue($step, 42); 696 697 $tour = $this->createMock(\tool_usertours\tour::class); 698 $rcp = $rc->getProperty('tour'); 699 $rcp->setAccessible(true); 700 $rcp->setValue($step, $tour); 701 702 $this->assertSame($step, $step->persist(true)); 703 } 704 705 /** 706 * Check that a tour which has never been persisted is removed correctly. 707 */ 708 public function test_remove_non_persisted() { 709 $step = $this->getMockBuilder(\tool_usertours\step::class) 710 ->setMethods(null) 711 ->getMock() 712 ; 713 714 // Mock the database. 715 $DB = $this->mock_database(); 716 $DB->expects($this->never()) 717 ->method('delete_records') 718 ; 719 720 $this->assertNull($step->remove()); 721 } 722 723 /** 724 * Check that a tour which has been persisted is removed correctly. 725 */ 726 public function test_remove_persisted() { 727 $id = rand(1, 100); 728 729 $tour = $this->getMockBuilder(\tool_usertours\tour::class) 730 ->setMethods([ 731 'reset_step_sortorder', 732 ]) 733 ->getMock() 734 ; 735 736 $tour->expects($this->once()) 737 ->method('reset_step_sortorder') 738 ; 739 740 $step = $this->getMockBuilder(\tool_usertours\step::class) 741 ->setMethods([ 742 'get_tour', 743 ]) 744 ->getMock() 745 ; 746 747 $step->expects($this->once()) 748 ->method('get_tour') 749 ->willReturn($tour) 750 ; 751 752 // Mock the database. 753 $DB = $this->mock_database(); 754 $DB->expects($this->once()) 755 ->method('delete_records') 756 ->with($this->equalTo('tool_usertours_steps'), $this->equalTo(['id' => $id])) 757 ; 758 759 $rc = new \ReflectionClass(\tool_usertours\step::class); 760 $rcp = $rc->getProperty('id'); 761 $rcp->setAccessible(true); 762 $rcp->setValue($step, $id); 763 764 $this->assertEquals($id, $step->get_id()); 765 $this->assertNull($step->remove()); 766 } 767 768 /** 769 * Data provider for the get_ tests. 770 * 771 * @return array 772 */ 773 public function getter_provider() { 774 return [ 775 'id' => [ 776 'id', 777 rand(1, 100), 778 ], 779 'tourid' => [ 780 'tourid', 781 rand(1, 100), 782 ], 783 'title' => [ 784 'title', 785 'Lorem', 786 ], 787 'content' => [ 788 'content', 789 'Lorem', 790 ], 791 'targettype' => [ 792 'targettype', 793 'Lorem', 794 ], 795 'targetvalue' => [ 796 'targetvalue', 797 'Lorem', 798 ], 799 'sortorder' => [ 800 'sortorder', 801 rand(1, 100), 802 ], 803 ]; 804 } 805 806 /** 807 * Test that getters return the configured value. 808 * 809 * @dataProvider getter_provider 810 * @param string $key The key to test 811 * @param mixed $value The expected value 812 */ 813 public function test_getters($key, $value) { 814 $step = new \tool_usertours\step(); 815 816 $rc = new \ReflectionClass(\tool_usertours\step::class); 817 818 $rcp = $rc->getProperty($key); 819 $rcp->setAccessible(true); 820 $rcp->setValue($step, $value); 821 822 $getter = 'get_' . $key; 823 824 $this->assertEquals($value, $step->$getter()); 825 } 826 827 /** 828 * Data Provider for get_string_from_input. 829 * 830 * @return array 831 */ 832 public function get_string_from_input_provider() { 833 return [ 834 'Text' => [ 835 'example', 836 'example', 837 ], 838 'Text which looks like a langstring' => [ 839 'example,fakecomponent', 840 'example,fakecomponent', 841 ], 842 'Text which is a langstring' => [ 843 'administration,core', 844 'Administration', 845 ], 846 'Text which is a langstring but uses "moodle" instead of "core"' => [ 847 'administration,moodle', 848 'Administration', 849 ], 850 'Text which is a langstring, but with extra whitespace' => [ 851 ' administration,moodle ', 852 'Administration', 853 ], 854 'Looks like a langstring, but has incorrect space around comma' => [ 855 'administration , moodle', 856 'administration , moodle', 857 ], 858 ]; 859 } 860 861 /** 862 * Ensure that the get_string_from_input function returns langstring strings correctly. 863 * 864 * @dataProvider get_string_from_input_provider 865 * @param string $string The string to test 866 * @param string $expected The expected result 867 */ 868 public function test_get_string_from_input($string, $expected) { 869 $this->assertEquals($expected, \tool_usertours\step::get_string_from_input($string)); 870 } 871} 872