Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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 tour.
      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  
      25  defined('MOODLE_INTERNAL') || die();
      26  
      27  global $CFG;
      28  require_once($CFG->libdir . '/formslib.php');
      29  
      30  use tool_usertours\tour;
      31  
      32  /**
      33   * Tests for tour.
      34   *
      35   * @package    tool_usertours
      36   * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
      37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      38   */
      39  class tour_testcase extends advanced_testcase {
      40  
      41      /**
      42       * @var moodle_database
      43       */
      44      protected $db;
      45  
      46      /**
      47       * Setup to store the DB reference.
      48       */
      49      public function setUp(): void {
      50          global $DB;
      51  
      52          $this->db = $DB;
      53      }
      54  
      55      /**
      56       * Tear down to restore the original DB reference.
      57       */
      58      public function tearDown(): void {
      59          global $DB;
      60  
      61          $DB = $this->db;
      62      }
      63  
      64      /**
      65       * Helper to mock the database.
      66       *
      67       * @return \PHPUnit\Framework\MockObject\MockObject
      68       */
      69      public function mock_database() {
      70          global $DB;
      71  
      72          $DB = $this->getMockBuilder(\moodle_database::class)
      73              ->getMock()
      74              ;
      75  
      76          return $DB;
      77      }
      78  
      79      /**
      80       * Data provider for the dirty value tester.
      81       *
      82       * @return array
      83       */
      84      public function dirty_value_provider() {
      85          return [
      86                  'name' => [
      87                          'name',
      88                          ['Lorem'],
      89                      ],
      90                  'description' => [
      91                          'description',
      92                          ['Lorem'],
      93                      ],
      94                  'pathmatch' => [
      95                          'pathmatch',
      96                          ['Lorem'],
      97                      ],
      98                  'enabled' => [
      99                          'enabled',
     100                          ['Lorem'],
     101                      ],
     102                  'sortorder' => [
     103                          'sortorder',
     104                          [1],
     105                      ],
     106                  'config' => [
     107                          'config',
     108                          ['key', 'value'],
     109                      ],
     110              ];
     111      }
     112  
     113      /**
     114       * Test that setters mark things as dirty.
     115       *
     116       * @dataProvider dirty_value_provider
     117       * @param   string  $name           The name of the key being tested
     118       * @param   mixed   $value          The value being set
     119       */
     120      public function test_dirty_values($name, $value) {
     121          $tour = new \tool_usertours\tour();
     122          $method = 'set_' . $name;
     123          call_user_func_array([$tour, $method], $value);
     124  
     125          $rc = new \ReflectionClass(\tool_usertours\tour::class);
     126          $rcp = $rc->getProperty('dirty');
     127          $rcp->setAccessible(true);
     128  
     129          $this->assertTrue($rcp->getValue($tour));
     130      }
     131  
     132      /**
     133       * Data provider for the get_ tests.
     134       *
     135       * @return array
     136       */
     137      public function getter_provider() {
     138          return [
     139                  'id' => [
     140                          'id',
     141                          rand(1, 100),
     142                      ],
     143                  'name' => [
     144                          'name',
     145                          'Lorem',
     146                      ],
     147                  'description' => [
     148                          'description',
     149                          'Lorem',
     150                      ],
     151                  'pathmatch' => [
     152                          'pathmatch',
     153                          'Lorem',
     154                      ],
     155                  'enabled' => [
     156                          'enabled',
     157                          'Lorem',
     158                      ],
     159                  'sortorder' => [
     160                          'sortorder',
     161                          rand(1, 100),
     162                      ],
     163                  'config' => [
     164                          'config',
     165                          ['key', 'value'],
     166                      ],
     167              ];
     168      }
     169  
     170      /**
     171       * Test that getters return the configured value.
     172       *
     173       * @dataProvider getter_provider
     174       * @param   string  $key            The name of the key being tested
     175       * @param   mixed   $value          The value being set
     176       */
     177      public function test_getters($key, $value) {
     178          $tour = new \tool_usertours\tour();
     179  
     180          $rc = new \ReflectionClass(tour::class);
     181  
     182          $rcp = $rc->getProperty($key);
     183          $rcp->setAccessible(true);
     184          $rcp->setValue($tour, $value);
     185  
     186          $getter = 'get_' . $key;
     187  
     188          $this->assertEquals($value, $tour->$getter());
     189      }
     190  
     191      /**
     192       * Ensure that non-dirty tours are not persisted.
     193       */
     194      public function test_persist_non_dirty() {
     195          $tour = $this->getMockBuilder(tour::class)
     196              ->onlyMethods(['to_record'])
     197              ->getMock()
     198              ;
     199  
     200          $tour->expects($this->never())
     201              ->method('to_record')
     202              ;
     203  
     204          $this->assertSame($tour, $tour->persist());
     205      }
     206  
     207      /**
     208       * Ensure that new dirty tours are persisted.
     209       */
     210      public function test_persist_dirty_new() {
     211          // Mock the database.
     212          $DB = $this->mock_database();
     213  
     214          $DB->expects($this->never())
     215              ->method('update_record')
     216              ;
     217  
     218          $id = rand(1, 100);
     219          $DB->expects($this->once())
     220              ->method('insert_record')
     221              ->willReturn($id)
     222              ;
     223  
     224          // Mock the tour.
     225          $tour = $this->getMockBuilder(tour::class)
     226              ->onlyMethods([
     227                      'to_record',
     228                      'reload',
     229                  ])
     230              ->getMock()
     231              ;
     232  
     233          $tour->expects($this->once())
     234              ->method('to_record')
     235              ;
     236  
     237          $tour->expects($this->once())
     238              ->method('reload')
     239              ;
     240  
     241          $rc = new \ReflectionClass(tour::class);
     242  
     243          $rcp = $rc->getProperty('dirty');
     244          $rcp->setAccessible(true);
     245          $rcp->setValue($tour, true);
     246  
     247          $this->assertSame($tour, $tour->persist());
     248  
     249          $rcp = $rc->getProperty('id');
     250          $rcp->setAccessible(true);
     251          $this->assertEquals($id, $rcp->getValue($tour));
     252      }
     253  
     254      /**
     255       * Ensure that non-dirty, forced tours are persisted.
     256       */
     257      public function test_persist_force_new() {
     258          global $DB;
     259  
     260          // Mock the database.
     261          $DB = $this->mock_database();
     262  
     263          $DB->expects($this->never())
     264              ->method('update_record')
     265              ;
     266  
     267          $id = rand(1, 100);
     268          $DB->expects($this->once())
     269              ->method('insert_record')
     270              ->willReturn($id)
     271              ;
     272  
     273          // Mock the tour.
     274          $tour = $this->getMockBuilder(tour::class)
     275              ->onlyMethods([
     276                      'to_record',
     277                      'reload',
     278                  ])
     279              ->getMock()
     280              ;
     281  
     282          $tour->expects($this->once())
     283              ->method('to_record')
     284              ;
     285  
     286          $tour->expects($this->once())
     287              ->method('reload')
     288              ;
     289  
     290          $this->assertSame($tour, $tour->persist(true));
     291  
     292          $rc = new \ReflectionClass(tour::class);
     293          $rcp = $rc->getProperty('id');
     294          $rcp->setAccessible(true);
     295          $this->assertEquals($id, $rcp->getValue($tour));
     296      }
     297  
     298      /**
     299       * Ensure that dirty tours are persisted.
     300       */
     301      public function test_persist_dirty_existing() {
     302          // Mock the database.
     303          $DB = $this->mock_database();
     304          $DB->expects($this->once())
     305              ->method('update_record')
     306              ->willReturn($this->returnSelf())
     307              ;
     308  
     309          $DB->expects($this->never())
     310              ->method('insert_record')
     311              ;
     312  
     313          // Mock the tour.
     314          $tour = $this->getMockBuilder(tour::class)
     315              ->onlyMethods([
     316                      'to_record',
     317                      'reload',
     318                  ])
     319              ->getMock()
     320              ;
     321  
     322          $tour->expects($this->once())
     323              ->method('to_record')
     324              ;
     325  
     326          $tour->expects($this->once())
     327              ->method('reload')
     328              ;
     329  
     330          $rc = new \ReflectionClass(tour::class);
     331  
     332          $rcp = $rc->getProperty('id');
     333          $rcp->setAccessible(true);
     334          $rcp->setValue($tour, 42);
     335  
     336          $rcp = $rc->getProperty('dirty');
     337          $rcp->setAccessible(true);
     338          $rcp->setValue($tour, true);
     339  
     340          $this->assertSame($tour, $tour->persist());
     341      }
     342  
     343      /**
     344       * Ensure that non-dirty, forced tours are persisted.
     345       */
     346      public function test_persist_force() {
     347          global $DB;
     348  
     349          // Mock the database.
     350          $DB = $this->mock_database();
     351  
     352          $DB->expects($this->once())
     353              ->method('update_record')
     354              ->willReturn($this->returnSelf())
     355              ;
     356  
     357          $DB->expects($this->never())
     358              ->method('insert_record')
     359              ;
     360  
     361          // Mock the tour.
     362          $tour = $this->getMockBuilder(tour::class)
     363              ->onlyMethods([
     364                      'to_record',
     365                      'reload',
     366                  ])
     367              ->getMock()
     368              ;
     369  
     370          $tour->expects($this->once())
     371              ->method('to_record')
     372              ;
     373  
     374          $tour->expects($this->once())
     375              ->method('reload')
     376              ;
     377  
     378          $rc = new \ReflectionClass(tour::class);
     379  
     380          $rcp = $rc->getProperty('id');
     381          $rcp->setAccessible(true);
     382          $rcp->setValue($tour, 42);
     383  
     384          $rcp = $rc->getProperty('dirty');
     385          $rcp->setAccessible(true);
     386          $rcp->setValue($tour, true);
     387  
     388          $this->assertSame($tour, $tour->persist(true));
     389      }
     390  
     391      /**
     392       * Test setting config.
     393       */
     394      public function test_set_config() {
     395          $tour = new \tool_usertours\tour();
     396  
     397          $tour->set_config('key', 'value');
     398          $tour->set_config('another', [
     399                  'foo' => 'bar',
     400              ]);
     401  
     402          $rc = new \ReflectionClass(tour::class);
     403          $rcp = $rc->getProperty('config');
     404          $rcp->setAccessible(true);
     405          $this->assertEquals((object) [
     406                  'key' => 'value',
     407                  'another' => [
     408                      'foo' => 'bar',
     409                  ],
     410              ], $rcp->getValue($tour));
     411      }
     412  
     413      /**
     414       * Test get_config with no keys provided.
     415       */
     416      public function test_get_config_no_keys() {
     417          $tour = new \tool_usertours\tour();
     418  
     419          $rc = new \ReflectionClass(tour::class);
     420          $rcp = $rc->getProperty('config');
     421          $rcp->setAccessible(true);
     422  
     423          $allvalues = (object) [
     424                  'some' => 'value',
     425                  'another' => 42,
     426                  'key' => [
     427                      'somethingelse',
     428                  ],
     429              ];
     430  
     431          $rcp->setValue($tour, $allvalues);
     432  
     433          $this->assertEquals($allvalues, $tour->get_config());
     434      }
     435  
     436      /**
     437       * Data provider for get_config.
     438       *
     439       * @return array
     440       */
     441      public function get_config_provider() {
     442          $allvalues = (object) [
     443                  'some' => 'value',
     444                  'another' => 42,
     445                  'key' => [
     446                      'somethingelse',
     447                  ],
     448              ];
     449  
     450          return [
     451                  'No nitial config' => [
     452                          null,
     453                          null,
     454                          null,
     455                          (object) [],
     456                      ],
     457                  'All values' => [
     458                          $allvalues,
     459                          null,
     460                          null,
     461                          $allvalues,
     462                      ],
     463                  'Valid string value' => [
     464                          $allvalues,
     465                          'some',
     466                          null,
     467                          'value',
     468                      ],
     469                  'Valid array value' => [
     470                          $allvalues,
     471                          'key',
     472                          null,
     473                          ['somethingelse'],
     474                      ],
     475                  'Invalid value' => [
     476                          $allvalues,
     477                          'notavalue',
     478                          null,
     479                          null,
     480                      ],
     481                  'Configuration value' => [
     482                          $allvalues,
     483                          'placement',
     484                          null,
     485                          \tool_usertours\configuration::get_default_value('placement'),
     486                      ],
     487                  'Invalid value with default' => [
     488                          $allvalues,
     489                          'notavalue',
     490                          'somedefault',
     491                          'somedefault',
     492                      ],
     493              ];
     494      }
     495  
     496      /**
     497       * Test get_config with valid keys provided.
     498       *
     499       * @dataProvider get_config_provider
     500       * @param   object  $values     The config values
     501       * @param   string  $key        The key
     502       * @param   mixed   $default    The default value
     503       * @param   mixed   $expected   The expected value
     504       */
     505      public function test_get_config_valid_keys($values, $key, $default, $expected) {
     506          $tour = new \tool_usertours\tour();
     507  
     508          $rc = new \ReflectionClass(tour::class);
     509          $rcp = $rc->getProperty('config');
     510          $rcp->setAccessible(true);
     511          $rcp->setValue($tour, $values);
     512  
     513          $this->assertEquals($expected, $tour->get_config($key, $default));
     514      }
     515  
     516      /**
     517       * Check that a tour which has never been persisted is removed correctly.
     518       */
     519      public function test_remove_non_persisted() {
     520          $tour = $this->getMockBuilder(tour::class)
     521              ->onlyMethods([
     522                      'get_steps',
     523                  ])
     524              ->getMock()
     525              ;
     526  
     527          $tour->expects($this->never())
     528              ->method('get_steps')
     529              ;
     530  
     531          // Mock the database.
     532          $DB = $this->mock_database();
     533          $DB->expects($this->never())
     534              ->method('delete_records')
     535              ;
     536  
     537          $this->assertNull($tour->remove());
     538      }
     539  
     540      /**
     541       * Check that a tour which has been persisted is removed correctly.
     542       */
     543      public function test_remove_persisted() {
     544          $id = rand(1, 100);
     545  
     546          $tour = $this->getMockBuilder(tour::class)
     547              ->onlyMethods([
     548                      'get_steps',
     549                  ])
     550              ->getMock()
     551              ;
     552  
     553          $rc = new \ReflectionClass(tour::class);
     554          $rcp = $rc->getProperty('id');
     555          $rcp->setAccessible(true);
     556          $rcp->setValue($tour, $id);
     557  
     558          $step = $this->getMockBuilder(\tool_usertours\step::class)
     559              ->onlyMethods([
     560                      'remove',
     561                  ])
     562              ->getMock()
     563              ;
     564  
     565          $tour->expects($this->once())
     566              ->method('get_steps')
     567              ->willReturn([$step])
     568              ;
     569  
     570          // Mock the database.
     571          $DB = $this->mock_database();
     572  
     573          $DB->expects($this->exactly(3))
     574              ->method('delete_records')
     575              ->withConsecutive(
     576                  [$this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id])],
     577                  [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_LAST_COMPLETED_BY_USER . $id])],
     578                  [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_REQUESTED_BY_USER . $id])]
     579              )
     580              ->willReturn(null)
     581              ;
     582  
     583          $DB->expects($this->once())
     584              ->method('get_records')
     585              ->with($this->equalTo('tool_usertours_tours'), $this->equalTo(null))
     586              ->willReturn([])
     587              ;
     588  
     589          $this->assertNull($tour->remove());
     590      }
     591  
     592      /**
     593       * Teset that sortorder is reset according to sortorder with values from 0.
     594       */
     595      public function test_reset_step_sortorder() {
     596          $tour = new \tool_usertours\tour();
     597  
     598          $mockdata = [];
     599          for ($i = 4; $i >= 0; $i--) {
     600              $id = rand($i * 10, ($i * 10) + 9);
     601              $mockdata[] = (object) ['id' => $id];
     602              $expectations[] = [$this->equalTo('tool_usertours_steps'), $this->equalTo('sortorder'), 4 - $i, ['id' => $id]];
     603          }
     604  
     605          // Mock the database.
     606          $DB = $this->mock_database();
     607          $DB->expects($this->once())
     608              ->method('get_records')
     609              ->willReturn($mockdata)
     610              ;
     611  
     612          $setfield = $DB->expects($this->exactly(5))
     613              ->method('set_field')
     614              ;
     615          call_user_func_array([$setfield, 'withConsecutive'], $expectations);
     616  
     617          $tour->reset_step_sortorder();
     618      }
     619  
     620      /**
     621       * Test that a disabled tour should never be shown to users.
     622       */
     623      public function test_should_show_for_user_disabled() {
     624          $tour = new \tool_usertours\tour();
     625          $tour->set_enabled(false);
     626  
     627          $this->assertFalse($tour->should_show_for_user());
     628      }
     629  
     630      /**
     631       * Provider for should_show_for_user.
     632       *
     633       * @return array
     634       */
     635      public function should_show_for_user_provider() {
     636          $time = time();
     637          return [
     638                  'Not seen by user at all' => [
     639                          null,
     640                          null,
     641                          null,
     642                          true,
     643                      ],
     644                  'Completed by user before majorupdatetime' => [
     645                          $time - DAYSECS,
     646                          null,
     647                          $time,
     648                          true,
     649                      ],
     650                  'Completed by user since majorupdatetime' => [
     651                          $time,
     652                          null,
     653                          $time - DAYSECS,
     654                          false,
     655                      ],
     656                  'Requested by user before current completion' => [
     657                          $time,
     658                          $time - DAYSECS,
     659                          null,
     660                          false,
     661                      ],
     662                  'Requested by user since completion' => [
     663                          $time - DAYSECS,
     664                          $time,
     665                          null,
     666                          true,
     667                      ],
     668              ];
     669      }
     670  
     671      /**
     672       * Test that a disabled tour should never be shown to users.
     673       *
     674       * @dataProvider should_show_for_user_provider
     675       * @param   mixed   $completiondate The user's completion date for this tour
     676       * @param   mixed   $requesteddate  The user's last requested date for this tour
     677       * @param   mixed   $updateddate    The date this tour was last updated
     678       * @param   string  $expectation    The expected tour key
     679       */
     680      public function test_should_show_for_user($completiondate, $requesteddate, $updateddate, $expectation) {
     681          // Uses user preferences so we must be in a user context.
     682          $this->resetAfterTest();
     683          $this->setAdminUser();
     684  
     685          $tour = $this->getMockBuilder(tour::class)
     686              ->onlyMethods([
     687                      'get_id',
     688                      'get_config',
     689                      'is_enabled',
     690                  ])
     691              ->getMock()
     692              ;
     693  
     694          $tour->method('is_enabled')
     695              ->willReturn(true)
     696              ;
     697  
     698          $id = rand(1, 100);
     699          $tour->method('get_id')
     700              ->willReturn($id)
     701              ;
     702  
     703          if ($completiondate !== null) {
     704              set_user_preference(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id, $completiondate);
     705          }
     706  
     707          if ($requesteddate !== null) {
     708              set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $requesteddate);
     709          }
     710  
     711          if ($updateddate !== null) {
     712              $tour->expects($this->once())
     713                  ->method('get_config')
     714                  ->willReturn($updateddate)
     715                  ;
     716          }
     717  
     718          $this->assertEquals($expectation, $tour->should_show_for_user());
     719      }
     720  
     721      /**
     722       * Provider for get_tour_key.
     723       *
     724       * @return array
     725       */
     726      public function get_tour_key_provider() {
     727          $id = rand(1, 100);
     728          $time = time();
     729  
     730          return [
     731              'No initial values' => [
     732                      $id,
     733                      [null, $time],
     734                      $this->greaterThanOrEqual($time),
     735                      true,
     736                      null,
     737                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
     738                  ],
     739  
     740              'Initial tour time, no user pref' => [
     741                      $id,
     742                      [$time],
     743                      null,
     744                      false,
     745                      null,
     746                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
     747                  ],
     748              'Initial tour time, with user reset lower' => [
     749                      $id,
     750                      [$time],
     751                      null,
     752                      false,
     753                      $time - DAYSECS,
     754                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
     755                  ],
     756              'Initial tour time, with user reset higher' => [
     757                      $id,
     758                      [$time],
     759                      null,
     760                      false,
     761                      $time + DAYSECS,
     762                      sprintf('tool_usertours_\d_%d_%s', $id, $time + DAYSECS),
     763                  ],
     764          ];
     765      }
     766  
     767      /**
     768       * Test that get_tour_key provides the anticipated unique keys.
     769       *
     770       * @dataProvider get_tour_key_provider
     771       * @param   int     $id             The tour ID
     772       * @param   array   $getconfig      The mocked values for get_config calls
     773       * @param   array   $setconfig      The mocked values for set_config calls
     774       * @param   bool    $willpersist    Whether a persist is expected
     775       * @param   mixed   $userpref       The value to set for the user preference
     776       * @param   string  $expectation    The expected tour key
     777       */
     778      public function test_get_tour_key($id, $getconfig, $setconfig, $willpersist, $userpref, $expectation) {
     779          // Uses user preferences so we must be in a user context.
     780          $this->resetAfterTest();
     781          $this->setAdminUser();
     782  
     783          $tour = $this->getMockBuilder(tour::class)
     784              ->onlyMethods([
     785                      'get_config',
     786                      'set_config',
     787                      'get_id',
     788                      'persist',
     789                  ])
     790              ->getMock()
     791              ;
     792  
     793          if ($getconfig) {
     794              $tour->expects($this->exactly(count($getconfig)))
     795                  ->method('get_config')
     796                  ->will(call_user_func_array([$this, 'onConsecutiveCalls'], $getconfig))
     797                  ;
     798          }
     799  
     800          if ($setconfig) {
     801              $tour->expects($this->once())
     802                  ->method('set_config')
     803                  ->with($this->equalTo('majorupdatetime'), $setconfig)
     804                  ->will($this->returnSelf())
     805                  ;
     806          } else {
     807              $tour->expects($this->never())
     808                  ->method('set_config')
     809                  ;
     810          }
     811  
     812          if ($willpersist) {
     813              $tour->expects($this->once())
     814                  ->method('persist')
     815                  ;
     816          } else {
     817              $tour->expects($this->never())
     818                  ->method('persist')
     819                  ;
     820          }
     821  
     822          $tour->expects($this->any())
     823              ->method('get_id')
     824              ->willReturn($id)
     825              ;
     826  
     827          if ($userpref !== null) {
     828              set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $userpref);
     829          }
     830  
     831          $this->assertMatchesRegularExpression(
     832                  '/' . $expectation . '/',
     833                  $tour->get_tour_key()
     834              );
     835      }
     836  
     837      /**
     838       * Ensure that the request_user_reset function sets an appropriate value for the tour.
     839       */
     840      public function test_requested_user_reset() {
     841          $tour = $this->getMockBuilder(tour::class)
     842              ->onlyMethods([
     843                      'get_id',
     844                  ])
     845              ->getMock()
     846              ;
     847  
     848          $id = rand(1, 100);
     849          $time = time();
     850  
     851          $tour->expects($this->once())
     852              ->method('get_id')
     853              ->willReturn($id)
     854              ;
     855  
     856          $tour->request_user_reset();
     857  
     858          $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id));
     859      }
     860  
     861      /**
     862       * Ensure that the request_user_reset function sets an appropriate value for the tour.
     863       */
     864      public function test_mark_user_completed() {
     865          $tour = $this->getMockBuilder(tour::class)
     866              ->onlyMethods([
     867                      'get_id',
     868                  ])
     869              ->getMock()
     870              ;
     871  
     872          $id = rand(1, 100);
     873          $time = time();
     874  
     875          $tour->expects($this->once())
     876              ->method('get_id')
     877              ->willReturn($id)
     878              ;
     879  
     880          $tour->mark_user_completed();
     881  
     882          $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id));
     883      }
     884  
     885      /**
     886       * Provider for the is_first_tour and is_last_tour tests.
     887       *
     888       * @return array
     889       */
     890      public function sortorder_first_last_provider() {
     891          $topcount = rand(10, 100);
     892          return [
     893                  'Only tour => first + last' => [
     894                          0,
     895                          true,
     896                          1,
     897                          true,
     898                      ],
     899                  'First tour of many' => [
     900                          0,
     901                          true,
     902                          $topcount,
     903                          false,
     904                      ],
     905                  'Last tour of many' => [
     906                          $topcount - 1,
     907                          false,
     908                          $topcount,
     909                          true,
     910                      ],
     911                  'Middle tour of many' => [
     912                          5,
     913                          false,
     914                          $topcount,
     915                          false,
     916                      ],
     917              ];
     918      }
     919  
     920      /**
     921       * Test the is_first_tour() function.
     922       *
     923       * @dataProvider sortorder_first_last_provider
     924       * @param   int     $sortorder      The new sort order
     925       * @param   bool    $isfirst        Whether this is the first tour
     926       * @param   int     $total          The number of tours
     927       * @param   bool    $islast         Whether this is the last tour
     928       */
     929      public function test_is_first_tour($sortorder, $isfirst, $total, $islast) {
     930          $tour = new \tool_usertours\tour();
     931  
     932          $rc = new \ReflectionClass(tour::class);
     933          $rcp = $rc->getProperty('sortorder');
     934          $rcp->setAccessible(true);
     935          $rcp->setValue($tour, $sortorder);
     936  
     937          $this->assertEquals($isfirst, $tour->is_first_tour());
     938      }
     939  
     940      /**
     941       * Test the is_last_tour() function.
     942       *
     943       * @dataProvider sortorder_first_last_provider
     944       * @param   int     $sortorder      The new sort order
     945       * @param   bool    $isfirst        Whether this is the first tour
     946       * @param   int     $total          The number of tours
     947       * @param   bool    $islast         Whether this is the last tour
     948       */
     949      public function test_is_last_tour_calculated($sortorder, $isfirst, $total, $islast) {
     950          $tour = new \tool_usertours\tour();
     951  
     952          $rc = new \ReflectionClass(tour::class);
     953          $rcp = $rc->getProperty('sortorder');
     954          $rcp->setAccessible(true);
     955          $rcp->setValue($tour, $sortorder);
     956  
     957          // The total will be calculated.
     958          $DB = $this->mock_database();
     959          $DB->expects($this->once())
     960              ->method('count_records')
     961              ->willReturn($total)
     962              ;
     963          $this->assertEquals($islast, $tour->is_last_tour());
     964      }
     965  
     966      /**
     967       * Test the is_last_tour() function.
     968       *
     969       * @dataProvider sortorder_first_last_provider
     970       * @param   int     $sortorder      The new sort order
     971       * @param   bool    $isfirst        Whether this is the first tour
     972       * @param   int     $total          The number of tours
     973       * @param   bool    $islast         Whether this is the last tour
     974       */
     975      public function test_is_last_tour_provided($sortorder, $isfirst, $total, $islast) {
     976          $tour = new \tool_usertours\tour();
     977  
     978          $rc = new \ReflectionClass(tour::class);
     979          $rcp = $rc->getProperty('sortorder');
     980          $rcp->setAccessible(true);
     981          $rcp->setValue($tour, $sortorder);
     982  
     983          // The total is provided.
     984          // No DB calls expected.
     985          $DB = $this->mock_database();
     986          $DB->expects($this->never())
     987              ->method('count_records')
     988              ->willReturn(0)
     989              ;
     990          $this->assertEquals($islast, $tour->is_last_tour($total));
     991      }
     992  
     993      /**
     994       * Data provider for the get_filter_values tests.
     995       *
     996       * @return array
     997       */
     998      public function get_filter_values_provider() {
     999          $cheese = ['cheddar', 'boursin', 'mozzarella'];
    1000          $horses = ['coolie', 'dakota', 'leo', 'twiggy'];
    1001          return [
    1002              'No config' => [
    1003                  [],
    1004                  'cheese',
    1005                  [],
    1006              ],
    1007              'Some config for another filter' => [
    1008                  [
    1009                      'horses' => $horses,
    1010                  ],
    1011                  'cheese',
    1012                  [],
    1013              ],
    1014              'Some config for this filter' => [
    1015                  [
    1016                      'horses' => $horses,
    1017                  ],
    1018                  'horses',
    1019                  $horses,
    1020              ],
    1021              'Some config for several filters' => [
    1022                  [
    1023                      'horses' => $horses,
    1024                      'cheese' => $cheese
    1025                  ],
    1026                  'horses',
    1027                  $horses,
    1028              ],
    1029          ];
    1030      }
    1031  
    1032      /**
    1033       * Tests for the get_filter_values function.
    1034       *
    1035       * @dataProvider get_filter_values_provider
    1036       * @param   array       $fullconfig     The config value being tested
    1037       * @param   string      $filtername     The name of the filter being tested
    1038       * @param   array       $expectedvalues The expected result
    1039       */
    1040      public function test_get_filter_values($fullconfig, $filtername, $expectedvalues) {
    1041          $tour = $this->getMockBuilder(tour::class)
    1042              ->onlyMethods(['get_config'])
    1043              ->getMock();
    1044  
    1045          $tour->expects($this->once())
    1046              ->method('get_config')
    1047              ->will($this->returnValue($fullconfig));
    1048  
    1049          $this->assertEquals($expectedvalues, $tour->get_filter_values($filtername));
    1050      }
    1051  
    1052      /**
    1053       * Data provider for set_filter_values tests.
    1054       *
    1055       * @return  array
    1056       */
    1057      public function set_filter_values_provider() {
    1058          $cheese = ['cheddar', 'boursin', 'mozzarella'];
    1059          $horses = ['coolie', 'dakota', 'leo', 'twiggy'];
    1060  
    1061          return [
    1062              'No initial value' => [
    1063                  [],
    1064                  'cheese',
    1065                  $cheese,
    1066                  ['cheese' => $cheese],
    1067              ],
    1068              'Existing filter merged' => [
    1069                  ['horses' => $horses],
    1070                  'cheese',
    1071                  $cheese,
    1072                  ['horses' => $horses, 'cheese' => $cheese],
    1073              ],
    1074              'Existing filter updated' => [
    1075                  ['cheese' => $cheese],
    1076                  'cheese',
    1077                  ['cheddar'],
    1078                  ['cheese' => ['cheddar']],
    1079              ],
    1080              'Existing filter updated with merge' => [
    1081                  ['horses' => $horses, 'cheese' => $cheese],
    1082                  'cheese',
    1083                  ['cheddar'],
    1084                  ['horses' => $horses, 'cheese' => ['cheddar']],
    1085              ],
    1086          ];
    1087      }
    1088  
    1089      /**
    1090       * Base tests for set_filter_values.
    1091       *
    1092       * @dataProvider set_filter_values_provider
    1093       * @param   array       $currentvalues  The current value
    1094       * @param   string      $filtername     The name of the filter to add to
    1095       * @param   array       $newvalues      The new values to store
    1096       * @param   array       $expectedvalues The combined values
    1097       */
    1098      public function test_set_filter_values_merge($currentvalues, $filtername, $newvalues, $expectedvalues) {
    1099          $tour = $this->getMockBuilder(tour::class)
    1100              ->onlyMethods(['get_config', 'set_config'])
    1101              ->getMock();
    1102  
    1103          $tour->expects($this->once())
    1104              ->method('get_config')
    1105              ->will($this->returnValue($currentvalues));
    1106  
    1107          $tour->expects($this->once())
    1108              ->method('set_config')
    1109              ->with(
    1110                  $this->equalTo('filtervalues'),
    1111                  $this->equalTo($expectedvalues)
    1112              );
    1113  
    1114          $tour->set_filter_values($filtername, $newvalues);
    1115      }
    1116  }