<?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 { /** * 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. * * @param stdClass $course the course data * @param string $rolename the testing role name */ private function set_test_user_by_role(stdClass $course, string $rolename) { if ($rolename == 'admin') { $this->setAdminUser(); } else { $user = $this->getDataGenerator()->create_user(); if ($rolename != 'unenroled') { $this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename); } $this->setUser($user); } } /** * Test the behaviour course_state. * * @dataProvider get_state_provider * @covers ::course_state * @covers ::section_state * @covers ::cm_state * * @param string $format The course will be created with this course format. * @param string $role The role of the user that will execute the method. * @param string $method the method to call * @param array $params the ids, targetsection and targetcm to use as params * @param array $expectedresults List of the course module names expected after calling the method. * @param bool $expectedexception If this call will raise an exception. */ 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. $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 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 * @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, array $expectedtotals = [], ?string $coursefield = null, $coursevalue = 0, ?string $sectionfield = null, $sectionvalue = 0, ?string $cmfield = null, $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); // Validate we have all the expected entries. $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 [ '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, ['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, ['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, ['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, ['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, ['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, ['put' => 2], null, null, null, null, 'stealth', 0 ); $this->basic_state_text( 'cm_stealth', $role, ['cm2', 'cm3'], $expectedexception, ['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. * * @covers ::cm_duplicate * @dataProvider cm_duplicate_provider * @param string $targetsection the target section (empty for none) * @param bool $validcms if uses valid cms * @param string $role the current user role name * @param bool $expectedexception if the test will raise an exception */ public function test_cm_duplicate( string $targetsection = '', bool $validcms = true, string $role = 'admin', bool $expectedexception = false ) { $this->resetAfterTest(); // Create a course with 3 sections. $course = $this->create_course('topics', 3, []); $references = $this->course_references($course); // Create and enrol user using given role. $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); if ($expectedexception) { $this->expectException(moodle_exception::class); } // Initialise stateupdates. $courseformat = course_get_format($course->id); $updates = new stateupdates($courseformat); // Execute method. $targetsectionid = (!empty($targetsection)) ? $references[$targetsection] : null; $cmrefs = ($validcms) ? ['cm0', 'cm1'] : ['invalidcm']; $actions = new stateactions(); $actions->cm_duplicate( $updates, $course, $this->translate_references($references, $cmrefs), $targetsectionid, ); // Check the new elements in the course structure. $originalsections = [ '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); } /** * Test for cm_moveright * * @covers ::cm_moveright * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_cm_moveright( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'cm_moveright', $role, ['cm0', 'cm1', 'cm2', 'cm3'], $expectedexception, ['put' => 4], null, null, null, null, 'indent', 1 ); } /** * Test for cm_moveleft * * @covers ::cm_moveleft * @dataProvider basic_role_provider * @param string $role the user role * @param bool $expectedexception if it will expect an exception. */ public function test_cm_moveleft( string $role = 'editingteacher', bool $expectedexception = false ): void { $this->basic_state_text( 'cm_moveleft', $role, ['cm0', 'cm1', 'cm2', 'cm3'], $expectedexception, ['put' => 4], null, null, null, null, 'indent', 0 ); } /**> * Test for cm_nogroups * Test for section_move_after > * * > * @covers ::cm_nogroups * @covers ::section_move_after > * @dataProvider basic_role_provider * @dataProvider section_move_after_provider > * @param string $role the user role * @param string[] $sectiontomove the sections to move > * @param bool $expectedexception if it will expect an exception. * @param string $targetsection the target section reference > */ * @param string[] $finalorder the final sections order > public function test_cm_nogroups( * @param string[] $updatedcms the list of cms in the state updates > string $role = 'editingteacher', * @param int $totalputs the total amount of put updates > bool $expectedexception = false */ > ): void { public function test_section_move_after( > $this->basic_state_text( array $sectiontomove, > 'cm_nogroups', string $targetsection, > $role, array $finalorder, > ['cm0', 'cm1', 'cm2', 'cm3'], array $updatedcms, > $expectedexception, int $totalputs > ['put' => 4], ): void { > null, $this->resetAfterTest(); > null, > null, $course = $this->create_course('topics', 8, []); > null, > 'groupmode', $references = $this->course_references($course); > NOGROUPS > ); // 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); > * Test for cm_visiblegroups $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); > * $references["cm4"] = $this->create_activity($course->id, 'forum', 3, false); > * @covers ::cm_visiblegroups $references["cm5"] = $this->create_activity($course->id, 'wiki', 3, false); > * @dataProvider basic_role_provider > * @param string $role the user role $user = $this->getDataGenerator()->create_user(); > * @param bool $expectedexception if it will expect an exception. $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); > */ $this->setUser($user); > public function test_cm_visiblegroups( > string $role = 'editingteacher', // Initialise stateupdates. > bool $expectedexception = false $courseformat = course_get_format($course->id); > ): void { $updates = new stateupdates($courseformat); > $this->basic_state_text( > 'cm_visiblegroups', // Execute the method. > $role, $actions = new stateactions(); > ['cm0', 'cm1', 'cm2', 'cm3'], $actions->section_move_after( > $expectedexception, $updates, > ['put' => 4], $course, > null, $this->translate_references($references, $sectiontomove), > null, $references[$targetsection] > null, ); > null, > 'groupmode', // Format results in a way we can compare easily. > VISIBLEGROUPS $results = $this->summarize_updates($updates); > ); > } // Validate we have all the expected entries. > $this->assertEquals(0, $results['create']['count']); > /** $this->assertEquals(0, $results['remove']['count']); > * Test for cm_separategroups // Moving a section puts: > * // - The course state. > * @covers ::cm_separategroups // - All sections state. > * @dataProvider basic_role_provider // - The cm states related to the moved and target sections. > * @param string $role the user role $this->assertEquals($totalputs, $results['put']['count']); > * @param bool $expectedexception if it will expect an exception. > */ // Course state should contain the sorted list of sections (section zero + 8 sections). > public function test_cm_separategroups( $finalsectionids = $this->translate_references($references, $finalorder); > string $role = 'editingteacher', $coursestate = reset($results['put']['course']); > bool $expectedexception = false $this->assertEquals($finalsectionids, $coursestate->sectionlist); > ): void { // All sections should be present in the update. > $this->basic_state_text( $this->assertCount(9, $results['put']['section']); > 'cm_separategroups', // Only cms from the affected sections should be updated. > $role, $cmids = $this->translate_references($references, $updatedcms); > ['cm0', 'cm1', 'cm2', 'cm3'], $cms = $results['put']['cm']; > $expectedexception, foreach ($cmids as $cmid) { > ['put' => 4], $this->assertArrayHasKey($cmid, $cms); > null, } > null, } > null, > null, /** > 'groupmode', * Provider for test_section_move_after. > SEPARATEGROUPS * > ); * @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' ); } }