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 310 and 401] [Versions 39 and 401]

   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 class core_course_category methods invoking hooks.
  19   *
  20   * @package    core_course
  21   * @category   test
  22   * @copyright  2020 Ruslan Kabalin
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core_course;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  global $CFG;
  31  require_once($CFG->dirroot . '/course/tests/fixtures/mock_hooks.php');
  32  
  33  use PHPUnit\Framework\MockObject\MockObject;
  34  use core_course\test\mock_hooks;
  35  
  36  /**
  37   * Functional test for class core_course_category methods invoking hooks.
  38   */
  39  class category_hooks_test extends \advanced_testcase {
  40  
  41      protected function setUp(): void {
  42          $this->resetAfterTest();
  43          $this->setAdminUser();
  44      }
  45  
  46      /**
  47       * Provides mocked category configured for named callback function.
  48       *
  49       * get_plugins_callback_function will return callable prefixed with `tool_unittest_`,
  50       * the actual callbacks are defined in mock_hooks.php fixture file.
  51       *
  52       * @param core_course_category $category Category to mock
  53       * @param string $callback Callback function used in method we test.
  54       * @return MockObject
  55       */
  56      public function get_mock_category(\core_course_category $category, string $callback = '') : MockObject {
  57          // Setup mock object for \core_course_category.
  58          // Disable original constructor, since we can't use it directly since it is private.
  59          $mockcategory = $this->getMockBuilder(\core_course_category::class)
  60              ->onlyMethods(['get_plugins_callback_function'])
  61              ->disableOriginalConstructor()
  62              ->getMock();
  63  
  64          // Define get_plugins_callback_function use and return value.
  65          if (!empty($callback)) {
  66              $mockcategory->method('get_plugins_callback_function')
  67                  ->with($this->equalTo($callback))
  68                  ->willReturn(['tool_unittest_' . $callback]);
  69          }
  70  
  71          // Modify constructor visibility and invoke mock object with real object.
  72          // This is used to overcome private constructor.
  73          $reflected = new \ReflectionClass(\core_course_category::class);
  74          $constructor = $reflected->getConstructor();
  75          $constructor->setAccessible(true);
  76          $constructor->invoke($mockcategory, $category->get_db_record());
  77  
  78          return $mockcategory;
  79      }
  80  
  81      public function test_can_course_category_delete_hook() {
  82          $category1 = \core_course_category::create(array('name' => 'Cat1'));
  83          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
  84          $category3 = \core_course_category::create(array('name' => 'Cat3'));
  85  
  86          $mockcategory2 = $this->get_mock_category($category2, 'can_course_category_delete');
  87  
  88          // Add course to mocked clone of category2.
  89          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
  90  
  91          // Now configure fixture to return false for the callback.
  92          mock_hooks::set_can_course_category_delete_return(false);
  93          $this->assertFalse($mockcategory2->can_delete_full($category3->id));
  94  
  95          // Now configure fixture to return true for the callback.
  96          mock_hooks::set_can_course_category_delete_return(true);
  97          $this->assertTrue($mockcategory2->can_delete_full($category3->id));
  98  
  99          // Verify passed arguments.
 100          $arguments = mock_hooks::get_calling_arguments();
 101          $this->assertCount(1, $arguments);
 102  
 103          // Argument 1 is the same core_course_category instance.
 104          $argument = array_shift($arguments);
 105          $this->assertSame($mockcategory2, $argument);
 106      }
 107  
 108      public function test_can_course_category_delete_move_hook() {
 109          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 110          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 111          $category3 = \core_course_category::create(array('name' => 'Cat3'));
 112  
 113          $mockcategory2 = $this->get_mock_category($category2, 'can_course_category_delete_move');
 114  
 115          // Add course to mocked clone of category2.
 116          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
 117  
 118          // Now configure fixture to return false for the callback.
 119          mock_hooks::set_can_course_category_delete_move_return(false);
 120          $this->assertFalse($mockcategory2->can_move_content_to($category3->id));
 121  
 122          // Now configure fixture to return true for the callback.
 123          mock_hooks::set_can_course_category_delete_move_return(true);
 124          $this->assertTrue($mockcategory2->can_move_content_to($category3->id));
 125  
 126          // Verify passed arguments.
 127          $arguments = mock_hooks::get_calling_arguments();
 128          $this->assertCount(2, $arguments);
 129  
 130          // Argument 1 is the same core_course_category instance.
 131          $argument = array_shift($arguments);
 132          $this->assertSame($mockcategory2, $argument);
 133  
 134          // Argument 2 is referring to category 3.
 135          $argument = array_shift($arguments);
 136          $this->assertInstanceOf(\core_course_category::class, $argument);
 137          $this->assertEquals($category3->id, $argument->id);
 138      }
 139  
 140      public function test_pre_course_category_delete_hook() {
 141          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 142          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 143  
 144          $mockcategory2 = $this->get_mock_category($category2, 'pre_course_category_delete');
 145          $mockcategory2->delete_full();
 146  
 147          // Verify passed arguments.
 148          $arguments = mock_hooks::get_calling_arguments();
 149          $this->assertCount(1, $arguments);
 150  
 151          // Argument 1 is the category object.
 152          $argument = array_shift($arguments);
 153          $this->assertEquals($mockcategory2->get_db_record(), $argument);
 154      }
 155  
 156      public function test_pre_course_category_delete_move_hook() {
 157          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 158          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 159          $category3 = \core_course_category::create(array('name' => 'Cat3'));
 160  
 161          $mockcategory2 = $this->get_mock_category($category2, 'pre_course_category_delete_move');
 162  
 163          // Add course to mocked clone of category2.
 164          $course1 = $this->getDataGenerator()->create_course(array('category' => $mockcategory2->id));
 165  
 166          $mockcategory2->delete_move($category3->id);
 167  
 168          // Verify passed arguments.
 169          $arguments = mock_hooks::get_calling_arguments();
 170          $this->assertCount(2, $arguments);
 171  
 172          // Argument 1 is the same core_course_category instance.
 173          $argument = array_shift($arguments);
 174          $this->assertSame($mockcategory2, $argument);
 175  
 176          // Argument 2 is referring to category 3.
 177          $argument = array_shift($arguments);
 178          $this->assertInstanceOf(\core_course_category::class, $argument);
 179          $this->assertEquals($category3->id, $argument->id);
 180      }
 181  
 182      public function test_get_course_category_contents_hook() {
 183          $category1 = \core_course_category::create(array('name' => 'Cat1'));
 184          $category2 = \core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
 185  
 186          $mockcategory2 = $this->get_mock_category($category2);
 187  
 188          // Define get_plugins_callback_function use in the mock, it is called twice for different callback in the form.
 189          $mockcategory2->expects($this->exactly(2))
 190              ->method('get_plugins_callback_function')
 191              ->withConsecutive(
 192                  [$this->equalTo('can_course_category_delete')],
 193                  [$this->equalTo('get_course_category_contents')]
 194              )
 195              ->willReturn(
 196                  ['tool_unittest_can_course_category_delete'],
 197                  ['tool_unittest_get_course_category_contents']
 198              );
 199  
 200          // Now configure fixture to return string for the callback.
 201          $content = 'Bunch of test artefacts';
 202          mock_hooks::set_get_course_category_contents_return($content);
 203  
 204          $mform = new \core_course_deletecategory_form(null, $mockcategory2);
 205          $this->expectOutputRegex("/<li>$content<\/li>/");
 206          $mform->display();
 207  
 208          // Verify passed arguments.
 209          $arguments = mock_hooks::get_calling_arguments();
 210          $this->assertCount(1, $arguments);
 211  
 212          // Argument 1 is the same core_course_category instance.
 213          $argument = array_shift($arguments);
 214          $this->assertSame($mockcategory2, $argument);
 215      }
 216  }