MDL-58557 mod_choice: change action event callback logic
[moodle.git] / mod / choice / 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  * Choice module library functions tests
19  *
20  * @package    mod_choice
21  * @category   test
22  * @copyright  2015 Juan Leyva <juan@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  * @since      Moodle 3.0
25  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
31 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32 require_once($CFG->dirroot . '/mod/choice/lib.php');
34 /**
35  * Choice module library functions tests
36  *
37  * @package    mod_choice
38  * @category   test
39  * @copyright  2015 Juan Leyva <juan@moodle.com>
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  * @since      Moodle 3.0
42  */
43 class mod_choice_lib_testcase extends externallib_advanced_testcase {
45     /**
46      * Test choice_view
47      * @return void
48      */
49     public function test_choice_view() {
50         global $CFG;
52         $this->resetAfterTest();
54         $this->setAdminUser();
55         // Setup test data.
56         $course = $this->getDataGenerator()->create_course();
57         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
58         $context = context_module::instance($choice->cmid);
59         $cm = get_coursemodule_from_instance('choice', $choice->id);
61         // Trigger and capture the event.
62         $sink = $this->redirectEvents();
64         choice_view($choice, $course, $cm, $context);
66         $events = $sink->get_events();
67         $this->assertCount(1, $events);
68         $event = array_shift($events);
70         // Checking that the event contains the expected values.
71         $this->assertInstanceOf('\mod_choice\event\course_module_viewed', $event);
72         $this->assertEquals($context, $event->get_context());
73         $url = new \moodle_url('/mod/choice/view.php', array('id' => $cm->id));
74         $this->assertEquals($url, $event->get_url());
75         $this->assertEventContextNotUsed($event);
76         $this->assertNotEmpty($event->get_name());
77     }
79     /**
80      * Test choice_can_view_results
81      * @return void
82      */
83     public function test_choice_can_view_results() {
84         global $DB, $USER;
86         $this->resetAfterTest();
88         $this->setAdminUser();
89         // Setup test data.
90         $course = $this->getDataGenerator()->create_course();
91         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
92         $context = context_module::instance($choice->cmid);
93         $cm = get_coursemodule_from_instance('choice', $choice->id);
95         // Default values are false, user cannot view results.
96         $canview = choice_can_view_results($choice);
97         $this->assertFalse($canview);
99         // Show results forced.
100         $choice->showresults = CHOICE_SHOWRESULTS_ALWAYS;
101         $DB->update_record('choice', $choice);
102         $canview = choice_can_view_results($choice);
103         $this->assertTrue($canview);
105         // Add a time restriction (choice not open yet).
106         $choice->timeopen = time() + YEARSECS;
107         $DB->update_record('choice', $choice);
108         $canview = choice_can_view_results($choice);
109         $this->assertFalse($canview);
111         // Show results after closing.
112         $choice->timeopen = 0;
113         $choice->showresults = CHOICE_SHOWRESULTS_AFTER_CLOSE;
114         $DB->update_record('choice', $choice);
115         $canview = choice_can_view_results($choice);
116         $this->assertFalse($canview);
118         $choice->timeclose = time() - HOURSECS;
119         $DB->update_record('choice', $choice);
120         $canview = choice_can_view_results($choice);
121         $this->assertTrue($canview);
123         // Show results after answering.
124         $choice->timeclose = 0;
125         $choice->showresults = CHOICE_SHOWRESULTS_AFTER_ANSWER;
126         $DB->update_record('choice', $choice);
127         $canview = choice_can_view_results($choice);
128         $this->assertFalse($canview);
130         // Get the first option.
131         $choicewithoptions = choice_get_choice($choice->id);
132         $optionids = array_keys($choicewithoptions->option);
134         choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
136         $canview = choice_can_view_results($choice);
137         $this->assertTrue($canview);
139     }
141     /**
142      * @expectedException moodle_exception
143      */
144     public function test_choice_user_submit_response_validation() {
145         global $USER;
147         $this->resetAfterTest();
149         $this->setAdminUser();
150         // Setup test data.
151         $course = $this->getDataGenerator()->create_course();
152         $choice1 = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
153         $choice2 = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
154         $cm = get_coursemodule_from_instance('choice', $choice1->id);
156         $choicewithoptions1 = choice_get_choice($choice1->id);
157         $choicewithoptions2 = choice_get_choice($choice2->id);
158         $optionids1 = array_keys($choicewithoptions1->option);
159         $optionids2 = array_keys($choicewithoptions2->option);
161         // Make sure we cannot submit options from a different choice instance.
162         choice_user_submit_response($optionids2[0], $choice1, $USER->id, $course, $cm);
163     }
165     /**
166      * Test choice_get_my_response
167      * @return void
168      */
169     public function test_choice_get_my_response() {
170         global $USER;
172         $this->resetAfterTest();
174         $this->setAdminUser();
175         // Setup test data.
176         $course = $this->getDataGenerator()->create_course();
177         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
178         $context = context_module::instance($choice->cmid);
179         $cm = get_coursemodule_from_instance('choice', $choice->id);
181         $choicewithoptions = choice_get_choice($choice->id);
182         $optionids = array_keys($choicewithoptions->option);
184         choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
185         $responses = choice_get_my_response($choice, $course, $cm, $context);
186         $this->assertCount(1, $responses);
187         $response = array_shift($responses);
188         $this->assertEquals($optionids[0], $response->optionid);
190         // Multiple responses.
191         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id, 'allowmultiple' => 1));
192         $context = context_module::instance($choice->cmid);
193         $cm = get_coursemodule_from_instance('choice', $choice->id);
195         $choicewithoptions = choice_get_choice($choice->id);
196         $optionids = array_keys($choicewithoptions->option);
198         choice_user_submit_response($optionids, $choice, $USER->id, $course, $cm);
199         $responses = choice_get_my_response($choice, $course, $cm, $context);
200         $this->assertCount(count($optionids), $responses);
201         foreach ($responses as $resp) {
202             $this->assertContains($resp->optionid, $optionids);
203         }
204     }
206     /**
207      * Test choice_get_availability_status
208      * @return void
209      */
210     public function test_choice_get_availability_status() {
211         global $USER;
213         $this->resetAfterTest();
215         $this->setAdminUser();
216         // Setup test data.
217         $course = $this->getDataGenerator()->create_course();
218         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
220         // No time restrictions and updates allowed.
221         list($status, $warnings) = choice_get_availability_status($choice, false);
222         $this->assertEquals(true, $status);
223         $this->assertCount(0, $warnings);
225         // No updates allowed, but haven't answered yet.
226         $choice->allowupdate = false;
227         list($status, $warnings) = choice_get_availability_status($choice, false);
228         $this->assertEquals(true, $status);
229         $this->assertCount(0, $warnings);
231         // No updates allowed and have answered.
232         $cm = get_coursemodule_from_instance('choice', $choice->id);
233         $choicewithoptions = choice_get_choice($choice->id);
234         $optionids = array_keys($choicewithoptions->option);
235         choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm);
236         list($status, $warnings) = choice_get_availability_status($choice, false);
237         $this->assertEquals(false, $status);
238         $this->assertCount(1, $warnings);
239         $this->assertEquals('choicesaved', array_keys($warnings)[0]);
241         $choice->allowupdate = true;
243         // With time restrictions, still open.
244         $choice->timeopen = time() - DAYSECS;
245         $choice->timeclose = time() + DAYSECS;
246         list($status, $warnings) = choice_get_availability_status($choice, false);
247         $this->assertEquals(true, $status);
248         $this->assertCount(0, $warnings);
250         // Choice not open yet.
251         $choice->timeopen = time() + DAYSECS;
252         $choice->timeclose = $choice->timeopen + DAYSECS;
253         list($status, $warnings) = choice_get_availability_status($choice, false);
254         $this->assertEquals(false, $status);
255         $this->assertCount(1, $warnings);
256         $this->assertEquals('notopenyet', array_keys($warnings)[0]);
258         // Choice closed.
259         $choice->timeopen = time() - DAYSECS;
260         $choice->timeclose = time() - 1;
261         list($status, $warnings) = choice_get_availability_status($choice, false);
262         $this->assertEquals(false, $status);
263         $this->assertCount(1, $warnings);
264         $this->assertEquals('expired', array_keys($warnings)[0]);
265     }
267     public function test_choice_core_calendar_provide_event_action_open() {
268         $this->resetAfterTest();
270         $this->setAdminUser();
272         // Create a course.
273         $course = $this->getDataGenerator()->create_course();
275         // Create a choice.
276         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
277             'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
279         // Create a calendar event.
280         $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
282         // Create an action factory.
283         $factory = new \core_calendar\action_factory();
285         // Decorate action event.
286         $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
288         // Confirm the event was decorated.
289         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
290         $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
291         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
292         $this->assertEquals(1, $actionevent->get_item_count());
293         $this->assertTrue($actionevent->is_actionable());
294     }
296     /**
297      * An event should not have an action if the user has already submitted a response
298      * to the choice activity.
299      */
300     public function test_choice_core_calendar_provide_event_action_already_submitted() {
301         global $DB;
303         $this->resetAfterTest();
305         $this->setAdminUser();
307         // Create a course.
308         $course = $this->getDataGenerator()->create_course();
309         // Create user.
310         $student = $this->getDataGenerator()->create_user();
311         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
312         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
314         // Create a choice.
315         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
316             'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
317         $context = context_module::instance($choice->cmid);
318         $cm = get_coursemodule_from_instance('choice', $choice->id);
320         $choicewithoptions = choice_get_choice($choice->id);
321         $optionids = array_keys($choicewithoptions->option);
323         choice_user_submit_response($optionids[0], $choice, $student->id, $course, $cm);
325         // Create a calendar event.
326         $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
328         // Create an action factory.
329         $factory = new \core_calendar\action_factory();
331         $this->setUser($student);
333         // Decorate action event.
334         $action = mod_choice_core_calendar_provide_event_action($event, $factory);
336         // Confirm no action was returned if the user has already submitted the
337         // choice activity.
338         $this->assertNull($action);
339     }
341     public function test_choice_core_calendar_provide_event_action_closed() {
342         $this->resetAfterTest();
344         $this->setAdminUser();
346         // Create a course.
347         $course = $this->getDataGenerator()->create_course();
349         // Create a choice.
350         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
351             'timeclose' => time() - DAYSECS));
353         // Create a calendar event.
354         $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
356         // Create an action factory.
357         $factory = new \core_calendar\action_factory();
359         // Decorate action event.
360         $action = mod_choice_core_calendar_provide_event_action($event, $factory);
362         // Confirm not action was provided for a closed activity.
363         $this->assertNull($action);
364     }
366     public function test_choice_core_calendar_provide_event_action_open_in_future() {
367         $this->resetAfterTest();
369         $this->setAdminUser();
371         // Create a course.
372         $course = $this->getDataGenerator()->create_course();
374         // Create a choice.
375         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
376             'timeopen' => time() + DAYSECS));
378         // Create a calendar event.
379         $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
381         // Create an action factory.
382         $factory = new \core_calendar\action_factory();
384         // Decorate action event.
385         $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
387         // Confirm the event was decorated.
388         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
389         $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
390         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
391         $this->assertEquals(1, $actionevent->get_item_count());
392         $this->assertFalse($actionevent->is_actionable());
393     }
395     public function test_choice_core_calendar_provide_event_action_no_time_specified() {
396         $this->resetAfterTest();
398         $this->setAdminUser();
400         // Create a course.
401         $course = $this->getDataGenerator()->create_course();
403         // Create a choice.
404         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id));
406         // Create a calendar event.
407         $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
409         // Create an action factory.
410         $factory = new \core_calendar\action_factory();
412         // Decorate action event.
413         $actionevent = mod_choice_core_calendar_provide_event_action($event, $factory);
415         // Confirm the event was decorated.
416         $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
417         $this->assertEquals(get_string('viewchoices', 'choice'), $actionevent->get_name());
418         $this->assertInstanceOf('moodle_url', $actionevent->get_url());
419         $this->assertEquals(1, $actionevent->get_item_count());
420         $this->assertTrue($actionevent->is_actionable());
421     }
423     /**
424      * Creates an action event.
425      *
426      * @param int $courseid
427      * @param int $instanceid The choice id.
428      * @param string $eventtype The event type. eg. CHOICE_EVENT_TYPE_OPEN.
429      * @return bool|calendar_event
430      */
431     private function create_action_event($courseid, $instanceid, $eventtype) {
432         $event = new stdClass();
433         $event->name = 'Calendar event';
434         $event->modulename = 'choice';
435         $event->courseid = $courseid;
436         $event->instance = $instanceid;
437         $event->type = CALENDAR_EVENT_TYPE_ACTION;
438         $event->eventtype = $eventtype;
439         $event->timestart = time();
441         return calendar_event::create($event);
442     }
444     /**
445      * Test the callback responsible for returning the completion rule descriptions.
446      * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
447      * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
448      */
449     public function test_mod_choice_completion_get_active_rule_descriptions() {
450         $this->resetAfterTest();
451         $this->setAdminUser();
453         // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
454         $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
455         $choice1 = $this->getDataGenerator()->create_module('choice', [
456             'course' => $course->id,
457             'completion' => 2,
458             'completionsubmit' => 1
459         ]);
460         $choice2 = $this->getDataGenerator()->create_module('choice', [
461             'course' => $course->id,
462             'completion' => 2,
463             'completionsubmit' => 0
464         ]);
465         $cm1 = cm_info::create(get_coursemodule_from_instance('choice', $choice1->id));
466         $cm2 = cm_info::create(get_coursemodule_from_instance('choice', $choice2->id));
468         // Data for the stdClass input type.
469         // This type of input would occur when checking the default completion rules for an activity type, where we don't have
470         // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
471         $moddefaults = new stdClass();
472         $moddefaults->customdata = ['customcompletionrules' => ['completionsubmit' => 1]];
473         $moddefaults->completion = 2;
475         $activeruledescriptions = [get_string('completionsubmit', 'choice')];
476         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
477         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($cm2), []);
478         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
479         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions(new stdClass()), []);
480     }