Merge branch 'MDL-59644-master' of git://github.com/lameze/moodle
[moodle.git] / mod / lesson / tests / lib_test.php
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/>.
17 /**
18  * Unit tests for mod/lesson/lib.php.
19  *
20  * @package    mod_lesson
21  * @category   test
22  * @copyright  2017 Jun Pataleta
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->dirroot . '/mod/lesson/lib.php');
31 /**
32  * Unit tests for mod/lesson/lib.php.
33  *
34  * @copyright  2017 Jun Pataleta
35  * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
36  */
37 class mod_lesson_lib_testcase extends advanced_testcase {
38     /**
39      * Test for lesson_get_group_override_priorities().
40      */
41     public function test_lesson_get_group_override_priorities() {
42         global $DB;
43         $this->resetAfterTest();
44         $this->setAdminUser();
46         $dg = $this->getDataGenerator();
47         $course = $dg->create_course();
48         $lessonmodule = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
50         $this->assertNull(lesson_get_group_override_priorities($lessonmodule->id));
52         $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
53         $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
55         $now = 100;
56         $override1 = (object)[
57             'lessonid' => $lessonmodule->id,
58             'groupid' => $group1->id,
59             'available' => $now,
60             'deadline' => $now + 20
61         ];
62         $DB->insert_record('lesson_overrides', $override1);
64         $override2 = (object)[
65             'lessonid' => $lessonmodule->id,
66             'groupid' => $group2->id,
67             'available' => $now - 10,
68             'deadline' => $now + 10
69         ];
70         $DB->insert_record('lesson_overrides', $override2);
72         $priorities = lesson_get_group_override_priorities($lessonmodule->id);
73         $this->assertNotEmpty($priorities);
75         $openpriorities = $priorities['open'];
76         // Override 2's time open has higher priority since it is sooner than override 1's.
77         $this->assertEquals(2, $openpriorities[$override1->available]);
78         $this->assertEquals(1, $openpriorities[$override2->available]);
80         $closepriorities = $priorities['close'];
81         // Override 1's time close has higher priority since it is later than override 2's.
82         $this->assertEquals(1, $closepriorities[$override1->deadline]);
83         $this->assertEquals(2, $closepriorities[$override2->deadline]);
84     }
86     /**
87      * Test check_updates_since callback.
88      */
89     public function test_check_updates_since() {
90         global $DB;
92         $this->resetAfterTest();
93         $this->setAdminUser();
94         $course = $this->getDataGenerator()->create_course();
96         // Create user.
97         $student = self::getDataGenerator()->create_user();
99         // User enrolment.
100         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
101         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
103         $this->setCurrentTimeStart();
104         $record = array(
105             'course' => $course->id,
106             'custom' => 0,
107             'feedback' => 1,
108         );
109         $lessonmodule = $this->getDataGenerator()->create_module('lesson', $record);
110         // Convert to a lesson object.
111         $lesson = new lesson($lessonmodule);
112         $cm = $lesson->cm;
113         $cm = cm_info::create($cm);
115         // Check that upon creation, the updates are only about the new configuration created.
116         $onehourago = time() - HOURSECS;
117         $updates = lesson_check_updates_since($cm, $onehourago);
118         foreach ($updates as $el => $val) {
119             if ($el == 'configuration') {
120                 $this->assertTrue($val->updated);
121                 $this->assertTimeCurrent($val->timeupdated);
122             } else {
123                 $this->assertFalse($val->updated);
124             }
125         }
127         // Set up a generator to create content.
128         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
129         $tfrecord = $generator->create_question_truefalse($lesson);
131         // Check now for pages and answers.
132         $updates = lesson_check_updates_since($cm, $onehourago);
133         $this->assertTrue($updates->pages->updated);
134         $this->assertCount(1, $updates->pages->itemids);
136         $this->assertTrue($updates->answers->updated);
137         $this->assertCount(2, $updates->answers->itemids);
139         // Now, do something in the lesson.
140         $this->setUser($student);
141         mod_lesson_external::launch_attempt($lesson->id);
142         $data = array(
143             array(
144                 'name' => 'answerid',
145                 'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
146             ),
147             array(
148                 'name' => '_qf__lesson_display_answer_form_truefalse',
149                 'value' => 1,
150             )
151         );
152         mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
153         mod_lesson_external::finish_attempt($lesson->id);
155         $updates = lesson_check_updates_since($cm, $onehourago);
157         // Check question attempts, timers and new grades.
158         $this->assertTrue($updates->questionattempts->updated);
159         $this->assertCount(1, $updates->questionattempts->itemids);
161         $this->assertTrue($updates->grades->updated);
162         $this->assertCount(1, $updates->grades->itemids);
164         $this->assertTrue($updates->timers->updated);
165         $this->assertCount(1, $updates->timers->itemids);
166     }
168     public function test_lesson_core_calendar_provide_event_action_open() {
169         $this->resetAfterTest();
170         $this->setAdminUser();
171         // Create a course.
172         $course = $this->getDataGenerator()->create_course();
173         // Create a lesson activity.
174         $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
175             'available' => time() - DAYSECS, 'deadline' => time() + DAYSECS));
176         // Create a calendar event.
177         $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
178         // Create an action factory.
179         $factory = new \core_calendar\action_factory();
180         // Decorate action event.
181         $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
182         // Confirm the event was decorated.
183         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
184         $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
185         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
186         $this->assertEquals(1, $actionevent->get_item_count());
187         $this->assertTrue($actionevent->is_actionable());
188     }
190     public function test_lesson_core_calendar_provide_event_action_closed() {
191         $this->resetAfterTest();
192         $this->setAdminUser();
194         // Create a course.
195         $course = $this->getDataGenerator()->create_course();
197         // Create a lesson activity.
198         $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
199             'deadline' => time() - DAYSECS));
201         // Create a calendar event.
202         $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
204         // Create an action factory.
205         $factory = new \core_calendar\action_factory();
207         // Decorate action event.
208         $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
210         // Confirm the event was decorated.
211         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
212         $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
213         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
214         $this->assertEquals(1, $actionevent->get_item_count());
215         $this->assertFalse($actionevent->is_actionable());
216     }
218     public function test_lesson_core_calendar_provide_event_action_open_in_future() {
219         $this->resetAfterTest();
220         $this->setAdminUser();
222         // Create a course.
223         $course = $this->getDataGenerator()->create_course();
225         // Create a lesson activity.
226         $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
227             'available' => time() + DAYSECS));
229         // Create a calendar event.
230         $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
232         // Create an action factory.
233         $factory = new \core_calendar\action_factory();
235         // Decorate action event.
236         $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
238         // Confirm the event was decorated.
239         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
240         $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
241         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
242         $this->assertEquals(1, $actionevent->get_item_count());
243         $this->assertFalse($actionevent->is_actionable());
244     }
246     public function test_lesson_core_calendar_provide_event_action_no_time_specified() {
247         $this->resetAfterTest();
248         $this->setAdminUser();
250         // Create a course.
251         $course = $this->getDataGenerator()->create_course();
253         // Create a lesson activity.
254         $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
256         // Create a calendar event.
257         $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
259         // Create an action factory.
260         $factory = new \core_calendar\action_factory();
262         // Decorate action event.
263         $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
265         // Confirm the event was decorated.
266         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
267         $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
268         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
269         $this->assertEquals(1, $actionevent->get_item_count());
270         $this->assertTrue($actionevent->is_actionable());
271     }
273     public function test_lesson_core_calendar_provide_event_action_after_attempt() {
274         global $DB;
276         $this->resetAfterTest();
277         $this->setAdminUser();
279         // Create a course.
280         $course = $this->getDataGenerator()->create_course();
282         // Create user.
283         $student = self::getDataGenerator()->create_user();
285         // Create a lesson activity.
286         $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
288         // Create a calendar event.
289         $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
291         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
292         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
294         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
295         $tfrecord = $generator->create_question_truefalse($lesson);
297         // Now, do something in the lesson.
298         $this->setUser($student);
299         mod_lesson_external::launch_attempt($lesson->id);
300         $data = array(
301             array(
302                 'name' => 'answerid',
303                 'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
304             ),
305             array(
306                 'name' => '_qf__lesson_display_answer_form_truefalse',
307                 'value' => 1,
308             )
309         );
310         mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
311         mod_lesson_external::finish_attempt($lesson->id);
313         // Create an action factory.
314         $factory = new \core_calendar\action_factory();
316         // Decorate action event.
317         $action = mod_lesson_core_calendar_provide_event_action($event, $factory);
319         // Confirm there was no action for the user.
320         $this->assertNull($action);
321     }
323     /**
324      * Creates an action event.
325      *
326      * @param int $courseid
327      * @param int $instanceid The lesson id.
328      * @param string $eventtype The event type. eg. LESSON_EVENT_TYPE_OPEN.
329      * @return bool|calendar_event
330      */
331     private function create_action_event($courseid, $instanceid, $eventtype) {
332         $event = new stdClass();
333         $event->name = 'Calendar event';
334         $event->modulename  = 'lesson';
335         $event->courseid = $courseid;
336         $event->instance = $instanceid;
337         $event->type = CALENDAR_EVENT_TYPE_ACTION;
338         $event->eventtype = $eventtype;
339         $event->timestart = time();
340         return calendar_event::create($event);
341     }
343     /**
344      * Test the callback responsible for returning the completion rule descriptions.
345      * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
346      * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
347      */
348     public function test_mod_lesson_completion_get_active_rule_descriptions() {
349         $this->resetAfterTest();
350         $this->setAdminUser();
352         // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
353         $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
354         $lesson1 = $this->getDataGenerator()->create_module('lesson', [
355             'course' => $course->id,
356             'completion' => 2,
357             'completionendreached' => 1,
358             'completiontimespent' => 3600
359         ]);
360         $lesson2 = $this->getDataGenerator()->create_module('lesson', [
361             'course' => $course->id,
362             'completion' => 2,
363             'completionendreached' => 0,
364             'completiontimespent' => 0
365         ]);
366         $cm1 = cm_info::create(get_coursemodule_from_instance('lesson', $lesson1->id));
367         $cm2 = cm_info::create(get_coursemodule_from_instance('lesson', $lesson2->id));
369         // Data for the stdClass input type.
370         // This type of input would occur when checking the default completion rules for an activity type, where we don't have
371         // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
372         $moddefaults = new stdClass();
373         $moddefaults->customdata = ['customcompletionrules' => [
374             'completionendreached' => 1,
375             'completiontimespent' => 3600
376         ]];
377         $moddefaults->completion = 2;
379         $activeruledescriptions = [
380             get_string('completionendreached_desc', 'lesson'),
381             get_string('completiontimespentdesc', 'lesson', format_time(3600)),
382         ];
383         $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
384         $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($cm2), []);
385         $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
386         $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions(new stdClass()), []);
387     }