Proyectos de Subversion Moodle

Rev

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