Merge branch 'MDL-54907' of https://github.com/timhunt/moodle
[moodle.git] / mod / quiz / tests / attempt_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  * Tests for the quiz_attempt class.
19  *
20  * @package   mod_quiz
21  * @category  test
22  * @copyright 2014 Tim Hunt
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
31 /**
32  * Tests for the quiz_attempt class.
33  *
34  * @copyright 2014 Tim Hunt
35  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class mod_quiz_attempt_testcase extends advanced_testcase {
39     /**
40      * Create quiz and attempt data with layout.
41      *
42      * @param string $layout layout to set. Like quiz attempt.layout. E.g. '1,2,0,3,4,0,'.
43      * @return quiz_attempt the new quiz_attempt object
44      */
45     protected function create_quiz_and_attempt_with_layout($layout) {
46         $this->resetAfterTest(true);
48         // Make a user to do the quiz.
49         $user = $this->getDataGenerator()->create_user();
50         $course = $this->getDataGenerator()->create_course();
51         // Make a quiz.
52         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
53         $quiz = $quizgenerator->create_instance(['course' => $course->id,
54             'grade' => 100.0, 'sumgrades' => 2, 'layout' => $layout]);
56         $quizobj = quiz::create($quiz->id, $user->id);
59         $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
60         $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
62         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
63         $cat = $questiongenerator->create_question_category();
65         $page = 1;
66         foreach (explode(',', $layout) as $slot) {
67             if ($slot == 0) {
68                 $page += 1;
69                 continue;
70             }
72             $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
73             quiz_add_quiz_question($question->id, $quiz, $page);
74         }
76         $timenow = time();
77         $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user->id);
78         quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
79         quiz_attempt_save_started($quizobj, $quba, $attempt);
81         return quiz_attempt::create($attempt->id);
82     }
84     public function test_attempt_url() {
85         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0,3,4,0,5,6,0');
87         $attemptid = $attempt->get_attempt()->id;
88         $cmid = $attempt->get_cmid();
89         $url = '/mod/quiz/attempt.php';
90         $params = ['attempt' => $attemptid, 'cmid' => $cmid, 'page' => 2];
92         $this->assertEquals(new moodle_url($url, $params), $attempt->attempt_url(null, 2));
94         $params['page'] = 1;
95         $this->assertEquals(new moodle_url($url, $params), $attempt->attempt_url(3));
97         $questionattempt = $attempt->get_question_attempt(4);
98         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
99         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->attempt_url(4));
101         $this->assertEquals(new moodle_url('#'), $attempt->attempt_url(null, 2, 2));
102         $this->assertEquals(new moodle_url('#'), $attempt->attempt_url(3, -1, 1));
104         $questionattempt = $attempt->get_question_attempt(4);
105         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
106         $this->assertEquals(new moodle_url(null, null, $expecteanchor, null), $attempt->attempt_url(4, -1, 1));
108         // Summary page.
109         $url = '/mod/quiz/summary.php';
110         unset($params['page']);
111         $this->assertEquals(new moodle_url($url, $params), $attempt->summary_url());
113         // Review page.
114         $url = '/mod/quiz/review.php';
115         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url());
117         $params['page'] = 1;
118         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false));
119         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(4, -1, false));
121         unset($params['page']);
122         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, true));
123         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, true));
125         $params['page'] = 2;
126         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false));
127         unset($params['page']);
129         $params['showall'] = 0;
130         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 0, false));
131         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, false));
133         $params['page'] = 1;
134         unset($params['showall']);
135         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false));
137         $params['page'] = 2;
138         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
139         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, -1, null, 0));
141         $questionattempt = $attempt->get_question_attempt(3);
142         $expecteanchor = '#' . $questionattempt->get_outer_question_div_unique_id();
143         $this->assertEquals(new moodle_url($expecteanchor), $attempt->review_url(3, -1, null, 0));
145         $questionattempt = $attempt->get_question_attempt(4);
146         $expecteanchor = '#' . $questionattempt->get_outer_question_div_unique_id();
147         $this->assertEquals(new moodle_url($expecteanchor), $attempt->review_url(4, -1, null, 0));
148         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 2, true, 0));
149         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, true, 0));
150         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false, 0));
151         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 0, false, 0));
152         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, false, 0));
154         $params['page'] = 1;
155         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false, 0));
157         // Setup another attempt.
158         $attempt = $this->create_quiz_and_attempt_with_layout(
159             '1,2,3,4,5,6,7,8,9,10,0,11,12,13,14,15,16,17,18,19,20,0,' .
160             '21,22,23,24,25,26,27,28,29,30,0,31,32,33,34,35,36,37,38,39,40,0,' .
161             '41,42,43,44,45,46,47,48,49,50,0,51,52,53,54,55,56,57,58,59,60,0');
163         $attemptid = $attempt->get_attempt()->id;
164         $cmid = $attempt->get_cmid();
165         $params = ['attempt' => $attemptid, 'cmid' => $cmid];
166         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url());
168         $params['page'] = 2;
169         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
171         $params['page'] = 1;
172         unset($params['showall']);
173         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false));
175         $questionattempt = $attempt->get_question_attempt(12);
176         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
177         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(12, -1, false));
179         $params['showall'] = 1;
180         unset($params['page']);
181         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, true));
183         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, true));
184         $params['page'] = 2;
185         unset($params['showall']);
186         $this->assertEquals(new moodle_url($url, $params),  $attempt->review_url(null, 2, false));
187         unset($params['page']);
188         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 0, false));
189         $params['page'] = 1;
190         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false));
191         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(12, -1, false));
192         $params['page'] = 2;
193         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
194         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, -1, null, 0));
196         $questionattempt = $attempt->get_question_attempt(3);
197         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
198         $this->assertEquals(new moodle_url(null, null, $expecteanchor), $attempt->review_url(3, -1, null, 0));
200         $questionattempt = $attempt->get_question_attempt(4);
201         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
202         $this->assertEquals(new moodle_url(null, null, $expecteanchor), $attempt->review_url(4, -1, null, 0));
204         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 2, true, 0));
205         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, true, 0));
207         $params['page'] = 2;
208         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false, 0));
209         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 0, false, 0));
210         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, false, 0));
212         $params['page'] = 1;
213         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false, 0));
214     }
216     /**
217      * Tests attempt page titles when all questions are on a single page.
218      */
219     public function test_attempt_titles_single() {
220         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0');
222         // Attempt page.
223         $this->assertEquals('Quiz 1', $attempt->attempt_page_title(0));
225         // Summary page.
226         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
228         // Review page.
229         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0));
230     }
232     /**
233      * Tests attempt page titles when questions are on multiple pages, but are reviewed on a single page.
234      */
235     public function test_attempt_titles_multiple_single() {
236         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0,3,4,0,5,6,0');
238         // Attempt page.
239         $this->assertEquals('Quiz 1 (page 1 of 3)', $attempt->attempt_page_title(0));
240         $this->assertEquals('Quiz 1 (page 2 of 3)', $attempt->attempt_page_title(1));
241         $this->assertEquals('Quiz 1 (page 3 of 3)', $attempt->attempt_page_title(2));
243         // Summary page.
244         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
246         // Review page.
247         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0, true));
248     }
250     /**
251      * Tests attempt page titles when questions are on multiple pages, and they are reviewed on multiple pages as well.
252      */
253     public function test_attempt_titles_multiple_multiple() {
254         $attempt = $this->create_quiz_and_attempt_with_layout(
255                 '1,2,3,4,5,6,7,8,9,10,0,11,12,13,14,15,16,17,18,19,20,0,' .
256                 '21,22,23,24,25,26,27,28,29,30,0,31,32,33,34,35,36,37,38,39,40,0,' .
257                 '41,42,43,44,45,46,47,48,49,50,0,51,52,53,54,55,56,57,58,59,60,0');
259         // Attempt page.
260         $this->assertEquals('Quiz 1 (page 1 of 6)', $attempt->attempt_page_title(0));
261         $this->assertEquals('Quiz 1 (page 2 of 6)', $attempt->attempt_page_title(1));
262         $this->assertEquals('Quiz 1 (page 6 of 6)', $attempt->attempt_page_title(5));
264         // Summary page.
265         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
267         // Review page.
268         $this->assertEquals('Quiz 1: Attempt review (page 1 of 6)', $attempt->review_page_title(0));
269         $this->assertEquals('Quiz 1: Attempt review (page 2 of 6)', $attempt->review_page_title(1));
270         $this->assertEquals('Quiz 1: Attempt review (page 6 of 6)', $attempt->review_page_title(5));
272         // When all questions are shown.
273         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0, true));
274         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(1, true));
275     }
277     public function test_is_participant() {
278         global $USER;
279         $this->resetAfterTest();
280         $this->setAdminUser();
281         $course = $this->getDataGenerator()->create_course();
282         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
283         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', [], 'manual', 0, 0, ENROL_USER_SUSPENDED);
284         $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
285         $quizobj = quiz::create($quiz->id);
287         // Login as student.
288         $this->setUser($student);
289         // Convert to a lesson object.
290         $this->assertEquals(true, $quizobj->is_participant($student->id),
291             'Student is enrolled, active and can participate');
293         // Login as student2.
294         $this->setUser($student2);
295         $this->assertEquals(false, $quizobj->is_participant($student2->id),
296             'Student is enrolled, suspended and can NOT participate');
298         // Login as an admin.
299         $this->setAdminUser();
300         $this->assertEquals(false, $quizobj->is_participant($USER->id),
301             'Admin is not enrolled and can NOT participate');
303         $this->getDataGenerator()->enrol_user(2, $course->id);
304         $this->assertEquals(true, $quizobj->is_participant($USER->id),
305             'Admin is enrolled and can participate');
307         $this->getDataGenerator()->enrol_user(2, $course->id, [], 'manual', 0, 0, ENROL_USER_SUSPENDED);
308         $this->assertEquals(true, $quizobj->is_participant($USER->id),
309             'Admin is enrolled, suspended and can participate');
310     }
312     /**
313      * Test quiz_prepare_and_start_new_attempt function
314      */
315     public function test_quiz_prepare_and_start_new_attempt() {
316         global $USER;
317         $this->resetAfterTest();
319         // Create course.
320         $course = $this->getDataGenerator()->create_course();
321         // Create students.
322         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
323         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
324         // Create quiz.
325         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
326         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'grade' => 100.0, 'sumgrades' => 2, 'layout' => '1,0']);
327         // Create question and add it to quiz.
328         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
329         $cat = $questiongenerator->create_question_category();
330         $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
331         quiz_add_quiz_question($question->id, $quiz, 1);
333         $quizobj = quiz::create($quiz->id);
335         // Login as student1.
336         $this->setUser($student1);
337         // Create attempt for student1.
338         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null, false, [], []);
339         $this->assertEquals($student1->id, $attempt->userid);
340         $this->assertEquals(0, $attempt->preview);
342         // Login as student2.
343         $this->setUser($student2);
344         // Create attempt for student2.
345         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null, false, [], []);
346         $this->assertEquals($student2->id, $attempt->userid);
347         $this->assertEquals(0, $attempt->preview);
349         // Login as admin.
350         $this->setAdminUser();
351         // Create attempt for student1.
352         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $student1->id);
353         $this->assertEquals($student1->id, $attempt->userid);
354         $this->assertEquals(0, $attempt->preview);
355         $student1attempt = $attempt; // Save for extra verification below.
356         // Create attempt for student2.
357         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $student2->id);
358         $this->assertEquals($student2->id, $attempt->userid);
359         $this->assertEquals(0, $attempt->preview);
360         // Create attempt for user id that the same with current $USER->id.
361         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $USER->id);
362         $this->assertEquals($USER->id, $attempt->userid);
363         $this->assertEquals(1, $attempt->preview);
365         // Check that the userid stored in the first step is the user the attempt is for,
366         // not the user who triggered the creation.
367         $quba = question_engine::load_questions_usage_by_activity($student1attempt->uniqueid);
368         $step = $quba->get_question_attempt(1)->get_step(0);
369         $this->assertEquals($student1->id, $step->get_user_id());
370     }