Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
namespace mod_quiz;
18
 
1441 ariadna 19
use core\exception\coding_exception;
20
use question_bank;
21
 
1 efrain 22
defined('MOODLE_INTERNAL') || die();
23
 
24
global $CFG;
25
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
26
 
27
/**
28
 * Unit tests for quiz events.
29
 *
30
 * @package   mod_quiz
31
 * @category  test
32
 * @copyright 2013 Adrian Greeve
33
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 * @covers \mod_quiz\structure
35
 */
1441 ariadna 36
final class structure_test extends \advanced_testcase {
1 efrain 37
 
38
    use \quiz_question_helper_test_trait;
39
 
40
    /**
41
     * Create a course with an empty quiz.
42
     * @return array with three elements quiz, cm and course.
43
     */
44
    protected function prepare_quiz_data() {
45
 
46
        $this->resetAfterTest(true);
47
 
48
        // Create a course.
49
        $course = $this->getDataGenerator()->create_course();
50
 
51
        // Make a quiz.
52
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
53
 
54
        $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0,
55
            'grade' => 100.0, 'sumgrades' => 2, 'preferredbehaviour' => 'immediatefeedback']);
56
 
57
        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
58
 
59
        return [$quiz, $cm, $course];
60
    }
61
 
62
    /**
63
     * Creat a test quiz.
64
     *
65
     * $layout looks like this:
66
     * $layout = array(
67
     *     'Heading 1'
68
     *     array('TF1', 1, 'truefalse'),
69
     *     'Heading 2*'
70
     *     array('TF2', 2, 'truefalse'),
71
     * );
72
     * That is, either a string, which represents a section heading,
73
     * or an array that represents a question.
74
     *
75
     * If the section heading ends with *, that section is shuffled.
76
     *
77
     * The elements in the question array are name, page number, and question type.
78
     *
79
     * @param array $layout as above.
80
     * @return quiz_settings the created quiz.
81
     */
82
    protected function create_test_quiz($layout) {
83
        list($quiz, $cm, $course) = $this->prepare_quiz_data();
84
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
85
        $cat = $questiongenerator->create_question_category();
86
 
87
        $headings = [];
88
        $slot = 1;
89
        $lastpage = 0;
90
        foreach ($layout as $item) {
91
            if (is_string($item)) {
92
                if (isset($headings[$lastpage + 1])) {
93
                    throw new \coding_exception('Sections cannot be empty.');
94
                }
95
                $headings[$lastpage + 1] = $item;
96
 
97
            } else {
98
                list($name, $page, $qtype) = $item;
99
                if ($page < 1 || !($page == $lastpage + 1 ||
100
                        (!isset($headings[$lastpage + 1]) && $page == $lastpage))) {
101
                    throw new \coding_exception('Page numbers wrong.');
102
                }
103
                $q = $questiongenerator->create_question($qtype, null,
104
                        ['name' => $name, 'category' => $cat->id]);
105
 
106
                quiz_add_quiz_question($q->id, $quiz, $page);
107
                $lastpage = $page;
108
            }
109
        }
110
 
111
        $quizobj = new quiz_settings($quiz, $cm, $course);
112
        $structure = structure::create_for_quiz($quizobj);
113
        if (isset($headings[1])) {
114
            list($heading, $shuffle) = $this->parse_section_name($headings[1]);
115
            $sections = $structure->get_sections();
116
            $firstsection = reset($sections);
117
            $structure->set_section_heading($firstsection->id, $heading);
118
            $structure->set_section_shuffle($firstsection->id, $shuffle);
119
            unset($headings[1]);
120
        }
121
 
122
        foreach ($headings as $startpage => $heading) {
123
            list($heading, $shuffle) = $this->parse_section_name($heading);
124
            $id = $structure->add_section_heading($startpage, $heading);
125
            $structure->set_section_shuffle($id, $shuffle);
126
        }
127
 
128
        return $quizobj;
129
    }
130
 
131
    /**
132
     * Verify that the given layout matches that expected.
133
     * @param array $expectedlayout as for $layout in {@link create_test_quiz()}.
134
     * @param structure $structure the structure to test.
135
     */
136
    protected function assert_quiz_layout($expectedlayout, structure $structure) {
137
        $sections = $structure->get_sections();
138
 
139
        $slot = 1;
140
        foreach ($expectedlayout as $item) {
141
            if (is_string($item)) {
142
                list($heading, $shuffle) = $this->parse_section_name($item);
143
                $section = array_shift($sections);
144
 
145
                if ($slot > 1 && $section->heading == '' && $section->firstslot == 1) {
146
                    // The array $expectedlayout did not contain default first quiz section, so skip over it.
147
                    $section = array_shift($sections);
148
                }
149
 
150
                $this->assertEquals($slot, $section->firstslot);
151
                $this->assertEquals($heading, $section->heading);
152
                $this->assertEquals($shuffle, $section->shufflequestions);
153
 
154
            } else {
155
                list($name, $page, $qtype) = $item;
156
                $question = $structure->get_question_in_slot($slot);
157
                $this->assertEquals($name,  $question->name);
158
                $this->assertEquals($slot,  $question->slot,  'Slot number wrong for question ' . $name);
159
                $this->assertEquals($qtype, $question->qtype, 'Question type wrong for question ' . $name);
160
                $this->assertEquals($page,  $question->page,  'Page number wrong for question ' . $name);
161
 
162
                $slot += 1;
163
            }
164
        }
165
 
166
        if ($slot - 1 != count($structure->get_slots())) {
167
            $this->fail('The quiz contains more slots than expected.');
168
        }
169
 
170
        if (!empty($sections)) {
171
            $section = array_shift($sections);
172
            if ($section->heading != '' || $section->firstslot != 1) {
173
                $this->fail('Unexpected section (' . $section->heading .') found in the quiz.');
174
            }
175
        }
176
    }
177
 
178
    /**
179
     * Parse the section name, optionally followed by a * to mean shuffle, as
180
     * used by create_test_quiz as assert_quiz_layout.
181
     * @param string $heading the heading.
182
     * @return array with two elements, the heading and the shuffle setting.
183
     */
184
    protected function parse_section_name($heading) {
185
        if (substr($heading, -1) == '*') {
186
            return [substr($heading, 0, -1), 1];
187
        } else {
188
            return [$heading, 0];
189
        }
190
    }
191
 
11 efrain 192
    public function test_get_quiz_slots(): void {
1 efrain 193
        $quizobj = $this->create_test_quiz([
194
                ['TF1', 1, 'truefalse'],
195
                ['TF2', 1, 'truefalse'],
196
        ]);
197
        $structure = structure::create_for_quiz($quizobj);
198
 
199
        // Are the correct slots returned?
200
        $slots = $structure->get_slots();
201
        $this->assertCount(2, $structure->get_slots());
202
    }
203
 
11 efrain 204
    public function test_quiz_has_one_section_by_default(): void {
1 efrain 205
        $quizobj = $this->create_test_quiz([
206
                ['TF1', 1, 'truefalse'],
207
        ]);
208
        $structure = structure::create_for_quiz($quizobj);
209
 
210
        $sections = $structure->get_sections();
211
        $this->assertCount(1, $sections);
212
 
213
        $section = array_shift($sections);
214
        $this->assertEquals(1, $section->firstslot);
215
        $this->assertEquals('', $section->heading);
216
        $this->assertEquals(0, $section->shufflequestions);
217
    }
218
 
11 efrain 219
    public function test_get_sections(): void {
1 efrain 220
        $quizobj = $this->create_test_quiz([
221
                'Heading 1*',
222
                ['TF1', 1, 'truefalse'],
223
                'Heading 2*',
224
                ['TF2', 2, 'truefalse'],
225
        ]);
226
        $structure = structure::create_for_quiz($quizobj);
227
 
228
        $sections = $structure->get_sections();
229
        $this->assertCount(2, $sections);
230
 
231
        $section = array_shift($sections);
232
        $this->assertEquals(1, $section->firstslot);
233
        $this->assertEquals('Heading 1', $section->heading);
234
        $this->assertEquals(1, $section->shufflequestions);
235
 
236
        $section = array_shift($sections);
237
        $this->assertEquals(2, $section->firstslot);
238
        $this->assertEquals('Heading 2', $section->heading);
239
        $this->assertEquals(1, $section->shufflequestions);
240
    }
241
 
11 efrain 242
    public function test_remove_section_heading(): void {
1 efrain 243
        $quizobj = $this->create_test_quiz([
244
                'Heading 1',
245
                ['TF1', 1, 'truefalse'],
246
                'Heading 2',
247
                ['TF2', 2, 'truefalse'],
248
        ]);
249
        $structure = structure::create_for_quiz($quizobj);
250
 
251
        $sections = $structure->get_sections();
252
        $section = end($sections);
253
        $structure->remove_section_heading($section->id);
254
 
255
        $structure = structure::create_for_quiz($quizobj);
256
        $this->assert_quiz_layout([
257
                'Heading 1',
258
                ['TF1', 1, 'truefalse'],
259
                ['TF2', 2, 'truefalse'],
260
        ], $structure);
261
    }
262
 
11 efrain 263
    public function test_cannot_remove_first_section(): void {
1 efrain 264
        $quizobj = $this->create_test_quiz([
265
                'Heading 1',
266
                ['TF1', 1, 'truefalse'],
267
        ]);
268
        $structure = structure::create_for_quiz($quizobj);
269
 
270
        $sections = $structure->get_sections();
271
        $section = reset($sections);
272
 
273
        $this->expectException(\coding_exception::class);
274
        $structure->remove_section_heading($section->id);
275
    }
276
 
11 efrain 277
    public function test_move_slot_to_the_same_place_does_nothing(): void {
1 efrain 278
        $quizobj = $this->create_test_quiz([
279
                ['TF1', 1, 'truefalse'],
280
                ['TF2', 1, 'truefalse'],
281
                ['TF3', 2, 'truefalse'],
282
        ]);
283
        $structure = structure::create_for_quiz($quizobj);
284
 
285
        $idtomove = $structure->get_question_in_slot(2)->slotid;
286
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
287
        $structure->move_slot($idtomove, $idmoveafter, '1');
288
 
289
        $structure = structure::create_for_quiz($quizobj);
290
        $this->assert_quiz_layout([
291
                ['TF1', 1, 'truefalse'],
292
                ['TF2', 1, 'truefalse'],
293
                ['TF3', 2, 'truefalse'],
294
        ], $structure);
295
    }
296
 
11 efrain 297
    public function test_move_slot_end_of_one_page_to_start_of_next(): void {
1 efrain 298
        $quizobj = $this->create_test_quiz([
299
                ['TF1', 1, 'truefalse'],
300
                ['TF2', 1, 'truefalse'],
301
                ['TF3', 2, 'truefalse'],
302
        ]);
303
        $structure = structure::create_for_quiz($quizobj);
304
 
305
        $idtomove = $structure->get_question_in_slot(2)->slotid;
306
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
307
        $structure->move_slot($idtomove, $idmoveafter, '2');
308
 
309
        $structure = structure::create_for_quiz($quizobj);
310
        $this->assert_quiz_layout([
311
                ['TF1', 1, 'truefalse'],
312
                ['TF2', 2, 'truefalse'],
313
                ['TF3', 2, 'truefalse'],
314
        ], $structure);
315
    }
316
 
11 efrain 317
    public function test_move_last_slot_to_previous_page_emptying_the_last_page(): void {
1 efrain 318
        $quizobj = $this->create_test_quiz([
319
                ['TF1', 1, 'truefalse'],
320
                ['TF2', 2, 'truefalse'],
321
        ]);
322
        $structure = structure::create_for_quiz($quizobj);
323
 
324
        $idtomove = $structure->get_question_in_slot(2)->slotid;
325
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
326
        $structure->move_slot($idtomove, $idmoveafter, '1');
327
 
328
        $structure = structure::create_for_quiz($quizobj);
329
        $this->assert_quiz_layout([
330
                ['TF1', 1, 'truefalse'],
331
                ['TF2', 1, 'truefalse'],
332
        ], $structure);
333
    }
334
 
11 efrain 335
    public function test_end_of_one_section_to_start_of_next(): void {
1 efrain 336
        $quizobj = $this->create_test_quiz([
337
                ['TF1', 1, 'truefalse'],
338
                ['TF2', 1, 'truefalse'],
339
                'Heading',
340
                ['TF3', 2, 'truefalse'],
341
        ]);
342
        $structure = structure::create_for_quiz($quizobj);
343
 
344
        $idtomove = $structure->get_question_in_slot(2)->slotid;
345
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
346
        $structure->move_slot($idtomove, $idmoveafter, '2');
347
 
348
        $structure = structure::create_for_quiz($quizobj);
349
        $this->assert_quiz_layout([
350
                ['TF1', 1, 'truefalse'],
351
                'Heading',
352
                ['TF2', 2, 'truefalse'],
353
                ['TF3', 2, 'truefalse'],
354
        ], $structure);
355
    }
356
 
11 efrain 357
    public function test_start_of_one_section_to_end_of_previous(): void {
1 efrain 358
        $quizobj = $this->create_test_quiz([
359
                ['TF1', 1, 'truefalse'],
360
                'Heading',
361
                ['TF2', 2, 'truefalse'],
362
                ['TF3', 2, 'truefalse'],
363
        ]);
364
        $structure = structure::create_for_quiz($quizobj);
365
 
366
        $idtomove = $structure->get_question_in_slot(2)->slotid;
367
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
368
        $structure->move_slot($idtomove, $idmoveafter, '1');
369
 
370
        $structure = structure::create_for_quiz($quizobj);
371
        $this->assert_quiz_layout([
372
                ['TF1', 1, 'truefalse'],
373
                ['TF2', 1, 'truefalse'],
374
                'Heading',
375
                ['TF3', 2, 'truefalse'],
376
        ], $structure);
377
    }
11 efrain 378
    public function test_move_slot_on_same_page(): void {
1 efrain 379
        $quizobj = $this->create_test_quiz([
380
                ['TF1', 1, 'truefalse'],
381
                ['TF2', 1, 'truefalse'],
382
                ['TF3', 1, 'truefalse'],
383
        ]);
384
        $structure = structure::create_for_quiz($quizobj);
385
 
386
        $idtomove = $structure->get_question_in_slot(2)->slotid;
387
        $idmoveafter = $structure->get_question_in_slot(3)->slotid;
388
        $structure->move_slot($idtomove, $idmoveafter, '1');
389
 
390
        $structure = structure::create_for_quiz($quizobj);
391
        $this->assert_quiz_layout([
392
                ['TF1', 1, 'truefalse'],
393
                ['TF3', 1, 'truefalse'],
394
                ['TF2', 1, 'truefalse'],
395
        ], $structure);
396
    }
397
 
11 efrain 398
    public function test_move_slot_up_onto_previous_page(): void {
1 efrain 399
        $quizobj = $this->create_test_quiz([
400
                ['TF1', 1, 'truefalse'],
401
                ['TF2', 2, 'truefalse'],
402
                ['TF3', 2, 'truefalse'],
403
        ]);
404
        $structure = structure::create_for_quiz($quizobj);
405
 
406
        $idtomove = $structure->get_question_in_slot(3)->slotid;
407
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
408
        $structure->move_slot($idtomove, $idmoveafter, '1');
409
 
410
        $structure = structure::create_for_quiz($quizobj);
411
        $this->assert_quiz_layout([
412
                ['TF1', 1, 'truefalse'],
413
                ['TF3', 1, 'truefalse'],
414
                ['TF2', 2, 'truefalse'],
415
        ], $structure);
416
    }
417
 
11 efrain 418
    public function test_move_slot_emptying_a_page_renumbers_pages(): void {
1 efrain 419
        $quizobj = $this->create_test_quiz([
420
                ['TF1', 1, 'truefalse'],
421
                ['TF2', 2, 'truefalse'],
422
                ['TF3', 3, 'truefalse'],
423
        ]);
424
        $structure = structure::create_for_quiz($quizobj);
425
 
426
        $idtomove = $structure->get_question_in_slot(2)->slotid;
427
        $idmoveafter = $structure->get_question_in_slot(3)->slotid;
428
        $structure->move_slot($idtomove, $idmoveafter, '3');
429
 
430
        $structure = structure::create_for_quiz($quizobj);
431
        $this->assert_quiz_layout([
432
                ['TF1', 1, 'truefalse'],
433
                ['TF3', 2, 'truefalse'],
434
                ['TF2', 2, 'truefalse'],
435
        ], $structure);
436
    }
437
 
11 efrain 438
    public function test_move_slot_too_small_page_number_detected(): void {
1 efrain 439
        $quizobj = $this->create_test_quiz([
440
                ['TF1', 1, 'truefalse'],
441
                ['TF2', 2, 'truefalse'],
442
                ['TF3', 3, 'truefalse'],
443
        ]);
444
        $structure = structure::create_for_quiz($quizobj);
445
 
446
        $idtomove = $structure->get_question_in_slot(3)->slotid;
447
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
448
        $this->expectException(\coding_exception::class);
449
        $structure->move_slot($idtomove, $idmoveafter, '1');
450
    }
451
 
11 efrain 452
    public function test_move_slot_too_large_page_number_detected(): void {
1 efrain 453
        $quizobj = $this->create_test_quiz([
454
                ['TF1', 1, 'truefalse'],
455
                ['TF2', 2, 'truefalse'],
456
                ['TF3', 3, 'truefalse'],
457
        ]);
458
        $structure = structure::create_for_quiz($quizobj);
459
 
460
        $idtomove = $structure->get_question_in_slot(1)->slotid;
461
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
462
        $this->expectException(\coding_exception::class);
463
        $structure->move_slot($idtomove, $idmoveafter, '4');
464
    }
465
 
11 efrain 466
    public function test_move_slot_within_section(): void {
1 efrain 467
        $quizobj = $this->create_test_quiz([
468
                'Heading 1',
469
                ['TF1', 1, 'truefalse'],
470
                ['TF2', 1, 'truefalse'],
471
                'Heading 2',
472
                ['TF3', 2, 'truefalse'],
473
        ]);
474
        $structure = structure::create_for_quiz($quizobj);
475
 
476
        $idtomove = $structure->get_question_in_slot(1)->slotid;
477
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
478
        $structure->move_slot($idtomove, $idmoveafter, '1');
479
 
480
        $structure = structure::create_for_quiz($quizobj);
481
        $this->assert_quiz_layout([
482
                'Heading 1',
483
                ['TF2', 1, 'truefalse'],
484
                ['TF1', 1, 'truefalse'],
485
                'Heading 2',
486
                ['TF3', 2, 'truefalse'],
487
        ], $structure);
488
    }
489
 
11 efrain 490
    public function test_move_slot_to_new_section(): void {
1 efrain 491
        $quizobj = $this->create_test_quiz([
492
                'Heading 1',
493
                ['TF1', 1, 'truefalse'],
494
                ['TF2', 1, 'truefalse'],
495
                'Heading 2',
496
                ['TF3', 2, 'truefalse'],
497
        ]);
498
        $structure = structure::create_for_quiz($quizobj);
499
 
500
        $idtomove = $structure->get_question_in_slot(2)->slotid;
501
        $idmoveafter = $structure->get_question_in_slot(3)->slotid;
502
        $structure->move_slot($idtomove, $idmoveafter, '2');
503
 
504
        $structure = structure::create_for_quiz($quizobj);
505
        $this->assert_quiz_layout([
506
                'Heading 1',
507
                ['TF1', 1, 'truefalse'],
508
                'Heading 2',
509
                ['TF3', 2, 'truefalse'],
510
                ['TF2', 2, 'truefalse'],
511
        ], $structure);
512
    }
513
 
11 efrain 514
    public function test_move_slot_to_start(): void {
1 efrain 515
        $quizobj = $this->create_test_quiz([
516
                'Heading 1',
517
                ['TF1', 1, 'truefalse'],
518
                'Heading 2',
519
                ['TF2', 2, 'truefalse'],
520
                ['TF3', 2, 'truefalse'],
521
        ]);
522
        $structure = structure::create_for_quiz($quizobj);
523
 
524
        $idtomove = $structure->get_question_in_slot(3)->slotid;
525
        $structure->move_slot($idtomove, 0, '1');
526
 
527
        $structure = structure::create_for_quiz($quizobj);
528
        $this->assert_quiz_layout([
529
                'Heading 1',
530
                ['TF3', 1, 'truefalse'],
531
                ['TF1', 1, 'truefalse'],
532
                'Heading 2',
533
                ['TF2', 2, 'truefalse'],
534
        ], $structure);
535
    }
536
 
11 efrain 537
    public function test_move_slot_down_to_start_of_second_section(): void {
1 efrain 538
        $quizobj = $this->create_test_quiz([
539
                'Heading 1',
540
                ['TF1', 1, 'truefalse'],
541
                ['TF2', 1, 'truefalse'],
542
                'Heading 2',
543
                ['TF3', 2, 'truefalse'],
544
        ]);
545
        $structure = structure::create_for_quiz($quizobj);
546
 
547
        $idtomove = $structure->get_question_in_slot(2)->slotid;
548
        $idmoveafter = $structure->get_question_in_slot(2)->slotid;
549
        $structure->move_slot($idtomove, $idmoveafter, '2');
550
 
551
        $structure = structure::create_for_quiz($quizobj);
552
        $this->assert_quiz_layout([
553
                'Heading 1',
554
                ['TF1', 1, 'truefalse'],
555
                'Heading 2',
556
                ['TF2', 2, 'truefalse'],
557
                ['TF3', 2, 'truefalse'],
558
        ], $structure);
559
    }
560
 
11 efrain 561
    public function test_move_first_slot_down_to_start_of_page_2(): void {
1 efrain 562
        $quizobj = $this->create_test_quiz([
563
                'Heading 1',
564
                ['TF1', 1, 'truefalse'],
565
                ['TF2', 2, 'truefalse'],
566
        ]);
567
        $structure = structure::create_for_quiz($quizobj);
568
 
569
        $idtomove = $structure->get_question_in_slot(1)->slotid;
570
        $structure->move_slot($idtomove, 0, '2');
571
 
572
        $structure = structure::create_for_quiz($quizobj);
573
        $this->assert_quiz_layout([
574
                'Heading 1',
575
                ['TF1', 1, 'truefalse'],
576
                ['TF2', 1, 'truefalse'],
577
        ], $structure);
578
    }
579
 
11 efrain 580
    public function test_move_first_slot_to_same_place_on_page_1(): void {
1 efrain 581
        $quizobj = $this->create_test_quiz([
582
                'Heading 1',
583
                ['TF1', 1, 'truefalse'],
584
                ['TF2', 2, 'truefalse'],
585
        ]);
586
        $structure = structure::create_for_quiz($quizobj);
587
 
588
        $idtomove = $structure->get_question_in_slot(1)->slotid;
589
        $structure->move_slot($idtomove, 0, '1');
590
 
591
        $structure = structure::create_for_quiz($quizobj);
592
        $this->assert_quiz_layout([
593
                'Heading 1',
594
                ['TF1', 1, 'truefalse'],
595
                ['TF2', 2, 'truefalse'],
596
        ], $structure);
597
    }
598
 
11 efrain 599
    public function test_move_first_slot_to_before_page_1(): void {
1 efrain 600
        $quizobj = $this->create_test_quiz([
601
                'Heading 1',
602
                ['TF1', 1, 'truefalse'],
603
                ['TF2', 2, 'truefalse'],
604
        ]);
605
        $structure = structure::create_for_quiz($quizobj);
606
 
607
        $idtomove = $structure->get_question_in_slot(1)->slotid;
608
        $structure->move_slot($idtomove, 0, '');
609
 
610
        $structure = structure::create_for_quiz($quizobj);
611
        $this->assert_quiz_layout([
612
                'Heading 1',
613
                ['TF1', 1, 'truefalse'],
614
                ['TF2', 2, 'truefalse'],
615
        ], $structure);
616
    }
617
 
11 efrain 618
    public function test_move_slot_up_to_start_of_second_section(): void {
1 efrain 619
        $quizobj = $this->create_test_quiz([
620
                'Heading 1',
621
                ['TF1', 1, 'truefalse'],
622
                'Heading 2',
623
                ['TF2', 2, 'truefalse'],
624
                'Heading 3',
625
                ['TF3', 3, 'truefalse'],
626
                ['TF4', 3, 'truefalse'],
627
        ]);
628
        $structure = structure::create_for_quiz($quizobj);
629
 
630
        $idtomove = $structure->get_question_in_slot(3)->slotid;
631
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
632
        $structure->move_slot($idtomove, $idmoveafter, '2');
633
 
634
        $structure = structure::create_for_quiz($quizobj);
635
        $this->assert_quiz_layout([
636
                'Heading 1',
637
                ['TF1', 1, 'truefalse'],
638
                'Heading 2',
639
                ['TF3', 2, 'truefalse'],
640
                ['TF2', 2, 'truefalse'],
641
                'Heading 3',
642
                ['TF4', 3, 'truefalse'],
643
        ], $structure);
644
    }
645
 
11 efrain 646
    public function test_move_slot_does_not_violate_heading_unique_key(): void {
1 efrain 647
        $quizobj = $this->create_test_quiz([
648
                'Heading 1',
649
                ['TF1', 1, 'truefalse'],
650
                'Heading 2',
651
                ['TF2', 2, 'truefalse'],
652
                'Heading 3',
653
                ['TF3', 3, 'truefalse'],
654
                ['TF4', 3, 'truefalse'],
655
        ]);
656
        $structure = structure::create_for_quiz($quizobj);
657
 
658
        $idtomove = $structure->get_question_in_slot(4)->slotid;
659
        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
660
        $structure->move_slot($idtomove, $idmoveafter, 1);
661
 
662
        $structure = structure::create_for_quiz($quizobj);
663
        $this->assert_quiz_layout([
664
                'Heading 1',
665
                ['TF1', 1, 'truefalse'],
666
                ['TF4', 1, 'truefalse'],
667
                'Heading 2',
668
                ['TF2', 2, 'truefalse'],
669
                'Heading 3',
670
                ['TF3', 3, 'truefalse'],
671
        ], $structure);
672
    }
673
 
11 efrain 674
    public function test_quiz_remove_slot(): void {
1 efrain 675
        $quizobj = $this->create_test_quiz([
676
                ['TF1', 1, 'truefalse'],
677
                ['TF2', 1, 'truefalse'],
678
                'Heading 2',
679
                ['TF3', 2, 'truefalse'],
680
        ]);
681
        $structure = structure::create_for_quiz($quizobj);
682
 
683
        $structure->remove_slot(2);
684
 
685
        $structure = structure::create_for_quiz($quizobj);
686
        $this->assert_quiz_layout([
687
                ['TF1', 1, 'truefalse'],
688
                'Heading 2',
689
                ['TF3', 2, 'truefalse'],
690
        ], $structure);
691
    }
692
 
11 efrain 693
    public function test_quiz_removing_a_random_question_deletes_the_question(): void {
1 efrain 694
        global $DB;
695
 
696
        $this->resetAfterTest(true);
697
        $this->setAdminUser();
698
 
699
        $quizobj = $this->create_test_quiz([
700
                ['TF1', 1, 'truefalse'],
701
        ]);
702
 
703
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
704
        $cat = $questiongenerator->create_question_category();
705
        $this->add_random_questions($quizobj->get_quizid(), 1, $cat->id, 1);
706
        $structure = structure::create_for_quiz($quizobj);
707
        $sql = 'SELECT qsr.*
708
                 FROM {question_set_references} qsr
709
                 JOIN {quiz_slots} qs ON qs.id = qsr.itemid
710
                 WHERE qs.quizid = ?
711
                   AND qsr.component = ?
712
                   AND qsr.questionarea = ?';
713
        $randomq = $DB->get_record_sql($sql, [$quizobj->get_quizid(), 'mod_quiz', 'slot']);
714
 
715
        $structure->remove_slot(2);
716
 
717
        $structure = structure::create_for_quiz($quizobj);
718
        $this->assert_quiz_layout([
719
                ['TF1', 1, 'truefalse'],
720
        ], $structure);
721
        $this->assertFalse($DB->record_exists('question_set_references',
722
            ['id' => $randomq->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']));
723
    }
724
 
725
    /**
726
     * Unit test to make sue it is not possible to remove all slots in a section at once.
727
     */
11 efrain 728
    public function test_cannot_remove_all_slots_in_a_section(): void {
1 efrain 729
        $quizobj = $this->create_test_quiz([
730
            ['TF1', 1, 'truefalse'],
731
            ['TF2', 1, 'truefalse'],
732
            'Heading 2',
733
            ['TF3', 2, 'truefalse'],
734
        ]);
735
        $structure = structure::create_for_quiz($quizobj);
736
 
737
        $structure->remove_slot(1);
738
        $this->expectException(\coding_exception::class);
739
        $structure->remove_slot(2);
740
    }
741
 
11 efrain 742
    public function test_cannot_remove_last_slot_in_a_section(): void {
1 efrain 743
        $quizobj = $this->create_test_quiz([
744
                ['TF1', 1, 'truefalse'],
745
                ['TF2', 1, 'truefalse'],
746
                'Heading 2',
747
                ['TF3', 2, 'truefalse'],
748
        ]);
749
        $structure = structure::create_for_quiz($quizobj);
750
 
751
        $this->expectException(\coding_exception::class);
752
        $structure->remove_slot(3);
753
    }
754
 
11 efrain 755
    public function test_can_remove_last_question_in_a_quiz(): void {
1 efrain 756
        $quizobj = $this->create_test_quiz([
757
                'Heading 1',
758
                ['TF1', 1, 'truefalse'],
759
        ]);
760
        $structure = structure::create_for_quiz($quizobj);
761
 
762
        $structure->remove_slot(1);
763
 
764
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
765
        $cat = $questiongenerator->create_question_category();
766
        $q = $questiongenerator->create_question('truefalse', null,
767
                ['name' => 'TF2', 'category' => $cat->id]);
768
 
769
        quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 0);
770
        $structure = structure::create_for_quiz($quizobj);
771
 
772
        $this->assert_quiz_layout([
773
                'Heading 1',
774
                ['TF2', 1, 'truefalse'],
775
        ], $structure);
776
    }
777
 
11 efrain 778
    public function test_add_question_updates_headings(): void {
1 efrain 779
        $quizobj = $this->create_test_quiz([
780
                ['TF1', 1, 'truefalse'],
781
                'Heading 2',
782
                ['TF2', 2, 'truefalse'],
783
        ]);
784
 
785
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
786
        $cat = $questiongenerator->create_question_category();
787
        $q = $questiongenerator->create_question('truefalse', null,
788
                ['name' => 'TF3', 'category' => $cat->id]);
789
 
790
        quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 1);
791
 
792
        $structure = structure::create_for_quiz($quizobj);
793
        $this->assert_quiz_layout([
794
                ['TF1', 1, 'truefalse'],
795
                ['TF3', 1, 'truefalse'],
796
                'Heading 2',
797
                ['TF2', 2, 'truefalse'],
798
        ], $structure);
799
    }
800
 
11 efrain 801
    public function test_add_question_updates_headings_even_with_one_question_sections(): void {
1 efrain 802
        $quizobj = $this->create_test_quiz([
803
                'Heading 1',
804
                ['TF1', 1, 'truefalse'],
805
                'Heading 2',
806
                ['TF2', 2, 'truefalse'],
807
                'Heading 3',
808
                ['TF3', 3, 'truefalse'],
809
        ]);
810
 
811
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
812
        $cat = $questiongenerator->create_question_category();
813
        $q = $questiongenerator->create_question('truefalse', null,
814
                ['name' => 'TF4', 'category' => $cat->id]);
815
 
816
        quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 1);
817
 
818
        $structure = structure::create_for_quiz($quizobj);
819
        $this->assert_quiz_layout([
820
                'Heading 1',
821
                ['TF1', 1, 'truefalse'],
822
                ['TF4', 1, 'truefalse'],
823
                'Heading 2',
824
                ['TF2', 2, 'truefalse'],
825
                'Heading 3',
826
                ['TF3', 3, 'truefalse'],
827
        ], $structure);
828
    }
829
 
11 efrain 830
    public function test_add_question_at_end_does_not_update_headings(): void {
1 efrain 831
        $quizobj = $this->create_test_quiz([
832
                ['TF1', 1, 'truefalse'],
833
                'Heading 2',
834
                ['TF2', 2, 'truefalse'],
835
        ]);
836
 
837
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
838
        $cat = $questiongenerator->create_question_category();
839
        $q = $questiongenerator->create_question('truefalse', null,
840
                ['name' => 'TF3', 'category' => $cat->id]);
841
 
842
        quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 0);
843
 
844
        $structure = structure::create_for_quiz($quizobj);
845
        $this->assert_quiz_layout([
846
                ['TF1', 1, 'truefalse'],
847
                'Heading 2',
848
                ['TF2', 2, 'truefalse'],
849
                ['TF3', 2, 'truefalse'],
850
        ], $structure);
851
    }
852
 
11 efrain 853
    public function test_remove_page_break(): void {
1 efrain 854
        $quizobj = $this->create_test_quiz([
855
                ['TF1', 1, 'truefalse'],
856
                ['TF2', 2, 'truefalse'],
857
        ]);
858
        $structure = structure::create_for_quiz($quizobj);
859
 
860
        $slotid = $structure->get_question_in_slot(2)->slotid;
861
        $slots = $structure->update_page_break($slotid, repaginate::LINK);
862
 
863
        $structure = structure::create_for_quiz($quizobj);
864
        $this->assert_quiz_layout([
865
                ['TF1', 1, 'truefalse'],
866
                ['TF2', 1, 'truefalse'],
867
        ], $structure);
868
    }
869
 
11 efrain 870
    public function test_add_page_break(): void {
1 efrain 871
        $quizobj = $this->create_test_quiz([
872
                ['TF1', 1, 'truefalse'],
873
                ['TF2', 1, 'truefalse'],
874
        ]);
875
        $structure = structure::create_for_quiz($quizobj);
876
 
877
        $slotid = $structure->get_question_in_slot(2)->slotid;
878
        $slots = $structure->update_page_break($slotid, repaginate::UNLINK);
879
 
880
        $structure = structure::create_for_quiz($quizobj);
881
        $this->assert_quiz_layout([
882
                ['TF1', 1, 'truefalse'],
883
                ['TF2', 2, 'truefalse'],
884
        ], $structure);
885
    }
886
 
11 efrain 887
    public function test_update_question_dependency(): void {
1 efrain 888
        $quizobj = $this->create_test_quiz([
889
                ['TF1', 1, 'truefalse'],
890
                ['TF2', 1, 'truefalse'],
891
        ]);
892
        $structure = structure::create_for_quiz($quizobj);
893
 
894
        // Test adding a dependency.
895
        $slotid = $structure->get_slot_id_for_slot(2);
896
        $structure->update_question_dependency($slotid, true);
897
 
898
        // Having done an update, we need to reload $structure.
899
        $structure = structure::create_for_quiz($quizobj);
900
        $this->assertEquals(1, $structure->is_question_dependent_on_previous_slot(2));
901
 
902
        // Test removing a dependency.
903
        $structure->update_question_dependency($slotid, false);
904
 
905
        // Having done an update, we need to reload $structure.
906
        $structure = structure::create_for_quiz($quizobj);
907
        $this->assertEquals(0, $structure->is_question_dependent_on_previous_slot(2));
908
    }
909
 
1441 ariadna 910
    public function test_update_slot_version(): void {
911
        $this->resetAfterTest();
912
 
913
        $course = $this->getDataGenerator()->create_course();
914
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
915
        $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0,
916
                'grade' => 100.0, 'sumgrades' => 2]);
917
 
918
        get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
919
 
920
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
921
        $cat = $questiongenerator->create_question_category();
922
        $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
923
        $questiongenerator->update_question($numq, null, ['name' => 'Second version of numq']);
924
        quiz_add_quiz_question($numq->id, $quiz);
925
 
926
        $quizobj = quiz_settings::create($quiz->id);
927
        $quizobj->preload_questions();
928
        [$question] = array_values($quizobj->get_questions(null, false));
929
        $structure = $quizobj->get_structure();
930
 
931
        // Updating to a version which exists, should succeed.
932
        $this->assertTrue($structure->update_slot_version($question->slotid, 2));
933
 
934
        // Updating to the same version as the current version should return false.
935
        $this->assertFalse($structure->update_slot_version($question->slotid, 2));
936
 
937
        // Updating to a version which does not exists, should throw exception.
938
        $this->expectException(coding_exception::class);
939
        $this->expectExceptionMessage('Version: 3 does not exist for question bank entry: ' . $question->questionbankentryid);
940
        $structure->update_slot_version($question->slotid, 3);
941
 
942
    }
943
 
1 efrain 944
    public function test_update_slot_grade_item(): void {
945
        $quizobj = $this->create_test_quiz([
946
                ['TF1', 1, 'truefalse'],
947
                ['TF2', 1, 'truefalse'],
948
        ]);
949
        /** @var \mod_quiz_generator $quizgenerator */
950
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
951
        $gradeitem = $quizgenerator->create_grade_item(
952
            ['quizid' => $quizobj->get_quizid(), 'name' => 'Awesomeness!']);
953
         $structure = structure::create_for_quiz($quizobj);
954
 
955
        // Test setting the grade item for a slot.
956
        $slot = $structure->get_slot_by_number(1);
957
        $this->assertTrue($structure->update_slot_grade_item($slot, $gradeitem->id));
958
 
959
        // Having done an update, we need to reload $structure.
960
        $structure = structure::create_for_quiz($quizobj);
961
        $slot = $structure->get_slot_by_number(1);
962
        $this->assertEquals($gradeitem->id, $slot->quizgradeitemid);
963
 
964
        // Test returns false if no change.
965
        $this->assertFalse($structure->update_slot_grade_item($slot, $gradeitem->id));
966
 
967
        // Test unsetting grade item.
968
        $this->assertTrue($structure->update_slot_grade_item($slot, 0));
969
 
970
        // Having done an update, we need to reload $structure.
971
        $structure = structure::create_for_quiz($quizobj);
972
        $slot = $structure->get_slot_by_number(1);
973
        $this->assertEquals(null, $slot->quizgradeitemid);
974
 
975
        // Test returns false if no change.
976
        $this->assertFalse($structure->update_slot_grade_item($slot, null));
977
    }
978
 
11 efrain 979
    public function test_cannot_set_nonnull_slot_grade_item_for_description(): void {
980
        $quizobj = $this->create_test_quiz([
981
                ['Info', 1, 'description'],
982
        ]);
983
        /** @var \mod_quiz_generator $quizgenerator */
984
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
985
        $gradeitem = $quizgenerator->create_grade_item(
986
            ['quizid' => $quizobj->get_quizid(), 'name' => 'Awesomeness!']);
987
        $structure = structure::create_for_quiz($quizobj);
988
 
1441 ariadna 989
        $this->expectException(coding_exception::class);
11 efrain 990
        $structure->update_slot_grade_item($structure->get_slot_by_number(1), $gradeitem->id);
991
    }
992
 
1 efrain 993
    /**
994
     * Test for can_add_random_questions.
995
     */
11 efrain 996
    public function test_can_add_random_questions(): void {
1 efrain 997
        $this->resetAfterTest();
998
 
999
        $quiz = $this->create_test_quiz([]);
1000
        $course = $quiz->get_course();
1001
 
1002
        $generator = $this->getDataGenerator();
1003
        $teacher = $generator->create_and_enrol($course, 'editingteacher');
1441 ariadna 1004
        $nonteacher = $generator->create_and_enrol($course, 'student');
1 efrain 1005
 
1006
        $this->setUser($teacher);
1007
        $structure = structure::create_for_quiz($quiz);
1008
        $this->assertTrue($structure->can_add_random_questions());
1009
 
1441 ariadna 1010
        $this->setUser($nonteacher);
1 efrain 1011
        $structure = structure::create_for_quiz($quiz);
1012
        $this->assertFalse($structure->can_add_random_questions());
1013
    }
1014
 
1015
    /**
1016
     * Test to get the version information for a question to show in the version selection dropdown.
1017
     *
1018
     * @covers ::get_question_version_info
1019
     */
11 efrain 1020
    public function test_get_version_choices_for_slot(): void {
1 efrain 1021
        $this->resetAfterTest();
1022
 
1023
        $quizobj = $this->create_test_quiz([]);
1024
 
1025
        // Create a question with two versions.
1026
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
1027
        $cat = $questiongenerator->create_question_category(['contextid' => $quizobj->get_context()->id]);
1028
        $q = $questiongenerator->create_question('essay', null,
1029
                ['category' => $cat->id, 'name' => 'This is the first version']);
1030
        $questiongenerator->update_question($q, null, ['name' => 'This is the second version']);
1031
        $questiongenerator->update_question($q, null, ['name' => 'This is the third version']);
1032
        quiz_add_quiz_question($q->id, $quizobj->get_quiz());
1033
 
1034
        // Create the quiz object.
1035
        $structure = structure::create_for_quiz($quizobj);
1036
        $versiondata = $structure->get_version_choices_for_slot(1);
1037
        $this->assertEquals(4, count($versiondata));
1038
        $this->assertEquals('Always latest', $versiondata[0]->versionvalue);
1039
        $this->assertEquals('v3 (latest)', $versiondata[1]->versionvalue);
1040
        $this->assertEquals('v2', $versiondata[2]->versionvalue);
1041
        $this->assertEquals('v1', $versiondata[3]->versionvalue);
1042
        $this->assertTrue($versiondata[0]->selected);
1043
        $this->assertFalse($versiondata[1]->selected);
1044
        $this->assertFalse($versiondata[2]->selected);
1045
        $this->assertFalse($versiondata[3]->selected);
1046
    }
1047
 
1048
    /**
1049
     * Test the current user have '...use' capability over the question(s) in a given slot.
1050
     *
1051
     * @covers ::has_use_capability
1052
     */
11 efrain 1053
    public function test_has_use_capability(): void {
1 efrain 1054
        $this->resetAfterTest();
1055
 
1056
        // Create a quiz with question.
1057
        $quizobj = $this->create_test_quiz([]);
1058
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
1059
        $cat = $questiongenerator->create_question_category(['contextid' => $quizobj->get_context()->id]);
1060
        $q = $questiongenerator->create_question('essay', null,
1061
            ['category' => $cat->id, 'name' => 'This is essay question']);
1062
        quiz_add_quiz_question($q->id, $quizobj->get_quiz());
1063
 
1064
        // Create the quiz object.
1065
        $structure = structure::create_for_quiz($quizobj);
1066
        $slots = $structure->get_slots();
1067
 
1068
        // Get slot.
1069
        $slotid = array_pop($slots)->slot;
1070
 
1071
        $course = $quizobj->get_course();
1072
        $generator = $this->getDataGenerator();
1073
        $teacher = $generator->create_and_enrol($course, 'editingteacher');
1074
        $student = $generator->create_and_enrol($course);
1075
 
1076
        $this->setUser($teacher);
1077
        $this->assertTrue($structure->has_use_capability($slotid));
1078
 
1079
        $this->setUser($student);
1080
        $this->assertFalse($structure->has_use_capability($slotid));
1081
    }
1082
}