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.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace core_courseformat;

> use course_modinfo;
use moodle_exception; use stdClass; /** * Tests for the stateactions class. * * @package core_courseformat * @category test * @copyright 2021 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \core_courseformat\stateactions */ class stateactions_test extends \advanced_testcase {
< < /** < * Setup to ensure that fixtures are loaded. < */ < public static function setupBeforeClass(): void { < global $CFG; < require_once($CFG->dirroot . '/lib/externallib.php'); < } <
/** * Helper method to create an activity into a section and add it to the $sections and $activities arrays. * * @param int $courseid Course identifier where the activity will be added. * @param string $type Activity type ('forum', 'assign', ...). * @param int $section Section number where the activity will be added. * @param bool $visible Whether the activity will be visible or not. * @return int the activity cm id */ private function create_activity( int $courseid, string $type, int $section, bool $visible = true ): int { $activity = $this->getDataGenerator()->create_module( $type, ['course' => $courseid], [ 'section' => $section, 'visible' => $visible ] ); return $activity->cmid; } /** * Helper to create a course and generate a section list. * * @param string $format the course format * @param int $sections the number of sections * @param int[] $hiddensections the section numbers to hide * @return stdClass the course object */ private function create_course(string $format, int $sections, array $hiddensections): stdClass { global $DB; $course = $this->getDataGenerator()->create_course(['numsections' => $sections, 'format' => $format]); foreach ($hiddensections as $section) { set_section_visible($course->id, $section, 0); } return $course; } /** * Return an array if the course references. * * This method is used to create alias to sections and other stuff in the dataProviders. * * @param stdClass $course the course object * @return int[] a relation betwee all references and its element id */ private function course_references(stdClass $course): array { global $DB; $references = []; $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id]); foreach ($sectionrecords as $id => $section) { $references["section{$section->section}"] = $section->id; } $references['course'] = $course->id; $references['invalidsection'] = -1; $references['invalidcm'] = -1; return $references; } /** * Translate a references array into current ids. * * @param string[] $references the references list * @param string[] $values the values to translate * @return int[] the list of ids */ private function translate_references(array $references, array $values): array { $result = []; foreach ($values as $value) { $result[] = $references[$value]; } return $result; } /** * Generate a sorted and summarized list of an state updates message. * * It is important to note that the order in the update messages are not important in a real scenario * because each message affects a specific part of the course state. However, for the PHPUnit test * have them sorted and classified simplifies the asserts. * * @param stateupdates $updateobj the state updates object * @return array of all data updates. */ private function summarize_updates(stateupdates $updateobj): array { // Check state returned after executing given action. $updatelist = $updateobj->jsonSerialize(); // Initial summary structure. $result = [ 'create' => [ 'course' => [], 'section' => [], 'cm' => [], 'count' => 0, ], 'put' => [ 'course' => [], 'section' => [], 'cm' => [], 'count' => 0, ], 'remove' => [ 'course' => [], 'section' => [], 'cm' => [], 'count' => 0, ], ]; foreach ($updatelist as $update) { if (!isset($result[$update->action])) { $result[$update->action] = [ 'course' => [], 'section' => [], 'cm' => [], 'count' => 0, ]; } $elementid = $update->fields->id ?? 0; $result[$update->action][$update->name][$elementid] = $update->fields; $result[$update->action]['count']++; } return $result; } /**
> * Enrol, set and create the test user depending on the role name. * Test the behaviour course_state. > * * > * @param stdClass $course the course data * @dataProvider get_state_provider > * @param string $rolename the testing role name * @covers ::course_state > */ * @covers ::section_state > private function set_test_user_by_role(stdClass $course, string $rolename) { * @covers ::cm_state > if ($rolename == 'admin') { * > $this->setAdminUser(); * @param string $format The course will be created with this course format. > } else { * @param string $role The role of the user that will execute the method. > $user = $this->getDataGenerator()->create_user(); * @param string $method the method to call > if ($rolename != 'unenroled') { * @param array $params the ids, targetsection and targetcm to use as params > $this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename); * @param array $expectedresults List of the course module names expected after calling the method. > } * @param bool $expectedexception If this call will raise an exception. > $this->setUser($user); > } */ > } public function test_get_state( > string $format, > /**
string $role, string $method, array $params, array $expectedresults, bool $expectedexception = false ): void { $this->resetAfterTest(); // Create a course with 3 sections, 1 of them hidden. $course = $this->create_course($format, 3, [2]); $references = $this->course_references($course); // Create and enrol user using given role.
< if ($role == 'admin') { < $this->setAdminUser(); < } else { < $user = $this->getDataGenerator()->create_user(); < if ($role != 'unenroled') { < $this->getDataGenerator()->enrol_user($user->id, $course->id, $role); < } < $this->setUser($user); < }
> $this->set_test_user_by_role($course, $role);
// Add some activities to the course. One visible and one hidden in both sections 1 and 2. $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); if ($expectedexception) { $this->expectException(moodle_exception::class); } // Initialise stateupdates. $courseformat = course_get_format($course->id); $updates = new stateupdates($courseformat); // Execute given method. $actions = new stateactions(); $actions->$method( $updates, $course, $this->translate_references($references, $params['ids']), $references[$params['targetsectionid']] ?? null, $references[$params['targetcmid']] ?? null ); // Format results in a way we can compare easily. $results = $this->summarize_updates($updates); // The state actions does not use create or remove actions because they are designed // to refresh parts of the state. $this->assertEquals(0, $results['create']['count']); $this->assertEquals(0, $results['remove']['count']); // Validate we have all the expected entries. $expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']); $this->assertEquals($expectedtotal, $results['put']['count']); // Validate course, section and cm. foreach ($expectedresults as $name => $referencekeys) { foreach ($referencekeys as $referencekey) { $this->assertArrayHasKey($references[$referencekey], $results['put'][$name]); } } } /** * Data provider for data request creation tests. * * @return array the testing scenarios */ public function get_state_provider(): array { return array_merge( $this->course_state_provider('weeks'), $this->course_state_provider('topics'), $this->course_state_provider('social'), $this->section_state_provider('weeks', 'admin'), $this->section_state_provider('weeks', 'editingteacher'), $this->section_state_provider('weeks', 'student'), $this->section_state_provider('topics', 'admin'), $this->section_state_provider('topics', 'editingteacher'), $this->section_state_provider('topics', 'student'), $this->section_state_provider('social', 'admin'), $this->section_state_provider('social', 'editingteacher'), $this->section_state_provider('social', 'student'), $this->cm_state_provider('weeks', 'admin'), $this->cm_state_provider('weeks', 'editingteacher'), $this->cm_state_provider('weeks', 'student'), $this->cm_state_provider('topics', 'admin'), $this->cm_state_provider('topics', 'editingteacher'), $this->cm_state_provider('topics', 'student'), $this->cm_state_provider('social', 'admin'), $this->cm_state_provider('social', 'editingteacher'), $this->cm_state_provider('social', 'student'), ); } /** * Course state data provider. * * @param string $format the course format * @return array the testing scenarios */ public function course_state_provider(string $format): array { $expectedexception = ($format === 'social'); return [ // Tests for course_state. "admin $format course_state" => [ 'format' => $format, 'role' => 'admin', 'method' => 'course_state', 'params' => [ 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section2', 'section3'], 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], ], 'expectedexception' => $expectedexception, ], "editingteacher $format course_state" => [ 'format' => $format, 'role' => 'editingteacher', 'method' => 'course_state', 'params' => [ 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section2', 'section3'], 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], ], 'expectedexception' => $expectedexception, ], "student $format course_state" => [ 'format' => $format, 'role' => 'student', 'method' => 'course_state', 'params' => [ 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section3'], 'cm' => ['cm0'], ], 'expectedexception' => $expectedexception, ], ]; } /** * Section state data provider. * * @param string $format the course format * @param string $role the user role * @return array the testing scenarios */ public function section_state_provider(string $format, string $role): array { // Social format will raise an exception and debug messages because it does not // use sections and it does not provide a renderer. $expectedexception = ($format === 'social'); // All sections and cms that the user can access to. $usersections = ['section0', 'section1', 'section2', 'section3']; $usercms = ['cm0', 'cm1', 'cm2', 'cm3']; if ($role == 'student') { $usersections = ['section0', 'section1', 'section3']; $usercms = ['cm0']; } return [ "$role $format section_state no section" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [], 'expectedexception' => true, ], "$role $format section_state section 0" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section0'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section0'], $usersections), 'cm' => [], ], 'expectedexception' => $expectedexception, ], "$role $format section_state visible section" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section1'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1'], $usersections), 'cm' => array_intersect(['cm0', 'cm1'], $usercms), ], 'expectedexception' => $expectedexception, ], "$role $format section_state hidden section" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section2'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section2'], $usersections), 'cm' => array_intersect(['cm2', 'cm3'], $usercms), ], 'expectedexception' => $expectedexception, ], "$role $format section_state several sections" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section1', 'section3'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1', 'section3'], $usersections), 'cm' => array_intersect(['cm0', 'cm1'], $usercms), ], 'expectedexception' => $expectedexception, ], "$role $format section_state invalid section" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['invalidsection'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [], 'expectedexception' => true, ], "$role $format section_state using target section" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section1'], 'targetsectionid' => 'section3', 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1', 'section3'], $usersections), 'cm' => array_intersect(['cm0', 'cm1'], $usercms), ], 'expectedexception' => $expectedexception, ], "$role $format section_state using target targetcmid" => [ 'format' => $format, 'role' => $role, 'method' => 'section_state', 'params' => [ 'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1' ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section3'], $usersections), 'cm' => array_intersect(['cm1'], $usercms), ], 'expectedexception' => $expectedexception, ], ]; } /** * Course module state data provider. * * @param string $format the course format * @param string $role the user role * @return array the testing scenarios */ public function cm_state_provider(string $format, string $role): array { // All sections and cms that the user can access to. $usersections = ['section0', 'section1', 'section2', 'section3']; $usercms = ['cm0', 'cm1', 'cm2', 'cm3']; if ($role == 'student') { $usersections = ['section0', 'section1', 'section3']; $usercms = ['cm0']; } return [ "$role $format cm_state no cms" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [], 'expectedexception' => true, ], "$role $format cm_state visible cm" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1'], $usersections), 'cm' => array_intersect(['cm0'], $usercms), ], 'expectedexception' => false, ], "$role $format cm_state hidden cm" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['cm1'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1'], $usersections), 'cm' => array_intersect(['cm1'], $usercms), ], 'expectedexception' => false, ], "$role $format cm_state several cm" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1', 'section2'], $usersections), 'cm' => array_intersect(['cm0', 'cm2'], $usercms), ], 'expectedexception' => false, ], "$role $format cm_state using targetsection" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1', 'section2'], $usersections), 'cm' => array_intersect(['cm0'], $usercms), ], 'expectedexception' => ($format === 'social'), ], "$role $format cm_state using targetcm" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3' ], 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section1', 'section2'], $usersections), 'cm' => array_intersect(['cm0', 'cm3'], $usercms), ], 'expectedexception' => false, ], "$role $format cm_state using an invalid cm" => [ 'format' => $format, 'role' => $role, 'method' => 'cm_state', 'params' => [ 'ids' => ['invalidcm'], 'targetsectionid' => null, 'targetcmid' => null ], 'expectedresults' => [], 'expectedexception' => true, ], ]; } /** * Internal method for testing a specific state action. * * @param string $method the method to test * @param string $role the user role * @param string[] $idrefs the sections or cms id references to be used as method params * @param bool $expectedexception whether the call should throw an exception
< * @param int $expectedtotal the expected total number of state puts
> * @param int[] $expectedtotal the expected total number of state indexed by put, remove and create
* @param string|null $coursefield the course field to check * @param int|string|null $coursevalue the section field value * @param string|null $sectionfield the section field to check * @param int|string|null $sectionvalue the section field value * @param string|null $cmfield the cm field to check * @param int|string|null $cmvalue the cm field value
< * @return array the state update summary
> * @param string|null $targetsection optional target section reference > * @param string|null $targetcm optional target cm reference > * @return array an array of elements to do extra validations (course, references, results)
*/ protected function basic_state_text( string $method = 'section_hide', string $role = 'editingteacher', array $idrefs = [], bool $expectedexception = false,
< int $expectedtotal = 0,
> array $expectedtotals = [],
?string $coursefield = null, $coursevalue = 0, ?string $sectionfield = null, $sectionvalue = 0, ?string $cmfield = null,
< $cmvalue = 0
> $cmvalue = 0, > ?string $targetsection = null, > ?string $targetcm = null
): array { $this->resetAfterTest(); // Create a course with 3 sections, 1 of them hidden. $course = $this->create_course('topics', 3, [2]); $references = $this->course_references($course); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id, $role); $this->setUser($user); // Add some activities to the course. One visible and one hidden in both sections 1 and 2. $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
> $references["cm4"] = $this->create_activity($course->id, 'forum', 2, false); > $references["cm5"] = $this->create_activity($course->id, 'wiki', 2, false);
if ($expectedexception) { $this->expectException(moodle_exception::class); } // Initialise stateupdates. $courseformat = course_get_format($course->id); $updates = new stateupdates($courseformat); // Execute the method. $actions = new stateactions(); $actions->$method( $updates, $course, $this->translate_references($references, $idrefs),
> ($targetsection) ? $references[$targetsection] : null, ); > ($targetcm) ? $references[$targetcm] : null,
// Format results in a way we can compare easily. $results = $this->summarize_updates($updates);
< // Most state actions does not use create or remove actions because they are designed < // to refresh parts of the state. < $this->assertEquals(0, $results['create']['count']); < $this->assertEquals(0, $results['remove']['count']); <
// Validate we have all the expected entries.
< $this->assertEquals($expectedtotal, $results['put']['count']);
> $this->assertEquals($expectedtotals['create'] ?? 0, $results['create']['count']); > $this->assertEquals($expectedtotals['remove'] ?? 0, $results['remove']['count']); > $this->assertEquals($expectedtotals['put'] ?? 0, $results['put']['count']);
// Validate course, section and cm. if (!empty($coursefield)) { foreach ($results['put']['course'] as $courseid) { $this->assertEquals($coursevalue, $results['put']['course'][$courseid][$coursefield]); } } if (!empty($sectionfield)) { foreach ($results['put']['section'] as $section) { $this->assertEquals($sectionvalue, $section->$sectionfield); } } if (!empty($cmfield)) { foreach ($results['put']['cm'] as $cm) { $this->assertEquals($cmvalue, $cm->$cmfield); } }
< return $results;
> return [ > 'course' => $course, > 'references' => $references, > 'results' => $results, > ];
} /** * Test for section_hide * * @covers ::section_hide * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_section_hide( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'section_hide', $role, ['section1', 'section2', 'section3'], $expectedexception,
< 7,
> ['put' => 9],
null, null, 'visible', 0, null, null ); } /** * Test for section_hide * * @covers ::section_show * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_section_show( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'section_show', $role, ['section1', 'section2', 'section3'], $expectedexception,
< 7,
> ['put' => 9],
null, null, 'visible', 1, null, null ); } /** * Test for cm_show * * @covers ::cm_show * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_cm_show( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'cm_show', $role, ['cm0', 'cm1', 'cm2', 'cm3'], $expectedexception,
< 4,
> ['put' => 4],
null, null, null, null, 'visible', 1 ); } /** * Test for cm_hide * * @covers ::cm_hide * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_cm_hide( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'cm_hide', $role, ['cm0', 'cm1', 'cm2', 'cm3'], $expectedexception,
< 4,
> ['put' => 4],
null, null, null, null, 'visible', 0 ); } /** * Test for cm_stealth * * @covers ::cm_stealth * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_cm_stealth( string $role = 'editingteacher', bool $expectedexception = false ): void { set_config('allowstealth', 1); $this->basic_state_text( 'cm_stealth', $role, ['cm0', 'cm1', 'cm2', 'cm3'], $expectedexception,
< 4,
> ['put' => 4],
null, null, null, null, 'stealth', 1 ); // Disable stealth. set_config('allowstealth', 0); // When stealth are disabled the validation is a but more complex because they depends // also on the section visibility (legacy stealth). $this->basic_state_text( 'cm_stealth', $role, ['cm0', 'cm1'], $expectedexception,
< 2,
> ['put' => 2],
null, null, null, null, 'stealth', 0 ); $this->basic_state_text( 'cm_stealth', $role, ['cm2', 'cm3'], $expectedexception,
< 2,
> ['put' => 2],
null, null, null, null, 'stealth', 1 ); } /** * Data provider for basic role tests. * * @return array the testing scenarios */ public function basic_role_provider() { return [ 'editingteacher' => [ 'role' => 'editingteacher', 'expectedexception' => false, ], 'teacher' => [ 'role' => 'teacher', 'expectedexception' => true, ], 'student' => [ 'role' => 'student', 'expectedexception' => true, ], 'guest' => [ 'role' => 'guest', 'expectedexception' => true, ], ]; } /**
> * Duplicate course module method. * Test for cm_moveright > * * > * @covers ::cm_duplicate * @covers ::cm_moveright > * @dataProvider cm_duplicate_provider * @dataProvider basic_role_provider > * @param string $targetsection the target section (empty for none) * @param string $role the user role > * @param bool $validcms if uses valid cms * @param bool $expectedexception if it will expect an exception. > * @param string $role the current user role name */ > * @param bool $expectedexception if the test will raise an exception public function test_cm_moveright( > */ string $role = 'editingteacher', > public function test_cm_duplicate( bool $expectedexception = false > string $targetsection = '', ): void { > bool $validcms = true, $this->basic_state_text( > string $role = 'admin', 'cm_moveright', > bool $expectedexception = false $role, > ) { ['cm0', 'cm1', 'cm2', 'cm3'], > $this->resetAfterTest(); $expectedexception, > 4, > // Create a course with 3 sections. null, > $course = $this->create_course('topics', 3, []); null, > null, > $references = $this->course_references($course); null, > 'indent', > // Create and enrol user using given role. 1 > $this->set_test_user_by_role($course, $role); ); > } > // Add some activities to the course. One visible and one hidden in both sections 1 and 2. > $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); /** > $references["cm1"] = $this->create_activity($course->id, 'page', 2, false); * Test for cm_moveleft > * > if ($expectedexception) { * @covers ::cm_moveleft > $this->expectException(moodle_exception::class); * @dataProvider basic_role_provider > } * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > // Initialise stateupdates. */ > $courseformat = course_get_format($course->id); public function test_cm_moveleft( > $updates = new stateupdates($courseformat); string $role = 'editingteacher', > bool $expectedexception = false > // Execute method. ): void { > $targetsectionid = (!empty($targetsection)) ? $references[$targetsection] : null; $this->basic_state_text( > $cmrefs = ($validcms) ? ['cm0', 'cm1'] : ['invalidcm']; 'cm_moveleft', > $actions = new stateactions(); $role, > $actions->cm_duplicate( ['cm0', 'cm1', 'cm2', 'cm3'], > $updates, $expectedexception, > $course, 4, > $this->translate_references($references, $cmrefs), null, > $targetsectionid, null, > ); null, > null, > // Check the new elements in the course structure. 'indent', > $originalsections = [ 0 > 'assign' => $references['section1'], ); > 'page' => $references['section2'], } > ]; } > $modinfo = course_modinfo::instance($course); > $cms = $modinfo->get_cms(); > $i = 0; > foreach ($cms as $cmid => $cminfo) { > if ($cmid == $references['cm0'] || $cmid == $references['cm1']) { > continue; > } > $references["newcm$i"] = $cmid; > if ($targetsectionid) { > $this->assertEquals($targetsectionid, $cminfo->section); > } else { > $this->assertEquals($originalsections[$cminfo->modname], $cminfo->section); > } > $i++; > } > > // Check the resulting updates. > $results = $this->summarize_updates($updates); > > if ($targetsectionid) { > $this->assertArrayHasKey($references[$targetsection], $results['put']['section']); > } else { > $this->assertArrayHasKey($references['section1'], $results['put']['section']); > $this->assertArrayHasKey($references['section2'], $results['put']['section']); > } > $countcms = ($targetsection == 'section3' || $targetsection === '') ? 2 : 3; > $this->assertCount($countcms, $results['put']['cm']); > $this->assertArrayHasKey($references['newcm0'], $results['put']['cm']); > $this->assertArrayHasKey($references['newcm1'], $results['put']['cm']); > } > > /** > * Duplicate course module data provider. > * > * @return array the testing scenarios > */ > public function cm_duplicate_provider(): array { > return [ > 'valid cms without target section' => [ > 'targetsection' => '', > 'validcms' => true, > 'role' => 'admin', > 'expectedexception' => false, > ], > 'valid cms targeting an empty section' => [ > 'targetsection' => 'section3', > 'validcms' => true, > 'role' => 'admin', > 'expectedexception' => false, > ], > 'valid cms targeting a section with activities' => [ > 'targetsection' => 'section2', > 'validcms' => true, > 'role' => 'admin', > 'expectedexception' => false, > ], > 'invalid cms without target section' => [ > 'targetsection' => '', > 'validcms' => false, > 'role' => 'admin', > 'expectedexception' => true, > ], > 'invalid cms with target section' => [ > 'targetsection' => 'section3', > 'validcms' => false, > 'role' => 'admin', > 'expectedexception' => true, > ], > 'student role with target section' => [ > 'targetsection' => 'section3', > 'validcms' => true, > 'role' => 'student', > 'expectedexception' => true, > ], > 'student role without target section' => [ > 'targetsection' => '', > 'validcms' => true, > 'role' => 'student', > 'expectedexception' => true, > ], > 'unrenolled user with target section' => [ > 'targetsection' => 'section3', > 'validcms' => true, > 'role' => 'unenroled', > 'expectedexception' => true, > ], > 'unrenolled user without target section' => [ > 'targetsection' => '', > 'validcms' => true, > 'role' => 'unenroled', > 'expectedexception' => true, > ], > ]; > } > > /** > * Test for cm_delete > * > * @covers ::cm_delete > * @dataProvider basic_role_provider > * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > */ > public function test_cm_delete( > string $role = 'editingteacher', > bool $expectedexception = false > ): void { > $this->resetAfterTest(); > // We want modules to be deleted for good. > set_config('coursebinenable', 0, 'tool_recyclebin'); > > $info = $this->basic_state_text( > 'cm_delete', > $role, > ['cm2', 'cm3'], > $expectedexception, > ['remove' => 2, 'put' => 1], > ); > > $course = $info['course']; > $references = $info['references']; > $results = $info['results']; > $courseformat = course_get_format($course->id); > > $this->assertArrayNotHasKey($references['cm0'], $results['remove']['cm']); > $this->assertArrayNotHasKey($references['cm1'], $results['remove']['cm']); > $this->assertArrayHasKey($references['cm2'], $results['remove']['cm']); > $this->assertArrayHasKey($references['cm3'], $results['remove']['cm']); > $this->assertArrayNotHasKey($references['cm4'], $results['remove']['cm']); > $this->assertArrayNotHasKey($references['cm5'], $results['remove']['cm']); > > // Check the new section cm list. > $newcmlist = $this->translate_references($references, ['cm4', 'cm5']); > $section = $results['put']['section'][$references['section2']]; > $this->assertEquals($newcmlist, $section->cmlist); > > // Check activities are deleted. > $modinfo = $courseformat->get_modinfo(); > $cms = $modinfo->get_cms(); > $this->assertArrayHasKey($references['cm0'], $cms); > $this->assertArrayHasKey($references['cm1'], $cms); > $this->assertArrayNotHasKey($references['cm2'], $cms); > $this->assertArrayNotHasKey($references['cm3'], $cms); > $this->assertArrayHasKey($references['cm4'], $cms); > $this->assertArrayHasKey($references['cm5'], $cms); > } > > /**
< 4,
> ['put' => 4],
< 4,
> ['put' => 4],
> ); > } > > /** > * Test for cm_nogroups > * > * @covers ::cm_nogroups > * @dataProvider basic_role_provider > * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > */ > public function test_cm_nogroups( > string $role = 'editingteacher', > bool $expectedexception = false > ): void { > $this->basic_state_text( > 'cm_nogroups', > $role, > ['cm0', 'cm1', 'cm2', 'cm3'], > $expectedexception, > ['put' => 4], > null, > null, > null, > null, > 'groupmode', > NOGROUPS > ); > } > > /** > * Test for cm_visiblegroups > * > * @covers ::cm_visiblegroups > * @dataProvider basic_role_provider > * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > */ > public function test_cm_visiblegroups( > string $role = 'editingteacher', > bool $expectedexception = false > ): void { > $this->basic_state_text( > 'cm_visiblegroups', > $role, > ['cm0', 'cm1', 'cm2', 'cm3'], > $expectedexception, > ['put' => 4], > null, > null, > null, > null, > 'groupmode', > VISIBLEGROUPS > ); > } > > /** > * Test for cm_separategroups > * > * @covers ::cm_separategroups > * @dataProvider basic_role_provider > * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > */ > public function test_cm_separategroups( > string $role = 'editingteacher', > bool $expectedexception = false > ): void { > $this->basic_state_text( > 'cm_separategroups', > $role, > ['cm0', 'cm1', 'cm2', 'cm3'], > $expectedexception, > ['put' => 4], > null, > null, > null, > null, > 'groupmode', > SEPARATEGROUPS > ); > } > > /** > * Test for section_move_after > * > * @covers ::section_move_after > * @dataProvider section_move_after_provider > * @param string[] $sectiontomove the sections to move > * @param string $targetsection the target section reference > * @param string[] $finalorder the final sections order > * @param string[] $updatedcms the list of cms in the state updates > * @param int $totalputs the total amount of put updates > */ > public function test_section_move_after( > array $sectiontomove, > string $targetsection, > array $finalorder, > array $updatedcms, > int $totalputs > ): void { > $this->resetAfterTest(); > > $course = $this->create_course('topics', 8, []); > > $references = $this->course_references($course); > > // Add some activities to the course. One visible and one hidden in both sections 1 and 2. > $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); > $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); > $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); > $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); > $references["cm4"] = $this->create_activity($course->id, 'forum', 3, false); > $references["cm5"] = $this->create_activity($course->id, 'wiki', 3, false); > > $user = $this->getDataGenerator()->create_user(); > $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); > $this->setUser($user); > > // Initialise stateupdates. > $courseformat = course_get_format($course->id); > $updates = new stateupdates($courseformat); > > // Execute the method. > $actions = new stateactions(); > $actions->section_move_after( > $updates, > $course, > $this->translate_references($references, $sectiontomove), > $references[$targetsection] > ); > > // Format results in a way we can compare easily. > $results = $this->summarize_updates($updates); > > // Validate we have all the expected entries. > $this->assertEquals(0, $results['create']['count']); > $this->assertEquals(0, $results['remove']['count']); > // Moving a section puts: > // - The course state. > // - All sections state. > // - The cm states related to the moved and target sections. > $this->assertEquals($totalputs, $results['put']['count']); > > // Course state should contain the sorted list of sections (section zero + 8 sections). > $finalsectionids = $this->translate_references($references, $finalorder); > $coursestate = reset($results['put']['course']); > $this->assertEquals($finalsectionids, $coursestate->sectionlist); > // All sections should be present in the update. > $this->assertCount(9, $results['put']['section']); > // Only cms from the affected sections should be updated. > $cmids = $this->translate_references($references, $updatedcms); > $cms = $results['put']['cm']; > foreach ($cmids as $cmid) { > $this->assertArrayHasKey($cmid, $cms); > } > } > > /** > * Provider for test_section_move_after. > * > * @return array the testing scenarios > */ > public function section_move_after_provider(): array { > return [ > 'Move sections down' => [ > 'sectiontomove' => ['section2', 'section4'], > 'targetsection' => 'section7', > 'finalorder' => [ > 'section0', > 'section1', > 'section3', > 'section5', > 'section6', > 'section7', > 'section2', > 'section4', > 'section8', > ], > 'updatedcms' => ['cm2', 'cm3'], > 'totalputs' => 12, > ], > 'Move sections up' => [ > 'sectiontomove' => ['section3', 'section5'], > 'targetsection' => 'section1', > 'finalorder' => [ > 'section0', > 'section1', > 'section3', > 'section5', > 'section2', > 'section4', > 'section6', > 'section7', > 'section8', > ], > 'updatedcms' => ['cm0', 'cm1', 'cm4', 'cm5'], > 'totalputs' => 14, > ], > 'Move sections in the middle' => [ > 'sectiontomove' => ['section2', 'section5'], > 'targetsection' => 'section3', > 'finalorder' => [ > 'section0', > 'section1', > 'section3', > 'section2', > 'section5', > 'section4', > 'section6', > 'section7', > 'section8', > ], > 'updatedcms' => ['cm2', 'cm3', 'cm4', 'cm5'], > 'totalputs' => 14, > ], > 'Move sections on top' => [ > 'sectiontomove' => ['section3', 'section5'], > 'targetsection' => 'section0', > 'finalorder' => [ > 'section0', > 'section3', > 'section5', > 'section1', > 'section2', > 'section4', > 'section6', > 'section7', > 'section8', > ], > 'updatedcms' => ['cm4', 'cm5'], > 'totalputs' => 12, > ], > 'Move sections on bottom' => [ > 'sectiontomove' => ['section3', 'section5'], > 'targetsection' => 'section8', > 'finalorder' => [ > 'section0', > 'section1', > 'section2', > 'section4', > 'section6', > 'section7', > 'section8', > 'section3', > 'section5', > ], > 'updatedcms' => ['cm4', 'cm5'], > 'totalputs' => 12, > ], > ]; > } > > /** > * Test for section_move_after capability checks. > * > * @covers ::section_move_after > * @dataProvider basic_role_provider > * @param string $role the user role > * @param bool $expectedexception if it will expect an exception. > */ > public function test_section_move_after_capabilities( > string $role = 'editingteacher', > bool $expectedexception = false > ): void { > $this->resetAfterTest(); > // We want modules to be deleted for good. > set_config('coursebinenable', 0, 'tool_recyclebin'); > > $info = $this->basic_state_text( > 'section_move_after', > $role, > ['section2'], > $expectedexception, > ['put' => 9], > null, > 0, > null, > 0, > null, > 0, > 'section0'