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.

Differences Between: [Versions 311 and 401] [Versions 401 and 403]

   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   * Contains unit tests for core_completion/cm_completion_details.
  19   *
  20   * @package   core_completion
  21   * @copyright 2021 Jun Pataleta <jun@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  declare(strict_types = 1);
  26  
  27  namespace core_completion;
  28  
  29  use advanced_testcase;
  30  use cm_info;
  31  use completion_info;
  32  
  33  defined('MOODLE_INTERNAL') || die();
  34  
  35  global $CFG;
  36  require_once($CFG->libdir . '/completionlib.php');
  37  
  38  /**
  39   * Class for unit testing core_completion/cm_completion_details.
  40   *
  41   * @package   core_completion
  42   * @copyright 2021 Jun Pataleta <jun@moodle.com>
  43   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class cm_completion_details_test extends advanced_testcase {
  46  
  47      /** @var completion_info A completion object. */
  48      protected $completioninfo = null;
  49  
  50      /**
  51       * Fetches a mocked cm_completion_details instance.
  52       *
  53       * @param int|null $completion The completion tracking mode for the module.
  54       * @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
  55       * @param object $mockcompletiondata Mock data to be returned by get_data.
  56       * @param string $modname The modname to set in the cm if a specific one is required.
  57       * @return cm_completion_details
  58       */
  59      protected function setup_data(?int $completion, array $completionoptions = [],
  60              object $mockcompletiondata = null, $modname = 'somenonexistentmod'): cm_completion_details {
  61          if (is_null($completion)) {
  62              $completion = COMPLETION_TRACKING_AUTOMATIC;
  63          }
  64  
  65          // Mock a completion_info instance so we can simply mock the returns of completion_info::get_data() later.
  66          $this->completioninfo = $this->getMockBuilder(completion_info::class)
  67              ->disableOriginalConstructor()
  68              ->getMock();
  69  
  70          // Mock return of completion_info's is_enabled() method to match the expected completion tracking for the module.
  71          $this->completioninfo->expects($this->any())
  72              ->method('is_enabled')
  73              ->willReturn($completion);
  74  
  75          if (!empty($mockcompletiondata)) {
  76              $this->completioninfo->expects($this->any())
  77                  ->method('get_data')
  78                  ->willReturn($mockcompletiondata);
  79          }
  80  
  81          // Build a mock cm_info instance.
  82          $mockcminfo = $this->getMockBuilder(cm_info::class)
  83              ->disableOriginalConstructor()
  84              ->onlyMethods(['__get'])
  85              ->getMock();
  86  
  87          // Mock the return of the magic getter method when fetching the cm_info object's customdata and instance values.
  88          $mockcminfo->expects($this->any())
  89              ->method('__get')
  90              ->will($this->returnValueMap([
  91                  ['completion', $completion],
  92                  ['instance', 1],
  93                  ['modname', $modname],
  94                  ['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED],
  95                  ['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null],
  96                  ['completionpassgrade', $completionoptions['completionpassgrade'] ?? null],
  97              ]));
  98  
  99          return new cm_completion_details($this->completioninfo, $mockcminfo, 2);
 100      }
 101  
 102      /**
 103       * Provides data for test_has_completion().
 104       *
 105       * @return array[]
 106       */
 107      public function has_completion_provider(): array {
 108          return [
 109              'Automatic' => [
 110                  COMPLETION_TRACKING_AUTOMATIC, true
 111              ],
 112              'Manual' => [
 113                  COMPLETION_TRACKING_MANUAL, true
 114              ],
 115              'None' => [
 116                  COMPLETION_TRACKING_NONE, false
 117              ],
 118          ];
 119      }
 120  
 121      /**
 122       * Test for has_completion().
 123       *
 124       * @dataProvider has_completion_provider
 125       * @param int $completion The completion tracking mode.
 126       * @param bool $expectedresult Expected result.
 127       */
 128      public function test_has_completion(int $completion, bool $expectedresult) {
 129          $cmcompletion = $this->setup_data($completion);
 130  
 131          $this->assertEquals($expectedresult, $cmcompletion->has_completion());
 132      }
 133  
 134      /**
 135       * Provides data for test_is_automatic().
 136       *
 137       * @return array[]
 138       */
 139      public function is_automatic_provider(): array {
 140          return [
 141              'Automatic' => [
 142                  COMPLETION_TRACKING_AUTOMATIC, true
 143              ],
 144              'Manual' => [
 145                  COMPLETION_TRACKING_MANUAL, false
 146              ],
 147              'None' => [
 148                  COMPLETION_TRACKING_NONE, false
 149              ],
 150          ];
 151      }
 152  
 153      /**
 154       * Test for is_available().
 155       *
 156       * @dataProvider is_automatic_provider
 157       * @param int $completion The completion tracking mode.
 158       * @param bool $expectedresult Expected result.
 159       */
 160      public function test_is_automatic(int $completion, bool $expectedresult) {
 161          $cmcompletion = $this->setup_data($completion);
 162  
 163          $this->assertEquals($expectedresult, $cmcompletion->is_automatic());
 164      }
 165  
 166      /**
 167       * Data provider for test_get_overall_completion().
 168       * @return array[]
 169       */
 170      public function overall_completion_provider(): array {
 171          return [
 172              'Complete' => [COMPLETION_COMPLETE],
 173              'Incomplete' => [COMPLETION_INCOMPLETE],
 174          ];
 175      }
 176  
 177      /**
 178       * Test for get_overall_completion().
 179       *
 180       * @dataProvider overall_completion_provider
 181       * @param int $state
 182       */
 183      public function test_get_overall_completion(int $state) {
 184          $completiondata = (object)['completionstate' => $state];
 185          $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, [], $completiondata);
 186          $this->assertEquals($state, $cmcompletion->get_overall_completion());
 187      }
 188  
 189      /**
 190       * Data provider for test_get_details().
 191       * @return array[]
 192       */
 193      public function get_details_provider() {
 194          return [
 195              'No completion tracking' => [
 196                  COMPLETION_TRACKING_NONE, null, null, null, []
 197              ],
 198              'Manual completion tracking' => [
 199                  COMPLETION_TRACKING_MANUAL, null, null, null, []
 200              ],
 201              'Automatic, require view, not viewed' => [
 202                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, null, [
 203                      'completionview' => (object)[
 204                          'status' => COMPLETION_INCOMPLETE,
 205                          'description' => get_string('detail_desc:view', 'completion'),
 206                      ]
 207                  ]
 208              ],
 209              'Automatic, require view, viewed' => [
 210                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, null, [
 211                      'completionview' => (object)[
 212                          'status' => COMPLETION_COMPLETE,
 213                          'description' => get_string('detail_desc:view', 'completion'),
 214                      ]
 215                  ]
 216              ],
 217              'Automatic, require grade, incomplete' => [
 218                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, null, [
 219                      'completionusegrade' => (object)[
 220                          'status' => COMPLETION_INCOMPLETE,
 221                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 222                      ]
 223                  ]
 224              ],
 225              'Automatic, require grade, complete' => [
 226                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, null, [
 227                      'completionusegrade' => (object)[
 228                          'status' => COMPLETION_COMPLETE,
 229                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 230                      ]
 231                  ]
 232              ],
 233              'Automatic, require view (complete) and grade (incomplete)' => [
 234                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, null, [
 235                      'completionview' => (object)[
 236                          'status' => COMPLETION_COMPLETE,
 237                          'description' => get_string('detail_desc:view', 'completion'),
 238                      ],
 239                      'completionusegrade' => (object)[
 240                          'status' => COMPLETION_INCOMPLETE,
 241                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 242                      ]
 243                  ]
 244              ],
 245              'Automatic, require view (incomplete) and grade (complete)' => [
 246                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, null, [
 247                      'completionview' => (object)[
 248                          'status' => COMPLETION_INCOMPLETE,
 249                          'description' => get_string('detail_desc:view', 'completion'),
 250                      ],
 251                      'completionusegrade' => (object)[
 252                          'status' => COMPLETION_COMPLETE,
 253                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 254                      ]
 255                  ]
 256              ],
 257              'Automatic, require grade, require pass grade, complete' => [
 258                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
 259                      'completionusegrade' => (object)[
 260                          'status' => COMPLETION_COMPLETE,
 261                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 262                      ],
 263                      'completionpassgrade' => (object)[
 264                          'status' => COMPLETION_COMPLETE,
 265                          'description' => get_string('detail_desc:receivepassgrade', 'completion'),
 266                      ],
 267                  ]
 268              ],
 269              'Automatic, require grade, require pass grade, incomplete' => [
 270                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
 271                      'completionusegrade' => (object)[
 272                          'status' => COMPLETION_COMPLETE,
 273                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 274                      ],
 275                      'completionpassgrade' => (object)[
 276                          'status' => COMPLETION_INCOMPLETE,
 277                          'description' => get_string('detail_desc:receivepassgrade', 'completion'),
 278                      ],
 279                  ]
 280              ],
 281              'Automatic, require view (complete), require grade(complete), require pass grade(complete)' => [
 282                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
 283                      'completionview' => (object)[
 284                          'status' => COMPLETION_COMPLETE,
 285                          'description' => get_string('detail_desc:view', 'completion'),
 286                      ],
 287                      'completionusegrade' => (object)[
 288                          'status' => COMPLETION_COMPLETE,
 289                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 290                      ],
 291                      'completionpassgrade' => (object)[
 292                          'status' => COMPLETION_COMPLETE,
 293                          'description' => get_string('detail_desc:receivepassgrade', 'completion'),
 294                      ],
 295                  ]
 296              ],
 297              'Automatic, require view (incomplete), require grade(complete), require pass grade(complete)' => [
 298                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
 299                      'completionview' => (object)[
 300                          'status' => COMPLETION_INCOMPLETE,
 301                          'description' => get_string('detail_desc:view', 'completion'),
 302                      ],
 303                      'completionusegrade' => (object)[
 304                          'status' => COMPLETION_COMPLETE,
 305                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 306                      ],
 307                      'completionpassgrade' => (object)[
 308                          'status' => COMPLETION_COMPLETE,
 309                          'description' => get_string('detail_desc:receivepassgrade', 'completion'),
 310                      ],
 311                  ]
 312              ],
 313          ];
 314      }
 315  
 316      /**
 317       * Test for \core_completion\cm_completion_details::get_details().
 318       *
 319       * @dataProvider get_details_provider
 320       * @param int $completion The completion tracking mode.
 321       * @param int|null $completionview Completion status of the "view" completion condition.
 322       * @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
 323       * @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
 324       * @param array $expecteddetails Expected completion details returned by get_details().
 325       */
 326      public function test_get_details(int $completion, ?int $completionview,
 327               ?int $completiongrade, ?int $completionpassgrade, array $expecteddetails) {
 328          $options = [];
 329          $getdatareturn = (object)[
 330              'viewed' => $completionview,
 331              'completiongrade' => $completiongrade,
 332              'passgrade' => $completionpassgrade,
 333          ];
 334  
 335          if (!is_null($completionview)) {
 336              $options['completionview'] = true;
 337          }
 338          if (!is_null($completiongrade)) {
 339              $options['completionusegrade'] = true;
 340          }
 341          if (!is_null($completionpassgrade)) {
 342              $options['completionpassgrade'] = true;
 343          }
 344  
 345          $cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
 346          $this->assertEquals($expecteddetails, $cmcompletion->get_details());
 347      }
 348  
 349      /**
 350       * Data provider for test_get_details_custom_order().
 351       * @return array[]
 352       */
 353      public function get_details_custom_order_provider() {
 354          return [
 355              'Custom and view/grade standard conditions, view first and grade last' => [
 356                  true,
 357                  true,
 358                  [
 359                      'completionsubmit' => true,
 360                  ],
 361                  'assign',
 362                  ['completionview', 'completionsubmit', 'completionusegrade'],
 363              ],
 364              'Custom and view/grade standard conditions, grade not last' => [
 365                  true,
 366                  true,
 367                  [
 368                      'completionminattempts' => 2,
 369                      'completionusegrade' => 50,
 370                      'completionpassorattemptsexhausted' => 1,
 371                  ],
 372                  'quiz',
 373                  ['completionview', 'completionminattempts', 'completionusegrade', 'completionpassorattemptsexhausted'],
 374              ],
 375              'Custom and grade standard conditions only, no view condition' => [
 376                  false,
 377                  true,
 378                  [
 379                      'completionsubmit' => true,
 380                  ],
 381                  'assign',
 382                  ['completionsubmit', 'completionusegrade'],
 383              ],
 384              'Custom and view standard conditions only, no grade condition' => [
 385                  true,
 386                  false,
 387                  [
 388                      'completionsubmit' => true
 389                  ],
 390                  'assign',
 391                  ['completionview', 'completionsubmit'],
 392              ],
 393              'View and grade conditions only, activity with no custom conditions' => [
 394                  true,
 395                  true,
 396                  [
 397                      'completionview' => true,
 398                      'completionusegrade' => true
 399                  ],
 400                  'workshop',
 401                  ['completionview', 'completionusegrade'],
 402              ],
 403              'View condition only, activity with no custom conditions' => [
 404                  true,
 405                  false,
 406                  [
 407                      'completionview' => true,
 408                  ],
 409                  'workshop',
 410                  ['completionview'],
 411              ],
 412          ];
 413      }
 414  
 415      /**
 416       * Test custom sort order is functioning in \core_completion\cm_completion_details::get_details().
 417       *
 418       * @dataProvider get_details_custom_order_provider
 419       * @param bool $completionview Completion status of the "view" completion condition.
 420       * @param bool $completiongrade Completion status of the "must receive grade" completion condition.
 421       * @param array $customcompletionrules Custom completion requirements, along with their values.
 422       * @param string $modname The name of the module having data fetched.
 423       * @param array $expectedorder The expected order of completion conditions returned about the module.
 424       */
 425      public function test_get_details_custom_order(bool $completionview, bool $completiongrade, array $customcompletionrules,
 426              string $modname, array $expectedorder) {
 427  
 428          $options['customcompletion'] = [];
 429          $customcompletiondata = [];
 430  
 431          if ($completionview) {
 432              $options['completionview'] = true;
 433          }
 434  
 435          if ($completiongrade) {
 436              $options['completionusegrade'] = true;
 437          }
 438  
 439          // Set up the completion rules for the completion info.
 440          foreach ($customcompletionrules as $customtype => $isenabled) {
 441              $customcompletiondata[$customtype] = COMPLETION_COMPLETE;
 442          }
 443  
 444          $getdatareturn = (object)[
 445              'viewed' => $completionview ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
 446              'completiongrade' => $completiongrade ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
 447              'customcompletion' => $customcompletiondata,
 448          ];
 449  
 450          $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, $options, $getdatareturn, $modname);
 451  
 452          $this->completioninfo->expects($this->any())
 453              ->method('get_data')
 454              ->willReturn($getdatareturn);
 455  
 456          $fetcheddetails = $cmcompletion->get_details();
 457  
 458          // Check the expected number of items are returned, and sorted in the correct order.
 459          $this->assertCount(count($expectedorder), $fetcheddetails);
 460          $this->assertTrue((array_keys($fetcheddetails) === $expectedorder));
 461      }
 462  }