Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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  namespace core_courseformat;
  18  
  19  use moodle_exception;
  20  use stdClass;
  21  
  22  /**
  23   * Tests for the stateactions class.
  24   *
  25   * @package    core_courseformat
  26   * @category   test
  27   * @copyright  2021 Sara Arjona (sara@moodle.com)
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   * @coversDefaultClass \core_courseformat\stateactions
  30   */
  31  class stateactions_test extends \advanced_testcase {
  32  
  33      /**
  34       * Helper method to create an activity into a section and add it to the $sections and $activities arrays.
  35       *
  36       * @param int $courseid Course identifier where the activity will be added.
  37       * @param string $type Activity type ('forum', 'assign', ...).
  38       * @param int $section Section number where the activity will be added.
  39       * @param bool $visible Whether the activity will be visible or not.
  40       * @return int the activity cm id
  41       */
  42      private function create_activity(
  43          int $courseid,
  44          string $type,
  45          int $section,
  46          bool $visible = true
  47      ): int {
  48  
  49          $activity = $this->getDataGenerator()->create_module(
  50              $type,
  51              ['course' => $courseid],
  52              [
  53                  'section' => $section,
  54                  'visible' => $visible
  55              ]
  56          );
  57          return $activity->cmid;
  58      }
  59  
  60      /**
  61       * Helper to create a course and generate a section list.
  62       *
  63       * @param string $format the course format
  64       * @param int $sections the number of sections
  65       * @param int[] $hiddensections the section numbers to hide
  66       * @return stdClass the course object
  67       */
  68      private function create_course(string $format, int $sections, array $hiddensections): stdClass {
  69          global $DB;
  70  
  71          $course = $this->getDataGenerator()->create_course(['numsections' => $sections, 'format' => $format]);
  72          foreach ($hiddensections as $section) {
  73              set_section_visible($course->id, $section, 0);
  74          }
  75  
  76          return $course;
  77      }
  78  
  79      /**
  80       * Return an array if the course references.
  81       *
  82       * This method is used to create alias to sections and other stuff in the dataProviders.
  83       *
  84       * @param stdClass $course the course object
  85       * @return int[] a relation betwee all references and its element id
  86       */
  87      private function course_references(stdClass $course): array {
  88          global $DB;
  89  
  90          $references = [];
  91  
  92          $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id]);
  93          foreach ($sectionrecords as $id => $section) {
  94              $references["section{$section->section}"] = $section->id;
  95          }
  96          $references['course'] = $course->id;
  97          $references['invalidsection'] = -1;
  98          $references['invalidcm'] = -1;
  99  
 100          return $references;
 101      }
 102  
 103      /**
 104       * Translate a references array into current ids.
 105       *
 106       * @param string[] $references the references list
 107       * @param string[] $values the values to translate
 108       * @return int[] the list of ids
 109       */
 110      private function translate_references(array $references, array $values): array {
 111          $result = [];
 112          foreach ($values as $value) {
 113              $result[] = $references[$value];
 114          }
 115          return $result;
 116      }
 117  
 118      /**
 119       * Generate a sorted and summarized list of an state updates message.
 120       *
 121       * It is important to note that the order in the update messages are not important in a real scenario
 122       * because each message affects a specific part of the course state. However, for the PHPUnit test
 123       * have them sorted and classified simplifies the asserts.
 124       *
 125       * @param stateupdates $updateobj the state updates object
 126       * @return array of all data updates.
 127       */
 128      private function summarize_updates(stateupdates $updateobj): array {
 129          // Check state returned after executing given action.
 130          $updatelist = $updateobj->jsonSerialize();
 131  
 132          // Initial summary structure.
 133          $result = [
 134              'create' => [
 135                  'course' => [],
 136                  'section' => [],
 137                  'cm' => [],
 138                  'count' => 0,
 139              ],
 140              'put' => [
 141                  'course' => [],
 142                  'section' => [],
 143                  'cm' => [],
 144                  'count' => 0,
 145              ],
 146              'remove' => [
 147                  'course' => [],
 148                  'section' => [],
 149                  'cm' => [],
 150                  'count' => 0,
 151              ],
 152          ];
 153          foreach ($updatelist as $update) {
 154              if (!isset($result[$update->action])) {
 155                  $result[$update->action] = [
 156                      'course' => [],
 157                      'section' => [],
 158                      'cm' => [],
 159                      'count' => 0,
 160                  ];
 161              }
 162              $elementid = $update->fields->id ?? 0;
 163              $result[$update->action][$update->name][$elementid] = $update->fields;
 164              $result[$update->action]['count']++;
 165          }
 166          return $result;
 167      }
 168  
 169      /**
 170       * Test the behaviour course_state.
 171       *
 172       * @dataProvider get_state_provider
 173       * @covers ::course_state
 174       * @covers ::section_state
 175       * @covers ::cm_state
 176       *
 177       * @param string $format The course will be created with this course format.
 178       * @param string $role The role of the user that will execute the method.
 179       * @param string $method the method to call
 180       * @param array $params the ids, targetsection and targetcm to use as params
 181       * @param array $expectedresults List of the course module names expected after calling the method.
 182       * @param bool $expectedexception If this call will raise an exception.
 183       */
 184      public function test_get_state(
 185          string $format,
 186          string $role,
 187          string $method,
 188          array $params,
 189          array $expectedresults,
 190          bool $expectedexception = false
 191      ): void {
 192  
 193          $this->resetAfterTest();
 194  
 195          // Create a course with 3 sections, 1 of them hidden.
 196          $course = $this->create_course($format, 3, [2]);
 197  
 198          $references = $this->course_references($course);
 199  
 200          // Create and enrol user using given role.
 201          if ($role == 'admin') {
 202              $this->setAdminUser();
 203          } else {
 204              $user = $this->getDataGenerator()->create_user();
 205              if ($role != 'unenroled') {
 206                  $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
 207              }
 208              $this->setUser($user);
 209          }
 210  
 211          // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
 212          $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
 213          $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
 214          $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
 215          $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
 216  
 217          if ($expectedexception) {
 218              $this->expectException(moodle_exception::class);
 219          }
 220  
 221          // Initialise stateupdates.
 222          $courseformat = course_get_format($course->id);
 223          $updates = new stateupdates($courseformat);
 224  
 225          // Execute given method.
 226          $actions = new stateactions();
 227          $actions->$method(
 228              $updates,
 229              $course,
 230              $this->translate_references($references, $params['ids']),
 231              $references[$params['targetsectionid']] ?? null,
 232              $references[$params['targetcmid']] ?? null
 233          );
 234  
 235          // Format results in a way we can compare easily.
 236          $results = $this->summarize_updates($updates);
 237  
 238          // The state actions does not use create or remove actions because they are designed
 239          // to refresh parts of the state.
 240          $this->assertEquals(0, $results['create']['count']);
 241          $this->assertEquals(0, $results['remove']['count']);
 242  
 243          // Validate we have all the expected entries.
 244          $expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']);
 245          $this->assertEquals($expectedtotal, $results['put']['count']);
 246  
 247          // Validate course, section and cm.
 248          foreach ($expectedresults as $name => $referencekeys) {
 249              foreach ($referencekeys as $referencekey) {
 250                  $this->assertArrayHasKey($references[$referencekey], $results['put'][$name]);
 251              }
 252          }
 253      }
 254  
 255      /**
 256       * Data provider for data request creation tests.
 257       *
 258       * @return array the testing scenarios
 259       */
 260      public function get_state_provider(): array {
 261          return array_merge(
 262              $this->course_state_provider('weeks'),
 263              $this->course_state_provider('topics'),
 264              $this->course_state_provider('social'),
 265              $this->section_state_provider('weeks', 'admin'),
 266              $this->section_state_provider('weeks', 'editingteacher'),
 267              $this->section_state_provider('weeks', 'student'),
 268              $this->section_state_provider('topics', 'admin'),
 269              $this->section_state_provider('topics', 'editingteacher'),
 270              $this->section_state_provider('topics', 'student'),
 271              $this->section_state_provider('social', 'admin'),
 272              $this->section_state_provider('social', 'editingteacher'),
 273              $this->section_state_provider('social', 'student'),
 274              $this->cm_state_provider('weeks', 'admin'),
 275              $this->cm_state_provider('weeks', 'editingteacher'),
 276              $this->cm_state_provider('weeks', 'student'),
 277              $this->cm_state_provider('topics', 'admin'),
 278              $this->cm_state_provider('topics', 'editingteacher'),
 279              $this->cm_state_provider('topics', 'student'),
 280              $this->cm_state_provider('social', 'admin'),
 281              $this->cm_state_provider('social', 'editingteacher'),
 282              $this->cm_state_provider('social', 'student'),
 283          );
 284      }
 285  
 286      /**
 287       * Course state data provider.
 288       *
 289       * @param string $format the course format
 290       * @return array the testing scenarios
 291       */
 292      public function course_state_provider(string $format): array {
 293          $expectedexception = ($format === 'social');
 294          return [
 295              // Tests for course_state.
 296              "admin $format course_state" => [
 297                  'format' => $format,
 298                  'role' => 'admin',
 299                  'method' => 'course_state',
 300                  'params' => [
 301                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 302                  ],
 303                  'expectedresults' => [
 304                      'course' => ['course'],
 305                      'section' => ['section0', 'section1', 'section2', 'section3'],
 306                      'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
 307                  ],
 308                  'expectedexception' => $expectedexception,
 309              ],
 310              "editingteacher $format course_state" => [
 311                  'format' => $format,
 312                  'role' => 'editingteacher',
 313                  'method' => 'course_state',
 314                  'params' => [
 315                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 316                  ],
 317                  'expectedresults' => [
 318                      'course' => ['course'],
 319                      'section' => ['section0', 'section1', 'section2', 'section3'],
 320                      'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
 321                  ],
 322                  'expectedexception' => $expectedexception,
 323              ],
 324              "student $format course_state" => [
 325                  'format' => $format,
 326                  'role' => 'student',
 327                  'method' => 'course_state',
 328                  'params' => [
 329                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 330                  ],
 331                  'expectedresults' => [
 332                      'course' => ['course'],
 333                      'section' => ['section0', 'section1', 'section3'],
 334                      'cm' => ['cm0'],
 335                  ],
 336                  'expectedexception' => $expectedexception,
 337              ],
 338          ];
 339      }
 340  
 341      /**
 342       * Section state data provider.
 343       *
 344       * @param string $format the course format
 345       * @param string $role the user role
 346       * @return array the testing scenarios
 347       */
 348      public function section_state_provider(string $format, string $role): array {
 349  
 350          // Social format will raise an exception and debug messages because it does not
 351          // use sections and it does not provide a renderer.
 352          $expectedexception = ($format === 'social');
 353  
 354          // All sections and cms that the user can access to.
 355          $usersections = ['section0', 'section1', 'section2', 'section3'];
 356          $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
 357          if ($role == 'student') {
 358              $usersections = ['section0', 'section1', 'section3'];
 359              $usercms = ['cm0'];
 360          }
 361  
 362          return [
 363              "$role $format section_state no section" => [
 364                  'format' => $format,
 365                  'role' => $role,
 366                  'method' => 'section_state',
 367                  'params' => [
 368                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 369                  ],
 370                  'expectedresults' => [],
 371                  'expectedexception' => true,
 372              ],
 373              "$role $format section_state section 0" => [
 374                  'format' => $format,
 375                  'role' => $role,
 376                  'method' => 'section_state',
 377                  'params' => [
 378                      'ids' => ['section0'], 'targetsectionid' => null, 'targetcmid' => null
 379                  ],
 380                  'expectedresults' => [
 381                      'course' => [],
 382                      'section' => array_intersect(['section0'], $usersections),
 383                      'cm' => [],
 384                  ],
 385                  'expectedexception' => $expectedexception,
 386              ],
 387              "$role $format section_state visible section" => [
 388                  'format' => $format,
 389                  'role' => $role,
 390                  'method' => 'section_state',
 391                  'params' => [
 392                      'ids' => ['section1'], 'targetsectionid' => null, 'targetcmid' => null
 393                  ],
 394                  'expectedresults' => [
 395                      'course' => [],
 396                      'section' => array_intersect(['section1'], $usersections),
 397                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 398                  ],
 399                  'expectedexception' => $expectedexception,
 400              ],
 401              "$role $format section_state hidden section" => [
 402                  'format' => $format,
 403                  'role' => $role,
 404                  'method' => 'section_state',
 405                  'params' => [
 406                      'ids' => ['section2'], 'targetsectionid' => null, 'targetcmid' => null
 407                  ],
 408                  'expectedresults' => [
 409                      'course' => [],
 410                      'section' => array_intersect(['section2'], $usersections),
 411                      'cm' => array_intersect(['cm2', 'cm3'], $usercms),
 412                  ],
 413                  'expectedexception' => $expectedexception,
 414              ],
 415              "$role $format section_state several sections" => [
 416                  'format' => $format,
 417                  'role' => $role,
 418                  'method' => 'section_state',
 419                  'params' => [
 420                      'ids' => ['section1', 'section3'], 'targetsectionid' => null, 'targetcmid' => null
 421                  ],
 422                  'expectedresults' => [
 423                      'course' => [],
 424                      'section' => array_intersect(['section1', 'section3'], $usersections),
 425                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 426                  ],
 427                  'expectedexception' => $expectedexception,
 428              ],
 429              "$role $format section_state invalid section" => [
 430                  'format' => $format,
 431                  'role' => $role,
 432                  'method' => 'section_state',
 433                  'params' => [
 434                      'ids' => ['invalidsection'], 'targetsectionid' => null, 'targetcmid' => null
 435                  ],
 436                  'expectedresults' => [],
 437                  'expectedexception' => true,
 438              ],
 439              "$role $format section_state using target section" => [
 440                  'format' => $format,
 441                  'role' => $role,
 442                  'method' => 'section_state',
 443                  'params' => [
 444                      'ids' => ['section1'], 'targetsectionid' => 'section3', 'targetcmid' => null
 445                  ],
 446                  'expectedresults' => [
 447                      'course' => [],
 448                      'section' => array_intersect(['section1', 'section3'], $usersections),
 449                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 450                  ],
 451                  'expectedexception' => $expectedexception,
 452              ],
 453              "$role $format section_state using target targetcmid" => [
 454                  'format' => $format,
 455                  'role' => $role,
 456                  'method' => 'section_state',
 457                  'params' => [
 458                      'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1'
 459                  ],
 460                  'expectedresults' => [
 461                      'course' => [],
 462                      'section' => array_intersect(['section3'], $usersections),
 463                      'cm' => array_intersect(['cm1'], $usercms),
 464                  ],
 465                  'expectedexception' => $expectedexception,
 466              ],
 467          ];
 468      }
 469  
 470      /**
 471       * Course module state data provider.
 472       *
 473       * @param string $format the course format
 474       * @param string $role the user role
 475       * @return array the testing scenarios
 476       */
 477      public function cm_state_provider(string $format, string $role): array {
 478  
 479          // All sections and cms that the user can access to.
 480          $usersections = ['section0', 'section1', 'section2', 'section3'];
 481          $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
 482          if ($role == 'student') {
 483              $usersections = ['section0', 'section1', 'section3'];
 484              $usercms = ['cm0'];
 485          }
 486  
 487          return [
 488              "$role $format cm_state no cms" => [
 489                  'format' => $format,
 490                  'role' => $role,
 491                  'method' => 'cm_state',
 492                  'params' => [
 493                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 494                  ],
 495                  'expectedresults' => [],
 496                  'expectedexception' => true,
 497              ],
 498              "$role $format cm_state visible cm" => [
 499                  'format' => $format,
 500                  'role' => $role,
 501                  'method' => 'cm_state',
 502                  'params' => [
 503                      'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => null
 504                  ],
 505                  'expectedresults' => [
 506                      'course' => [],
 507                      'section' => array_intersect(['section1'], $usersections),
 508                      'cm' => array_intersect(['cm0'], $usercms),
 509                  ],
 510                  'expectedexception' => false,
 511              ],
 512              "$role $format cm_state hidden cm" => [
 513                  'format' => $format,
 514                  'role' => $role,
 515                  'method' => 'cm_state',
 516                  'params' => [
 517                      'ids' => ['cm1'], 'targetsectionid' => null, 'targetcmid' => null
 518                  ],
 519                  'expectedresults' => [
 520                      'course' => [],
 521                      'section' => array_intersect(['section1'], $usersections),
 522                      'cm' => array_intersect(['cm1'], $usercms),
 523                  ],
 524                  'expectedexception' => false,
 525              ],
 526              "$role $format cm_state several cm" => [
 527                  'format' => $format,
 528                  'role' => $role,
 529                  'method' => 'cm_state',
 530                  'params' => [
 531                      'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null
 532                  ],
 533                  'expectedresults' => [
 534                      'course' => [],
 535                      'section' => array_intersect(['section1', 'section2'], $usersections),
 536                      'cm' => array_intersect(['cm0', 'cm2'], $usercms),
 537                  ],
 538                  'expectedexception' => false,
 539              ],
 540              "$role $format cm_state using targetsection" => [
 541                  'format' => $format,
 542                  'role' => $role,
 543                  'method' => 'cm_state',
 544                  'params' => [
 545                      'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null
 546                  ],
 547                  'expectedresults' => [
 548                      'course' => [],
 549                      'section' => array_intersect(['section1', 'section2'], $usersections),
 550                      'cm' => array_intersect(['cm0'], $usercms),
 551                  ],
 552                  'expectedexception' => ($format === 'social'),
 553              ],
 554              "$role $format cm_state using targetcm" => [
 555                  'format' => $format,
 556                  'role' => $role,
 557                  'method' => 'cm_state',
 558                  'params' => [
 559                      'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3'
 560                  ],
 561                  'expectedresults' => [
 562                      'course' => [],
 563                      'section' => array_intersect(['section1', 'section2'], $usersections),
 564                      'cm' => array_intersect(['cm0', 'cm3'], $usercms),
 565                  ],
 566                  'expectedexception' => false,
 567              ],
 568              "$role $format cm_state using an invalid cm" => [
 569                  'format' => $format,
 570                  'role' => $role,
 571                  'method' => 'cm_state',
 572                  'params' => [
 573                      'ids' => ['invalidcm'], 'targetsectionid' => null, 'targetcmid' => null
 574                  ],
 575                  'expectedresults' => [],
 576                  'expectedexception' => true,
 577              ],
 578          ];
 579      }
 580  
 581      /**
 582       * Internal method for testing a specific state action.
 583       *
 584       * @param string $method the method to test
 585       * @param string $role the user role
 586       * @param string[] $idrefs the sections or cms id references to be used as method params
 587       * @param bool $expectedexception whether the call should throw an exception
 588       * @param int $expectedtotal the expected total number of state puts
 589       * @param string|null $coursefield the course field to check
 590       * @param int|string|null $coursevalue the section field value
 591       * @param string|null $sectionfield the section field to check
 592       * @param int|string|null $sectionvalue the section field value
 593       * @param string|null $cmfield the cm field to check
 594       * @param int|string|null $cmvalue the cm field value
 595       * @return array the state update summary
 596       */
 597      protected function basic_state_text(
 598          string  $method = 'section_hide',
 599          string  $role = 'editingteacher',
 600          array   $idrefs = [],
 601          bool    $expectedexception = false,
 602          int     $expectedtotal = 0,
 603          ?string $coursefield = null,
 604                  $coursevalue = 0,
 605          ?string $sectionfield = null,
 606                  $sectionvalue = 0,
 607          ?string $cmfield = null,
 608                  $cmvalue = 0
 609      ): array {
 610          $this->resetAfterTest();
 611  
 612          // Create a course with 3 sections, 1 of them hidden.
 613          $course = $this->create_course('topics', 3, [2]);
 614  
 615          $references = $this->course_references($course);
 616  
 617          $user = $this->getDataGenerator()->create_user();
 618          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
 619          $this->setUser($user);
 620  
 621          // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
 622          $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
 623          $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
 624          $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
 625          $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
 626  
 627          if ($expectedexception) {
 628              $this->expectException(moodle_exception::class);
 629          }
 630  
 631          // Initialise stateupdates.
 632          $courseformat = course_get_format($course->id);
 633          $updates = new stateupdates($courseformat);
 634  
 635          // Execute the method.
 636          $actions = new stateactions();
 637          $actions->$method(
 638              $updates,
 639              $course,
 640              $this->translate_references($references, $idrefs),
 641          );
 642  
 643          // Format results in a way we can compare easily.
 644          $results = $this->summarize_updates($updates);
 645  
 646          // Most state actions does not use create or remove actions because they are designed
 647          // to refresh parts of the state.
 648          $this->assertEquals(0, $results['create']['count']);
 649          $this->assertEquals(0, $results['remove']['count']);
 650  
 651          // Validate we have all the expected entries.
 652          $this->assertEquals($expectedtotal, $results['put']['count']);
 653  
 654          // Validate course, section and cm.
 655          if (!empty($coursefield)) {
 656              foreach ($results['put']['course'] as $courseid) {
 657                  $this->assertEquals($coursevalue, $results['put']['course'][$courseid][$coursefield]);
 658              }
 659          }
 660          if (!empty($sectionfield)) {
 661              foreach ($results['put']['section'] as $section) {
 662                  $this->assertEquals($sectionvalue, $section->$sectionfield);
 663              }
 664          }
 665          if (!empty($cmfield)) {
 666              foreach ($results['put']['cm'] as $cm) {
 667                  $this->assertEquals($cmvalue, $cm->$cmfield);
 668              }
 669          }
 670          return $results;
 671      }
 672  
 673      /**
 674       * Data provider for basic role tests.
 675       *
 676       * @return array the testing scenarios
 677       */
 678      public function basic_role_provider() {
 679          return [
 680              'editingteacher' => [
 681                  'role' => 'editingteacher',
 682                  'expectedexception' => false,
 683              ],
 684              'teacher' => [
 685                  'role' => 'teacher',
 686                  'expectedexception' => true,
 687              ],
 688              'student' => [
 689                  'role' => 'student',
 690                  'expectedexception' => true,
 691              ],
 692              'guest' => [
 693                  'role' => 'guest',
 694                  'expectedexception' => true,
 695              ],
 696          ];
 697      }
 698  
 699      /**
 700       * Test for cm_moveright
 701       *
 702       * @covers ::cm_moveright
 703       * @dataProvider basic_role_provider
 704       * @param string $role the user role
 705       * @param bool $expectedexception if it will expect an exception.
 706       */
 707      public function test_cm_moveright(
 708          string $role = 'editingteacher',
 709          bool $expectedexception = false
 710      ): void {
 711          $this->basic_state_text(
 712              'cm_moveright',
 713              $role,
 714              ['cm0', 'cm1', 'cm2', 'cm3'],
 715              $expectedexception,
 716              4,
 717              null,
 718              null,
 719              null,
 720              null,
 721              'indent',
 722              1
 723          );
 724      }
 725  
 726      /**
 727       * Test for cm_moveleft
 728       *
 729       * @covers ::cm_moveleft
 730       * @dataProvider basic_role_provider
 731       * @param string $role the user role
 732       * @param bool $expectedexception if it will expect an exception.
 733       */
 734      public function test_cm_moveleft(
 735          string $role = 'editingteacher',
 736          bool $expectedexception = false
 737      ): void {
 738          $this->basic_state_text(
 739              'cm_moveleft',
 740              $role,
 741              ['cm0', 'cm1', 'cm2', 'cm3'],
 742              $expectedexception,
 743              4,
 744              null,
 745              null,
 746              null,
 747              null,
 748              'indent',
 749              0
 750          );
 751      }
 752  }