2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
22 * @copyright 2013 Adrian Greeve
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
32 * Unit tests for quiz events.
34 * @copyright 2013 Adrian Greeve
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class mod_quiz_structure_testcase extends advanced_testcase {
40 * Prepare the quiz object with standard data. Ready for testing.
42 protected function prepare_quiz_data() {
44 $this->resetAfterTest(true);
47 $course = $this->getDataGenerator()->create_course();
50 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
52 $quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0,
53 'grade' => 100.0, 'sumgrades' => 2));
55 $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
57 return array($quiz, $cm, $course);
61 * Test getting the quiz slots.
63 public function test_get_quiz_slots() {
65 list($quiz, $cm, $course) = $this->prepare_quiz_data();
66 $quizobj = new quiz($quiz, $cm, $course);
67 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
69 // When no slots exist or slots propery is not set.
70 $slots = $structure->get_slots();
71 $this->assertInternalType('array', $slots);
72 $this->assertCount(0, $slots);
74 // Append slots to the quiz.
75 $this->add_eight_questions_to_the_quiz($quiz);
76 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
78 // Are the correct slots returned?
79 $slots = $structure->get_slots();
80 $this->assertCount(8, $slots);
84 * Test getting the quiz sections.
86 public function test_get_quiz_sections() {
88 list($quiz, $cm, $course) = $this->prepare_quiz_data();
89 $quizobj = new quiz($quiz, $cm, $course);
90 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
92 // Are the correct sections returned?
93 $sections = $structure->get_quiz_sections();
94 $this->assertCount(1, $sections);
98 * Verify that the given layout matches that expected.
99 * @param array $expectedlayout
100 * @param \mod_quiz\structure $structure
102 protected function assert_quiz_layout($expectedlayout, \mod_quiz\structure $structure) {
104 foreach ($expectedlayout as $slotid => $page) {
106 $this->assertEquals($slotid, $structure->get_question_in_slot($slotnumber)->slotid,
107 'Wrong question in slot ' . $slotnumber);
108 $this->assertEquals($page, $structure->get_question_in_slot($slotnumber)->page,
109 'Wrong page number for slot ' . $slotnumber);
114 * Test moving slots in the quiz.
116 public function test_move_slot() {
117 // Create a test quiz with 8 questions.
118 list($quiz, $cm, $course) = $this->prepare_quiz_data();
119 $this->add_eight_questions_to_the_quiz($quiz);
120 $quizobj = new quiz($quiz, $cm, $course);
121 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
123 // Store the original order of slots, so we can assert what has changed.
124 $originalslotids = array();
125 foreach ($structure->get_slots() as $slot) {
126 $originalslotids[$slot->slot] = $slot->id;
129 // Don't actually move anything. Check the layout is unchanged.
130 $idmove = $structure->get_question_in_slot(2)->slotid;
131 $idbefore = $structure->get_question_in_slot(1)->slotid;
132 $structure->move_slot($idmove, $idbefore, 2);
134 // Having called move, we need to reload $structure.
135 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
136 $this->assert_quiz_layout(array(
137 $originalslotids[1] => 1,
138 $originalslotids[2] => 2,
139 $originalslotids[3] => 2,
140 $originalslotids[4] => 2,
141 $originalslotids[5] => 2,
142 $originalslotids[6] => 2,
143 $originalslotids[7] => 3,
144 $originalslotids[8] => 4,
147 // Slots don't move. Page changed.
148 $idmove = $structure->get_question_in_slot(2)->slotid;
149 $idbefore = $structure->get_question_in_slot(1)->slotid;
150 $structure->move_slot($idmove, $idbefore, 1);
152 // Having called move, we need to reload $structure.
153 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
154 $this->assert_quiz_layout(array(
155 $originalslotids[1] => 1,
156 $originalslotids[2] => 1,
157 $originalslotids[3] => 2,
158 $originalslotids[4] => 2,
159 $originalslotids[5] => 2,
160 $originalslotids[6] => 2,
161 $originalslotids[7] => 3,
162 $originalslotids[8] => 4,
165 // Slots move 2 > 3. Page unchanged. Pages not reordered.
166 $idmove = $structure->get_question_in_slot(2)->slotid;
167 $idbefore = $structure->get_question_in_slot(3)->slotid;
168 $structure->move_slot($idmove, $idbefore, '2');
170 // Having called move, we need to reload $structure.
171 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
172 $this->assert_quiz_layout(array(
173 $originalslotids[1] => 1,
174 $originalslotids[3] => 2,
175 $originalslotids[2] => 2,
176 $originalslotids[4] => 2,
177 $originalslotids[5] => 2,
178 $originalslotids[6] => 2,
179 $originalslotids[7] => 3,
180 $originalslotids[8] => 4,
183 // Slots move 6 > 7. Page changed. Pages not reordered.
184 $idmove = $structure->get_question_in_slot(6)->slotid;
185 $idbefore = $structure->get_question_in_slot(7)->slotid;
186 $structure->move_slot($idmove, $idbefore, '3');
188 // Having called move, we need to reload $structure.
189 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
190 $this->assert_quiz_layout(array(
191 $originalslotids[1] => 1,
192 $originalslotids[3] => 2,
193 $originalslotids[2] => 2,
194 $originalslotids[4] => 2,
195 $originalslotids[5] => 2,
196 $originalslotids[7] => 3,
197 $originalslotids[6] => 3,
198 $originalslotids[8] => 4,
201 // Page changed slot 6 . Pages not reordered.
202 $idmove = $structure->get_question_in_slot(6)->slotid;
203 $idbefore = $structure->get_question_in_slot(5)->slotid;
204 $structure->move_slot($idmove, $idbefore, 2);
206 // Having called move, we need to reload $structure.
207 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
208 $this->assert_quiz_layout(array(
209 $originalslotids[1] => 1,
210 $originalslotids[3] => 2,
211 $originalslotids[2] => 2,
212 $originalslotids[4] => 2,
213 $originalslotids[5] => 2,
214 $originalslotids[7] => 2,
215 $originalslotids[6] => 3,
216 $originalslotids[8] => 4,
219 // Slots move 1 > 2. Page changed. Page 2 becomes page 1. Pages reordered.
220 $idmove = $structure->get_question_in_slot(1)->slotid;
221 $idbefore = $structure->get_question_in_slot(2)->slotid;
222 $structure->move_slot($idmove, $idbefore, 2);
224 // Having called move, we need to reload $structure.
225 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
226 $this->assert_quiz_layout(array(
227 $originalslotids[3] => 1,
228 $originalslotids[1] => 1,
229 $originalslotids[2] => 1,
230 $originalslotids[4] => 1,
231 $originalslotids[5] => 1,
232 $originalslotids[7] => 1,
233 $originalslotids[6] => 2,
234 $originalslotids[8] => 3,
237 // Slots move 7 > 3. Page changed. Page 3 becomes page 2. Pages reordered.
238 $idmove = $structure->get_question_in_slot(7)->slotid;
239 $idbefore = $structure->get_question_in_slot(2)->slotid;
240 $structure->move_slot($idmove, $idbefore, 1);
242 // Having called move, we need to reload $structure.
243 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
244 $this->assert_quiz_layout(array(
245 $originalslotids[3] => 1,
246 $originalslotids[1] => 1,
247 $originalslotids[6] => 1,
248 $originalslotids[2] => 1,
249 $originalslotids[4] => 1,
250 $originalslotids[5] => 1,
251 $originalslotids[7] => 1,
252 $originalslotids[8] => 2,
255 // Slots move 2 > top. No page changes.
256 $idmove = $structure->get_question_in_slot(2)->slotid;
257 $structure->move_slot($idmove, 0, 1);
259 // Having called move, we need to reload $structure.
260 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
261 $this->assert_quiz_layout(array(
262 $originalslotids[1] => 1,
263 $originalslotids[3] => 1,
264 $originalslotids[6] => 1,
265 $originalslotids[2] => 1,
266 $originalslotids[4] => 1,
267 $originalslotids[5] => 1,
268 $originalslotids[7] => 1,
269 $originalslotids[8] => 2,
274 * Test removing slots from a quiz.
276 public function test_quiz_remove_slot() {
279 $this->resetAfterTest(true);
280 $this->setAdminUser();
282 // Setup a quiz with 1 standard and 1 random question.
283 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
284 $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
286 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
287 $cat = $questiongenerator->create_question_category();
288 $standardq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
290 quiz_add_quiz_question($standardq->id, $quiz);
291 quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
293 // Get the random question.
294 $randomq = $DB->get_record('question', array('qtype' => 'random'));
296 $structure = \mod_quiz\structure::create_for($quiz);
298 // Check that the setup looks right.
299 $this->assertEquals(2, $structure->get_question_count());
300 $this->assertEquals($standardq->id, $structure->get_question_in_slot(1)->questionid);
301 $this->assertEquals($randomq->id, $structure->get_question_in_slot(2)->questionid);
303 // Remove the standard question.
304 $structure->remove_slot($quiz, 1);
306 $alteredstructure = \mod_quiz\structure::create_for($quiz);
308 // Check the new ordering, and that the slot number was updated.
309 $this->assertEquals(1, $alteredstructure->get_question_count());
310 $this->assertEquals($randomq->id, $alteredstructure->get_question_in_slot(1)->questionid);
312 // Check that the ordinary question was not deleted.
313 $this->assertTrue($DB->record_exists('question', array('id' => $standardq->id)));
315 // Remove the random question.
316 $structure->remove_slot($quiz, 1);
317 $alteredstructure = \mod_quiz\structure::create_for($quiz);
319 // Check that new ordering.
320 $this->assertEquals(0, $alteredstructure->get_question_count());
322 // Check that the random question was deleted.
323 $this->assertFalse($DB->record_exists('question', array('id' => $randomq->id)));
327 * Test updating pagebreaks in the quiz.
329 public function test_update_page_break() {
330 // Create a test quiz with 8 questions.
331 list($quiz, $cm, $course) = $this->prepare_quiz_data();
332 $this->add_eight_questions_to_the_quiz($quiz);
333 $quizobj = new quiz($quiz, $cm, $course);
334 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
336 // Store the original order of slots, so we can assert what has changed.
337 $originalslotids = array();
338 foreach ($structure->get_slots() as $slot) {
339 $originalslotids[$slot->slot] = $slot->id;
342 // Test removing a page break.
343 $slotid = $structure->get_question_in_slot(2)->slotid;
344 $type = \mod_quiz\repaginate::LINK;
345 $slots = $structure->update_page_break($quiz, $slotid, $type);
347 // Having called update page break, we need to reload $structure.
348 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
349 $this->assert_quiz_layout(array(
350 $originalslotids[1] => 1,
351 $originalslotids[2] => 1,
352 $originalslotids[3] => 1,
353 $originalslotids[4] => 1,
354 $originalslotids[5] => 1,
355 $originalslotids[6] => 1,
356 $originalslotids[7] => 2,
357 $originalslotids[8] => 3,
360 // Test adding a page break.
361 $slotid = $structure->get_question_in_slot(2)->slotid;
362 $type = \mod_quiz\repaginate::UNLINK;
363 $slots = $structure->update_page_break($quiz, $slotid, $type);
365 // Having called update page break, we need to reload $structure.
366 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
367 $this->assert_quiz_layout(array(
368 $originalslotids[1] => 1,
369 $originalslotids[2] => 2,
370 $originalslotids[3] => 2,
371 $originalslotids[4] => 2,
372 $originalslotids[5] => 2,
373 $originalslotids[6] => 2,
374 $originalslotids[7] => 3,
375 $originalslotids[8] => 4,
380 * Populate quiz with eight questions.
381 * @param stdClass $quiz the quiz to add to.
383 public function add_eight_questions_to_the_quiz($quiz) {
384 // We add 8 numerical questions with this layout:
385 // Slot 1 2 3 4 5 6 7 8
386 // Page 1 2 2 2 2 2 3 4.
390 $pagenumberdefaults = array(2, 7, 8);
392 // Create a couple of questions.
393 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
395 $cat = $questiongenerator->create_question_category();
396 for ($i = 0; $i < 8; $i ++) {
397 $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
399 if (in_array($i + 1, $pagenumberdefaults)) {
402 // Add them to the quiz.
403 quiz_add_quiz_question($numq->id, $quiz, $pagenumber);