Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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  declare(strict_types = 1);
  18  
  19  namespace mod_lesson;
  20  
  21  use advanced_testcase;
  22  use cm_info;
  23  use coding_exception;
  24  use mod_lesson\completion\custom_completion;
  25  use moodle_exception;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  require_once($CFG->libdir . '/completionlib.php');
  31  
  32  /**
  33   * Class for unit testing mod_lesson/custom_completion.
  34   *
  35   * @package   mod_lesson
  36   * @copyright 2021 Michael Hawkins <michaelh@moodle.com>
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class custom_completion_test extends advanced_testcase {
  40  
  41      /**
  42       * Data provider for get_state().
  43       *
  44       * @return array[]
  45       */
  46      public function get_state_provider(): array {
  47          return [
  48              'Undefined completion requirement' => [
  49                  'somenonexistentrule', COMPLETION_ENABLED, 3, null, coding_exception::class
  50              ],
  51              'Minimum time spent requirement not available' => [
  52                  'completionstatusrequired', COMPLETION_DISABLED, 3, null, moodle_exception::class
  53              ],
  54              'Minimum time spent required, user has not spent time in the lesson' => [
  55                  'completiontimespent', 30, false, COMPLETION_INCOMPLETE, null
  56              ],
  57              'Minimum time spent required, user has not met completion requirement' => [
  58                  'completiontimespent', 30, 10, COMPLETION_INCOMPLETE, null
  59              ],
  60              'Minimum time spent required, user has met completion requirement' => [
  61                  'completiontimespent', 30, 30, COMPLETION_COMPLETE, null
  62              ],
  63              'Minimum time spent required, user has exceeded completion requirement' => [
  64                  'completiontimespent', 30, 40, COMPLETION_COMPLETE, null
  65              ],
  66              'User must reach end of lesson, has not met completion requirement' => [
  67                  'completionendreached', 1, false, COMPLETION_INCOMPLETE, null
  68              ],
  69              'User must reach end of lesson, has not met completion requirement' => [
  70                  'completionendreached', 1, true, COMPLETION_COMPLETE, null
  71              ],
  72          ];
  73      }
  74  
  75      /**
  76       * Test for get_state().
  77       *
  78       * @dataProvider get_state_provider
  79       * @param string $rule The custom completion condition.
  80       * @param int $rulevalue The custom completion rule value.
  81       * @param mixed $uservalue The database value returned when checking the rule for the user.
  82       * @param int|null $status Expected completion status for the rule.
  83       * @param string|null $exception Expected exception.
  84       */
  85      public function test_get_state(string $rule, int $rulevalue, $uservalue, ?int $status, ?string $exception) {
  86          global $DB;
  87  
  88          if (!is_null($exception)) {
  89              $this->expectException($exception);
  90          }
  91  
  92          // Custom completion rule data for cm_info::customdata.
  93          $customdataval = [
  94              'customcompletionrules' => [
  95                  $rule => $rulevalue
  96              ]
  97          ];
  98  
  99          // Build a mock cm_info instance.
 100          $mockcminfo = $this->getMockBuilder(cm_info::class)
 101              ->disableOriginalConstructor()
 102              ->onlyMethods(['__get'])
 103              ->getMock();
 104  
 105          // Mock the return of the magic getter method when fetching the cm_info object's
 106          // customdata and instance values.
 107          $mockcminfo->expects($this->any())
 108              ->method('__get')
 109              ->will($this->returnValueMap([
 110                  ['customdata', $customdataval],
 111                  ['instance', 1],
 112              ]));
 113  
 114          if ($rule === 'completiontimespent') {
 115              // Mock the DB call fetching user's lesson time spent.
 116              $DB = $this->createMock(get_class($DB));
 117              $DB->expects($this->atMost(1))
 118                  ->method('get_field_sql')
 119                  ->willReturn($uservalue);
 120          } else if ($rule === 'completionendreached') {
 121              // Mock the DB call fetching user's end reached state.
 122              $DB = $this->createMock(get_class($DB));
 123              $DB->expects($this->atMost(1))
 124                  ->method('record_exists')
 125                  ->willReturn($uservalue);
 126          }
 127  
 128          $customcompletion = new custom_completion($mockcminfo, 2);
 129  
 130          $this->assertEquals($status, $customcompletion->get_state($rule));
 131      }
 132  
 133      /**
 134       * Test for get_defined_custom_rules().
 135       */
 136      public function test_get_defined_custom_rules() {
 137          $expectedrules = [
 138              'completiontimespent',
 139              'completionendreached',
 140          ];
 141  
 142          $definedrules = custom_completion::get_defined_custom_rules();
 143          $this->assertCount(2, $definedrules);
 144  
 145          foreach ($definedrules as $definedrule) {
 146              $this->assertContains($definedrule, $expectedrules);
 147          }
 148      }
 149  
 150      /**
 151       * Test for get_defined_custom_rule_descriptions().
 152       */
 153      public function test_get_custom_rule_descriptions() {
 154          // Get defined custom rules.
 155          $rules = custom_completion::get_defined_custom_rules();
 156  
 157          // Build a mock cm_info instance.
 158          $mockcminfo = $this->getMockBuilder(cm_info::class)
 159              ->disableOriginalConstructor()
 160              ->onlyMethods(['__get'])
 161              ->getMock();
 162  
 163          // Instantiate a custom_completion object using the mocked cm_info.
 164          $customcompletion = new custom_completion($mockcminfo, 1);
 165  
 166          // Get custom rule descriptions.
 167          $ruledescriptions = $customcompletion->get_custom_rule_descriptions();
 168  
 169          // Confirm that defined rules and rule descriptions are consistent with each other.
 170          $this->assertEquals(count($rules), count($ruledescriptions));
 171          foreach ($rules as $rule) {
 172              $this->assertArrayHasKey($rule, $ruledescriptions);
 173          }
 174      }
 175  
 176      /**
 177       * Test for is_defined().
 178       */
 179      public function test_is_defined() {
 180          // Build a mock cm_info instance.
 181          $mockcminfo = $this->getMockBuilder(cm_info::class)
 182              ->disableOriginalConstructor()
 183              ->getMock();
 184  
 185          $customcompletion = new custom_completion($mockcminfo, 1);
 186  
 187          // All rules are defined.
 188          $this->assertTrue($customcompletion->is_defined('completiontimespent'));
 189          $this->assertTrue($customcompletion->is_defined('completionendreached'));
 190  
 191          // Undefined rule is not found.
 192          $this->assertFalse($customcompletion->is_defined('somerandomrule'));
 193      }
 194  
 195      /**
 196       * Data provider for test_get_available_custom_rules().
 197       *
 198       * @return array[]
 199       */
 200      public function get_available_custom_rules_provider(): array {
 201          return [
 202              'No completion conditions enabled' => [
 203                  [
 204                      'completiontimespent' => COMPLETION_DISABLED,
 205                      'completionendreached' => COMPLETION_DISABLED,
 206                  ],
 207                  [],
 208              ],
 209              'Completion end reached enabled only' => [
 210                  [
 211                      'completiontimespent' => COMPLETION_DISABLED,
 212                      'completionendreached' => COMPLETION_ENABLED,
 213                  ],
 214                  ['completionendreached'],
 215              ],
 216              'Completion time spent enabled only' => [
 217                  [
 218                      'completiontimespent' => 60,
 219                      'completionendreached' => COMPLETION_DISABLED,
 220                  ],
 221                  ['completiontimespent'],
 222              ],
 223              'Completion end reached and time spent both enabled' => [
 224                  [
 225                      'completiontimespent' => 90,
 226                      'completionendreached' => COMPLETION_ENABLED,
 227                  ],
 228                  ['completiontimespent', 'completionendreached'],
 229              ],
 230          ];
 231      }
 232  
 233      /**
 234       * Test for get_available_custom_rules().
 235       *
 236       * @dataProvider get_available_custom_rules_provider
 237       * @param array $completionrulesvalues
 238       * @param array $expected
 239       */
 240      public function test_get_available_custom_rules(array $completionrulesvalues, array $expected) {
 241          $customcompletionrules = [
 242              'customcompletionrules' => $completionrulesvalues,
 243          ];
 244  
 245          // Build a mock cm_info instance.
 246          $mockcminfo = $this->getMockBuilder(cm_info::class)
 247              ->disableOriginalConstructor()
 248              ->onlyMethods(['__get'])
 249              ->getMock();
 250  
 251          // Mock the return of magic getter for the customdata attribute.
 252          $mockcminfo->expects($this->any())
 253              ->method('__get')
 254              ->with('customdata')
 255              ->willReturn($customcompletionrules);
 256  
 257          $customcompletion = new custom_completion($mockcminfo, 1);
 258          $this->assertEquals($expected, $customcompletion->get_available_custom_rules());
 259      }
 260  }