Search moodle.org's
Developer Documentation

See Release Notes

  • 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 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 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              ]));
  97  
  98          return new cm_completion_details($this->completioninfo, $mockcminfo, 2);
  99      }
 100  
 101      /**
 102       * Provides data for test_has_completion().
 103       *
 104       * @return array[]
 105       */
 106      public function has_completion_provider(): array {
 107          return [
 108              'Automatic' => [
 109                  COMPLETION_TRACKING_AUTOMATIC, true
 110              ],
 111              'Manual' => [
 112                  COMPLETION_TRACKING_MANUAL, true
 113              ],
 114              'None' => [
 115                  COMPLETION_TRACKING_NONE, false
 116              ],
 117          ];
 118      }
 119  
 120      /**
 121       * Test for has_completion().
 122       *
 123       * @dataProvider has_completion_provider
 124       * @param int $completion The completion tracking mode.
 125       * @param bool $expectedresult Expected result.
 126       */
 127      public function test_has_completion(int $completion, bool $expectedresult) {
 128          $cmcompletion = $this->setup_data($completion);
 129  
 130          $this->assertEquals($expectedresult, $cmcompletion->has_completion());
 131      }
 132  
 133      /**
 134       * Provides data for test_is_automatic().
 135       *
 136       * @return array[]
 137       */
 138      public function is_automatic_provider(): array {
 139          return [
 140              'Automatic' => [
 141                  COMPLETION_TRACKING_AUTOMATIC, true
 142              ],
 143              'Manual' => [
 144                  COMPLETION_TRACKING_MANUAL, false
 145              ],
 146              'None' => [
 147                  COMPLETION_TRACKING_NONE, false
 148              ],
 149          ];
 150      }
 151  
 152      /**
 153       * Test for is_available().
 154       *
 155       * @dataProvider is_automatic_provider
 156       * @param int $completion The completion tracking mode.
 157       * @param bool $expectedresult Expected result.
 158       */
 159      public function test_is_automatic(int $completion, bool $expectedresult) {
 160          $cmcompletion = $this->setup_data($completion);
 161  
 162          $this->assertEquals($expectedresult, $cmcompletion->is_automatic());
 163      }
 164  
 165      /**
 166       * Data provider for test_get_overall_completion().
 167       * @return array[]
 168       */
 169      public function overall_completion_provider(): array {
 170          return [
 171              'Complete' => [COMPLETION_COMPLETE],
 172              'Incomplete' => [COMPLETION_INCOMPLETE],
 173          ];
 174      }
 175  
 176      /**
 177       * Test for get_overall_completion().
 178       *
 179       * @dataProvider overall_completion_provider
 180       * @param int $state
 181       */
 182      public function test_get_overall_completion(int $state) {
 183          $completiondata = (object)['completionstate' => $state];
 184          $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, [], $completiondata);
 185          $this->assertEquals($state, $cmcompletion->get_overall_completion());
 186      }
 187  
 188      /**
 189       * Data provider for test_get_details().
 190       * @return array[]
 191       */
 192      public function get_details_provider() {
 193          return [
 194              'No completion tracking' => [
 195                  COMPLETION_TRACKING_NONE, null, null, []
 196              ],
 197              'Manual completion tracking' => [
 198                  COMPLETION_TRACKING_MANUAL, null, null, []
 199              ],
 200              'Automatic, require view, not viewed' => [
 201                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, [
 202                      'completionview' => (object)[
 203                          'status' => COMPLETION_INCOMPLETE,
 204                          'description' => get_string('detail_desc:view', 'completion'),
 205                      ]
 206                  ]
 207              ],
 208              'Automatic, require view, viewed' => [
 209                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, [
 210                      'completionview' => (object)[
 211                          'status' => COMPLETION_COMPLETE,
 212                          'description' => get_string('detail_desc:view', 'completion'),
 213                      ]
 214                  ]
 215              ],
 216              'Automatic, require grade, incomplete' => [
 217                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, [
 218                      'completionusegrade' => (object)[
 219                          'status' => COMPLETION_INCOMPLETE,
 220                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 221                      ]
 222                  ]
 223              ],
 224              'Automatic, require grade, complete' => [
 225                  COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, [
 226                      'completionusegrade' => (object)[
 227                          'status' => COMPLETION_COMPLETE,
 228                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 229                      ]
 230                  ]
 231              ],
 232              'Automatic, require view (complete) and grade (incomplete)' => [
 233                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
 234                      'completionview' => (object)[
 235                          'status' => COMPLETION_COMPLETE,
 236                          'description' => get_string('detail_desc:view', 'completion'),
 237                      ],
 238                      'completionusegrade' => (object)[
 239                          'status' => COMPLETION_INCOMPLETE,
 240                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 241                      ]
 242                  ]
 243              ],
 244              'Automatic, require view (incomplete) and grade (complete)' => [
 245                  COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, [
 246                      'completionview' => (object)[
 247                          'status' => COMPLETION_INCOMPLETE,
 248                          'description' => get_string('detail_desc:view', 'completion'),
 249                      ],
 250                      'completionusegrade' => (object)[
 251                          'status' => COMPLETION_COMPLETE,
 252                          'description' => get_string('detail_desc:receivegrade', 'completion'),
 253                      ]
 254                  ]
 255              ],
 256          ];
 257      }
 258  
 259      /**
 260       * Test for \core_completion\cm_completion_details::get_details().
 261       *
 262       * @dataProvider get_details_provider
 263       * @param int $completion The completion tracking mode.
 264       * @param int|null $completionview Completion status of the "view" completion condition.
 265       * @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
 266       * @param array $expecteddetails Expected completion details returned by get_details().
 267       */
 268      public function test_get_details(int $completion, ?int $completionview, ?int $completiongrade, array $expecteddetails) {
 269          $options = [];
 270          $getdatareturn = (object)[
 271              'viewed' => $completionview,
 272              'completiongrade' => $completiongrade,
 273          ];
 274  
 275          if (!is_null($completionview)) {
 276              $options['completionview'] = true;
 277          }
 278          if (!is_null($completiongrade)) {
 279              $options['completionusegrade'] = true;
 280          }
 281  
 282          $cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
 283          $this->assertEquals($expecteddetails, $cmcompletion->get_details());
 284      }
 285  
 286      /**
 287       * Data provider for test_get_details().
 288       * @return array[]
 289       */
 290      public function get_details_custom_order_provider() {
 291          return [
 292              'Custom and view/grade standard conditions, view first and grade last' => [
 293                  true,
 294                  true,
 295                  [
 296                      'completionsubmit' => true,
 297                  ],
 298                  'assign',
 299                  ['completionview', 'completionsubmit', 'completionusegrade'],
 300              ],
 301              'Custom and view/grade standard conditions, grade not last' => [
 302                  true,
 303                  true,
 304                  [
 305                      'completionminattempts' => 2,
 306                      'completionusegrade' => 50,
 307                      'completionpassorattemptsexhausted' => 1,
 308                  ],
 309                  'quiz',
 310                  ['completionview', 'completionminattempts', 'completionusegrade', 'completionpassorattemptsexhausted'],
 311              ],
 312              'Custom and grade standard conditions only, no view condition' => [
 313                  false,
 314                  true,
 315                  [
 316                      'completionsubmit' => true,
 317                  ],
 318                  'assign',
 319                  ['completionsubmit', 'completionusegrade'],
 320              ],
 321              'Custom and view standard conditions only, no grade condition' => [
 322                  true,
 323                  false,
 324                  [
 325                      'completionsubmit' => true
 326                  ],
 327                  'assign',
 328                  ['completionview', 'completionsubmit'],
 329              ],
 330              'View and grade conditions only, activity with no custom conditions' => [
 331                  true,
 332                  true,
 333                  [
 334                      'completionview' => true,
 335                      'completionusegrade' => true
 336                  ],
 337                  'workshop',
 338                  ['completionview', 'completionusegrade'],
 339              ],
 340              'View condition only, activity with no custom conditions' => [
 341                  true,
 342                  false,
 343                  [
 344                      'completionview' => true,
 345                  ],
 346                  'workshop',
 347                  ['completionview'],
 348              ],
 349          ];
 350      }
 351  
 352      /**
 353       * Test custom sort order is functioning in \core_completion\cm_completion_details::get_details().
 354       *
 355       * @dataProvider get_details_custom_order_provider
 356       * @param bool $completionview Completion status of the "view" completion condition.
 357       * @param bool $completiongrade Completion status of the "must receive grade" completion condition.
 358       * @param array $customcompletionrules Custom completion requirements, along with their values.
 359       * @param string $modname The name of the module having data fetched.
 360       * @param array $expectedorder The expected order of completion conditions returned about the module.
 361       */
 362      public function test_get_details_custom_order(bool $completionview, bool $completiongrade, array $customcompletionrules,
 363              string $modname, array $expectedorder) {
 364  
 365          $options['customcompletion'] = [];
 366          $customcompletiondata = [];
 367  
 368          if ($completionview) {
 369              $options['completionview'] = true;
 370          }
 371  
 372          if ($completiongrade) {
 373              $options['completionusegrade'] = true;
 374          }
 375  
 376          // Set up the completion rules for the completion info.
 377          foreach ($customcompletionrules as $customtype => $isenabled) {
 378              $customcompletiondata[$customtype] = COMPLETION_COMPLETE;
 379          }
 380  
 381          $getdatareturn = (object)[
 382              'viewed' => $completionview ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
 383              'completiongrade' => $completiongrade ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
 384              'customcompletion' => $customcompletiondata,
 385          ];
 386  
 387          $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, $options, $getdatareturn, $modname);
 388  
 389          $this->completioninfo->expects($this->any())
 390              ->method('get_data')
 391              ->willReturn($getdatareturn);
 392  
 393          $fetcheddetails = $cmcompletion->get_details();
 394  
 395          // Check the expected number of items are returned, and sorted in the correct order.
 396          $this->assertCount(count($expectedorder), $fetcheddetails);
 397          $this->assertTrue((array_keys($fetcheddetails) === $expectedorder));
 398      }
 399  }