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 availability_completion;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once($CFG->libdir . '/completionlib.php');
23
 
24
/**
25
 * Unit tests for the completion condition.
26
 *
27
 * @package availability_completion
28
 * @copyright 2014 The Open University
29
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
1441 ariadna 31
final class condition_test extends \advanced_testcase {
1 efrain 32
 
33
    /**
34
     * Setup to ensure that fixtures are loaded.
35
     */
36
    public static function setupBeforeClass(): void {
37
        global $CFG;
38
        // Load the mock info class so that it can be used.
39
        require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php');
40
        require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_module.php');
41
        require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_section.php');
42
    }
43
 
44
    /**
45
     * Load required classes.
46
     */
47
    public function setUp(): void {
1441 ariadna 48
        parent::setUp();
1 efrain 49
        condition::wipe_static_cache();
50
    }
51
 
52
    /**
53
     * Tests constructing and using condition as part of tree.
54
     */
11 efrain 55
    public function test_in_tree(): void {
1 efrain 56
        global $USER, $CFG;
57
        $this->resetAfterTest();
58
 
59
        $this->setAdminUser();
60
 
61
        // Create course with completion turned on and a Page.
62
        $CFG->enablecompletion = true;
63
        $CFG->enableavailability = true;
64
        $generator = $this->getDataGenerator();
65
        $course = $generator->create_course(['enablecompletion' => 1]);
66
        $page = $generator->get_plugin_generator('mod_page')->create_instance(
67
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
68
        $selfpage = $generator->get_plugin_generator('mod_page')->create_instance(
69
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
70
 
71
        $modinfo = get_fast_modinfo($course);
72
        $cm = $modinfo->get_cm($page->cmid);
73
        $info = new \core_availability\mock_info($course, $USER->id);
74
 
75
        $structure = (object)[
76
            'op' => '|',
77
            'show' => true,
78
            'c' => [
79
                (object)[
80
                    'type' => 'completion',
81
                    'cm' => (int)$cm->id,
82
                    'e' => COMPLETION_COMPLETE
83
                ]
84
            ]
85
        ];
86
        $tree = new \core_availability\tree($structure);
87
 
88
        // Initial check (user has not completed activity).
89
        $result = $tree->check_available(false, $info, true, $USER->id);
90
        $this->assertFalse($result->is_available());
91
 
92
        // Mark activity complete.
93
        $completion = new \completion_info($course);
94
        $completion->update_state($cm, COMPLETION_COMPLETE);
95
 
96
        // Now it's true!
97
        $result = $tree->check_available(false, $info, true, $USER->id);
98
        $this->assertTrue($result->is_available());
99
    }
100
 
101
    /**
102
     * Tests the constructor including error conditions. Also tests the
103
     * string conversion feature (intended for debugging only).
104
     */
11 efrain 105
    public function test_constructor(): void {
1 efrain 106
        // No parameters.
107
        $structure = new \stdClass();
108
        try {
109
            $cond = new condition($structure);
110
            $this->fail();
111
        } catch (\coding_exception $e) {
112
            $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage());
113
        }
114
 
115
        // Invalid $cm.
116
        $structure->cm = 'hello';
117
        try {
118
            $cond = new condition($structure);
119
            $this->fail();
120
        } catch (\coding_exception $e) {
121
            $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage());
122
        }
123
 
124
        // Missing $e.
125
        $structure->cm = 42;
126
        try {
127
            $cond = new condition($structure);
128
            $this->fail();
129
        } catch (\coding_exception $e) {
130
            $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage());
131
        }
132
 
133
        // Invalid $e.
134
        $structure->e = 99;
135
        try {
136
            $cond = new condition($structure);
137
            $this->fail();
138
        } catch (\coding_exception $e) {
139
            $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage());
140
        }
141
 
142
        // Successful construct & display with all different expected values.
143
        $structure->e = COMPLETION_COMPLETE;
144
        $cond = new condition($structure);
145
        $this->assertEquals('{completion:cm42 COMPLETE}', (string)$cond);
146
 
147
        $structure->e = COMPLETION_COMPLETE_PASS;
148
        $cond = new condition($structure);
149
        $this->assertEquals('{completion:cm42 COMPLETE_PASS}', (string)$cond);
150
 
151
        $structure->e = COMPLETION_COMPLETE_FAIL;
152
        $cond = new condition($structure);
153
        $this->assertEquals('{completion:cm42 COMPLETE_FAIL}', (string)$cond);
154
 
155
        $structure->e = COMPLETION_INCOMPLETE;
156
        $cond = new condition($structure);
157
        $this->assertEquals('{completion:cm42 INCOMPLETE}', (string)$cond);
158
 
159
        // Successful contruct with previous activity.
160
        $structure->cm = condition::OPTION_PREVIOUS;
161
        $cond = new condition($structure);
162
        $this->assertEquals('{completion:cmopprevious INCOMPLETE}', (string)$cond);
163
 
164
    }
165
 
166
    /**
167
     * Tests the save() function.
168
     */
11 efrain 169
    public function test_save(): void {
1 efrain 170
        $structure = (object)['cm' => 42, 'e' => COMPLETION_COMPLETE];
171
        $cond = new condition($structure);
172
        $structure->type = 'completion';
173
        $this->assertEquals($structure, $cond->save());
174
    }
175
 
176
    /**
177
     * Tests the is_available and get_description functions.
178
     */
11 efrain 179
    public function test_usage(): void {
1 efrain 180
        global $CFG, $DB;
181
        require_once($CFG->dirroot . '/mod/assign/locallib.php');
182
        $this->resetAfterTest();
183
 
184
        // Create course with completion turned on.
185
        $CFG->enablecompletion = true;
186
        $CFG->enableavailability = true;
187
        $generator = $this->getDataGenerator();
188
        $course = $generator->create_course(['enablecompletion' => 1]);
189
        $user = $generator->create_user();
190
        $generator->enrol_user($user->id, $course->id);
191
        $this->setUser($user);
192
 
193
        // Create a Page with manual completion for basic checks.
194
        $page = $generator->get_plugin_generator('mod_page')->create_instance(
195
                ['course' => $course->id, 'name' => 'Page!',
196
                'completion' => COMPLETION_TRACKING_MANUAL]);
197
 
198
        // Create an assignment - we need to have something that can be graded
199
        // so as to test the PASS/FAIL states. Set it up to be completed based
200
        // on its grade item.
201
        $assignrow = $this->getDataGenerator()->create_module('assign', [
202
                        'course' => $course->id, 'name' => 'Assign!',
203
                        'completion' => COMPLETION_TRACKING_AUTOMATIC]);
204
        $DB->set_field('course_modules', 'completiongradeitemnumber', 0,
205
                ['id' => $assignrow->cmid]);
206
        // As we manually set the field here, we are going to need to reset the modinfo cache.
207
        rebuild_course_cache($course->id, true);
208
        $assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
209
 
210
        // Get basic details.
211
        $modinfo = get_fast_modinfo($course);
212
        $pagecm = $modinfo->get_cm($page->cmid);
213
        $assigncm = $assign->get_course_module();
214
        $info = new \core_availability\mock_info($course, $user->id);
215
 
216
        // COMPLETE state (false), positive and NOT.
217
        $cond = new condition((object)[
218
            'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE
219
        ]);
220
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
221
        $information = $cond->get_description(false, false, $info);
222
        $information = \core_availability\info::format_info($information, $course);
223
        $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information);
224
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
225
 
226
        // INCOMPLETE state (true).
227
        $cond = new condition((object)[
228
            'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE
229
        ]);
230
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
231
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
232
        $information = $cond->get_description(false, true, $info);
233
        $information = \core_availability\info::format_info($information, $course);
234
        $this->assertMatchesRegularExpression('~Page!.*is marked complete~', $information);
235
 
236
        // Mark page complete.
237
        $completion = new \completion_info($course);
238
        $completion->update_state($pagecm, COMPLETION_COMPLETE);
239
 
240
        // COMPLETE state (true).
241
        $cond = new condition((object)[
242
            'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE
243
        ]);
244
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
245
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
246
        $information = $cond->get_description(false, true, $info);
247
        $information = \core_availability\info::format_info($information, $course);
248
        $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information);
249
 
250
        // INCOMPLETE state (false).
251
        $cond = new condition((object)[
252
            'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE
253
        ]);
254
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
255
        $information = $cond->get_description(false, false, $info);
256
        $information = \core_availability\info::format_info($information, $course);
257
        $this->assertMatchesRegularExpression('~Page!.*is incomplete~', $information);
258
        $this->assertTrue($cond->is_available(true, $info,
259
                true, $user->id));
260
 
261
        // We are going to need the grade item so that we can get pass/fails.
262
        $gradeitem = $assign->get_grade_item();
263
        \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]);
264
        $gradeitem->update();
265
 
266
        // With no grade, it should return true for INCOMPLETE and false for
267
        // the other three.
268
        $cond = new condition((object)[
269
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE
270
        ]);
271
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
272
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
273
 
274
        $cond = new condition((object)[
275
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE
276
        ]);
277
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
278
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
279
 
280
        // Check $information for COMPLETE_PASS and _FAIL as we haven't yet.
281
        $cond = new condition((object)[
282
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS
283
        ]);
284
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
285
        $information = $cond->get_description(false, false, $info);
286
        $information = \core_availability\info::format_info($information, $course);
287
        $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information);
288
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
289
 
290
        $cond = new condition((object)[
291
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL
292
        ]);
293
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
294
        $information = $cond->get_description(false, false, $info);
295
        $information = \core_availability\info::format_info($information, $course);
296
        $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information);
297
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
298
 
299
        // Change the grade to be complete and failed.
300
        self::set_grade($assignrow, $user->id, 40);
301
 
302
        $cond = new condition((object)[
303
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE
304
        ]);
305
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
306
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
307
 
308
        $cond = new condition((object)[
309
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE
310
        ]);
311
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
312
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
313
 
314
        $cond = new condition((object)[
315
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS
316
        ]);
317
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
318
        $information = $cond->get_description(false, false, $info);
319
        $information = \core_availability\info::format_info($information, $course);
320
        $this->assertMatchesRegularExpression('~Assign!.*is complete and passed~', $information);
321
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
322
 
323
        $cond = new condition((object)[
324
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL
325
        ]);
326
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
327
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
328
        $information = $cond->get_description(false, true, $info);
329
        $information = \core_availability\info::format_info($information, $course);
330
        $this->assertMatchesRegularExpression('~Assign!.*is not complete and failed~', $information);
331
 
332
        // Now change it to pass.
333
        self::set_grade($assignrow, $user->id, 60);
334
 
335
        $cond = new condition((object)[
336
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE
337
        ]);
338
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
339
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
340
 
341
        $cond = new condition((object)[
342
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE
343
        ]);
344
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
345
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
346
 
347
        $cond = new condition((object)[
348
                        'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS
349
                    ]);
350
        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
351
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
352
        $information = $cond->get_description(false, true, $info);
353
        $information = \core_availability\info::format_info($information, $course);
354
        $this->assertMatchesRegularExpression('~Assign!.*is not complete and passed~', $information);
355
 
356
        $cond = new condition((object)[
357
            'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL
358
        ]);
359
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
360
        $information = $cond->get_description(false, false, $info);
361
        $information = \core_availability\info::format_info($information, $course);
362
        $this->assertMatchesRegularExpression('~Assign!.*is complete and failed~', $information);
363
        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
364
 
365
        // Simulate deletion of an activity by using an invalid cmid. These
366
        // conditions always fail, regardless of NOT flag or INCOMPLETE.
367
        $cond = new condition((object)[
368
            'cm' => ($assigncm->id + 100), 'e' => COMPLETION_COMPLETE
369
        ]);
370
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
371
        $information = $cond->get_description(false, false, $info);
372
        $information = \core_availability\info::format_info($information, $course);
373
        $this->assertMatchesRegularExpression('~(Missing activity).*is marked complete~', $information);
374
        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
375
        $cond = new condition((object)[
376
            'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE
377
        ]);
378
        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
379
    }
380
 
381
    /**
382
     * Tests the is_available and get_description functions for previous activity option.
383
     *
384
     * @dataProvider previous_activity_data
385
     * @param int $grade the current assign grade (0 for none)
386
     * @param int $condition true for complete, false for incomplete
387
     * @param string $mark activity to mark as complete
388
     * @param string $activity activity name to test
389
     * @param bool $result if it must be available or not
390
     * @param bool $resultnot if it must be available when the condition is inverted
391
     * @param string $description the availabiklity text to check
392
     */
393
    public function test_previous_activity(int $grade, int $condition, string $mark, string $activity,
394
            bool $result, bool $resultnot, string $description): void {
395
        global $CFG, $DB;
396
        require_once($CFG->dirroot . '/mod/assign/locallib.php');
397
        $this->resetAfterTest();
398
 
399
        // Create course with completion turned on.
400
        $CFG->enablecompletion = true;
401
        $CFG->enableavailability = true;
402
        $generator = $this->getDataGenerator();
403
        $course = $generator->create_course(['enablecompletion' => 1]);
404
        $user = $generator->create_user();
405
        $generator->enrol_user($user->id, $course->id);
406
        $this->setUser($user);
407
 
408
        // Page 1 (manual completion).
409
        $page1 = $generator->get_plugin_generator('mod_page')->create_instance(
410
                ['course' => $course->id, 'name' => 'Page1!',
411
                'completion' => COMPLETION_TRACKING_MANUAL]);
412
 
413
        // Page 2 (manual completion).
414
        $page2 = $generator->get_plugin_generator('mod_page')->create_instance(
415
                ['course' => $course->id, 'name' => 'Page2!',
416
                'completion' => COMPLETION_TRACKING_MANUAL]);
417
 
418
        // Page ignored (no completion).
419
        $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance(
420
                ['course' => $course->id, 'name' => 'Page ignored!']);
421
 
422
        // Create an assignment - we need to have something that can be graded
423
        // so as to test the PASS/FAIL states. Set it up to be completed based
424
        // on its grade item.
425
        $assignrow = $this->getDataGenerator()->create_module('assign', [
426
            'course' => $course->id, 'name' => 'Assign!',
427
            'completion' => COMPLETION_TRACKING_AUTOMATIC
428
        ]);
429
        $DB->set_field('course_modules', 'completiongradeitemnumber', 0,
430
                ['id' => $assignrow->cmid]);
431
        $assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
432
 
433
        // Page 3 (manual completion).
434
        $page3 = $generator->get_plugin_generator('mod_page')->create_instance(
435
                ['course' => $course->id, 'name' => 'Page3!',
436
                'completion' => COMPLETION_TRACKING_MANUAL]);
437
 
438
        // Get basic details.
439
        $activities = [];
440
        $modinfo = get_fast_modinfo($course);
441
        $activities['page1'] = $modinfo->get_cm($page1->cmid);
442
        $activities['page2'] = $modinfo->get_cm($page2->cmid);
443
        $activities['assign'] = $assign->get_course_module();
444
        $activities['page3'] = $modinfo->get_cm($page3->cmid);
445
        $prevvalue = condition::OPTION_PREVIOUS;
446
 
447
        // Setup gradings and completion.
448
        if ($grade) {
449
            $gradeitem = $assign->get_grade_item();
450
            \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]);
451
            $gradeitem->update();
452
            self::set_grade($assignrow, $user->id, $grade);
453
        }
454
        if ($mark) {
455
            $completion = new \completion_info($course);
456
            $completion->update_state($activities[$mark], COMPLETION_COMPLETE);
457
        }
458
 
459
        // Set opprevious WITH non existent previous activity.
460
        $info = new \core_availability\mock_info_module($user->id, $activities[$activity]);
461
        $cond = new condition((object)[
462
            'cm' => (int)$prevvalue, 'e' => $condition
463
        ]);
464
 
465
        // Do the checks.
466
        $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id));
467
        $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id));
468
        $information = $cond->get_description(false, false, $info);
469
        $information = \core_availability\info::format_info($information, $course);
470
        $this->assertMatchesRegularExpression($description, $information);
471
    }
472
 
1441 ariadna 473
    public static function previous_activity_data(): array {
1 efrain 474
        // Assign grade, condition, activity to complete, activity to test, result, resultnot, description.
475
        return [
476
            'Missing previous activity complete' => [
477
                0, COMPLETION_COMPLETE, '', 'page1', false, false, '~Missing activity.*is marked complete~'
478
            ],
479
            'Missing previous activity incomplete' => [
480
                0, COMPLETION_INCOMPLETE, '', 'page1', false, false, '~Missing activity.*is incomplete~'
481
            ],
482
            'Previous complete condition with previous activity incompleted' => [
483
                0, COMPLETION_COMPLETE, '', 'page2', false, true, '~Page1!.*is marked complete~'
484
            ],
485
            'Previous incomplete condition with previous activity incompleted' => [
486
                0, COMPLETION_INCOMPLETE, '', 'page2', true, false, '~Page1!.*is incomplete~'
487
            ],
488
            'Previous complete condition with previous activity completed' => [
489
                0, COMPLETION_COMPLETE, 'page1', 'page2', true, false, '~Page1!.*is marked complete~'
490
            ],
491
            'Previous incomplete condition with previous activity completed' => [
492
                0, COMPLETION_INCOMPLETE, 'page1', 'page2', false, true, '~Page1!.*is incomplete~'
493
            ],
494
            // Depenging on page pass fail (pages are not gradable).
495
            'Previous complete pass condition with previous no gradable activity incompleted' => [
496
                0, COMPLETION_COMPLETE_PASS, '', 'page2', false, true, '~Page1!.*is complete and passed~'
497
            ],
498
            'Previous complete fail condition with previous no gradable activity incompleted' => [
499
                0, COMPLETION_COMPLETE_FAIL, '', 'page2', false, true, '~Page1!.*is complete and failed~'
500
            ],
501
            'Previous complete pass condition with previous no gradable activity completed' => [
502
                0, COMPLETION_COMPLETE_PASS, 'page1', 'page2', false, true, '~Page1!.*is complete and passed~'
503
            ],
504
            'Previous complete fail condition with previous no gradable activity completed' => [
505
                0, COMPLETION_COMPLETE_FAIL, 'page1', 'page2', false, true, '~Page1!.*is complete and failed~'
506
            ],
507
            // There's an page without completion between page2 ans assign.
508
            'Previous complete condition with sibling activity incompleted' => [
509
                0, COMPLETION_COMPLETE, '', 'assign', false, true, '~Page2!.*is marked complete~'
510
            ],
511
            'Previous incomplete condition with sibling activity incompleted' => [
512
                0, COMPLETION_INCOMPLETE, '', 'assign', true, false, '~Page2!.*is incomplete~'
513
            ],
514
            'Previous complete condition with sibling activity completed' => [
515
                0, COMPLETION_COMPLETE, 'page2', 'assign', true, false, '~Page2!.*is marked complete~'
516
            ],
517
            'Previous incomplete condition with sibling activity completed' => [
518
                0, COMPLETION_INCOMPLETE, 'page2', 'assign', false, true, '~Page2!.*is incomplete~'
519
            ],
520
            // Depending on assign without grade.
521
            'Previous complete condition with previous without grade' => [
522
                0, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~'
523
            ],
524
            'Previous incomplete condition with previous without grade' => [
525
                0, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~'
526
            ],
527
            'Previous complete pass condition with previous without grade' => [
528
                0, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~'
529
            ],
530
            'Previous complete fail condition with previous without grade' => [
531
                0, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~'
532
            ],
533
            // Depending on assign with grade.
534
            'Previous complete condition with previous fail grade' => [
535
                40, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~',
536
            ],
537
            'Previous incomplete condition with previous fail grade' => [
538
                40, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~',
539
            ],
540
            'Previous complete pass condition with previous fail grade' => [
541
                40, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~'
542
            ],
543
            'Previous complete fail condition with previous fail grade' => [
544
                40, COMPLETION_COMPLETE_FAIL, '', 'page3', true, false, '~Assign!.*is complete and failed~'
545
            ],
546
            'Previous complete condition with previous pass grade' => [
547
                60, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~'
548
            ],
549
            'Previous incomplete condition with previous pass grade' => [
550
                60, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~'
551
            ],
552
            'Previous complete pass condition with previous pass grade' => [
553
                60, COMPLETION_COMPLETE_PASS, '', 'page3', true, false, '~Assign!.*is complete and passed~'
554
            ],
555
            'Previous complete fail condition with previous pass grade' => [
556
                60, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~'
557
            ],
558
        ];
559
    }
560
 
561
    /**
562
     * Tests the is_available and get_description functions for
563
     * previous activity option in course sections.
564
     *
565
     * @dataProvider section_previous_activity_data
566
     * @param int $condition condition value
567
     * @param bool $mark if Page 1 must be mark as completed
568
     * @param string $section section to add the availability
569
     * @param bool $result expected result
570
     * @param bool $resultnot expected negated result
571
     * @param string $description description to match
572
     */
573
    public function test_section_previous_activity(int $condition, bool $mark, string $section,
574
                bool $result, bool $resultnot, string $description): void {
575
        global $CFG, $DB;
576
        require_once($CFG->dirroot . '/mod/assign/locallib.php');
577
        $this->resetAfterTest();
578
 
579
        // Create course with completion turned on.
580
        $CFG->enablecompletion = true;
581
        $CFG->enableavailability = true;
582
        $generator = $this->getDataGenerator();
583
        $course = $generator->create_course(
584
                ['numsections' => 4, 'enablecompletion' => 1],
585
                ['createsections' => true]);
586
        $user = $generator->create_user();
587
        $generator->enrol_user($user->id, $course->id);
588
        $this->setUser($user);
589
 
590
        // Section 1 - page1 (manual completion).
591
        $page1 = $generator->get_plugin_generator('mod_page')->create_instance(
592
                ['course' => $course->id, 'name' => 'Page1!', 'section' => 1,
593
                'completion' => COMPLETION_TRACKING_MANUAL]);
594
 
595
        // Section 1 - page ignored 1 (no completion).
596
        $pagenocompletion1 = $generator->get_plugin_generator('mod_page')->create_instance(
597
                ['course' => $course, 'name' => 'Page ignored!', 'section' => 1]);
598
 
599
        // Section 2 - page ignored 2 (no completion).
600
        $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance(
601
                ['course' => $course, 'name' => 'Page ignored!', 'section' => 2]);
602
 
603
        // Section 3 - page2 (manual completion).
604
        $page2 = $generator->get_plugin_generator('mod_page')->create_instance(
605
                ['course' => $course->id, 'name' => 'Page2!', 'section' => 3,
606
                'completion' => COMPLETION_TRACKING_MANUAL]);
607
 
608
        // Section 4 is empty.
609
 
610
        // Get basic details.
611
        get_fast_modinfo(0, 0, true);
612
        $modinfo = get_fast_modinfo($course);
613
        $sections['section1'] = $modinfo->get_section_info(1);
614
        $sections['section2'] = $modinfo->get_section_info(2);
615
        $sections['section3'] = $modinfo->get_section_info(3);
616
        $sections['section4'] = $modinfo->get_section_info(4);
617
        $page1cm = $modinfo->get_cm($page1->cmid);
618
        $prevvalue = condition::OPTION_PREVIOUS;
619
 
620
        if ($mark) {
621
            // Mark page1 complete.
622
            $completion = new \completion_info($course);
623
            $completion->update_state($page1cm, COMPLETION_COMPLETE);
624
        }
625
 
626
        $info = new \core_availability\mock_info_section($user->id, $sections[$section]);
627
        $cond = new condition((object)[
628
            'cm' => (int)$prevvalue, 'e' => $condition
629
        ]);
630
        $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id));
631
        $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id));
632
        $information = $cond->get_description(false, false, $info);
633
        $information = \core_availability\info::format_info($information, $course);
634
        $this->assertMatchesRegularExpression($description, $information);
635
 
636
    }
637
 
1441 ariadna 638
    public static function section_previous_activity_data(): array {
1 efrain 639
        return [
640
            // Condition, Activity completion, section to test, result, resultnot, description.
641
            'Completion complete Section with no previous activity' => [
642
                COMPLETION_COMPLETE, false, 'section1', false, false, '~Missing activity.*is marked complete~'
643
            ],
644
            'Completion incomplete Section with no previous activity' => [
645
                COMPLETION_INCOMPLETE, false, 'section1', false, false, '~Missing activity.*is incomplete~'
646
            ],
647
            // Section 2 depending on section 1 -> Page 1 (no grading).
648
            'Completion complete Section with previous activity incompleted' => [
649
                COMPLETION_COMPLETE, false, 'section2', false, true, '~Page1!.*is marked complete~'
650
            ],
651
            'Completion incomplete Section with previous activity incompleted' => [
652
                COMPLETION_INCOMPLETE, false, 'section2', true, false, '~Page1!.*is incomplete~'
653
            ],
654
            'Completion complete Section with previous activity completed' => [
655
                COMPLETION_COMPLETE, true, 'section2', true, false, '~Page1!.*is marked complete~'
656
            ],
657
            'Completion incomplete Section with previous activity completed' => [
658
                COMPLETION_INCOMPLETE, true, 'section2', false, true, '~Page1!.*is incomplete~'
659
            ],
660
            // Section 3 depending on section 1 -> Page 1 (no grading).
661
            'Completion complete Section ignoring empty sections and activity incompleted' => [
662
                COMPLETION_COMPLETE, false, 'section3', false, true, '~Page1!.*is marked complete~'
663
            ],
664
            'Completion incomplete Section ignoring empty sections and activity incompleted' => [
665
                COMPLETION_INCOMPLETE, false, 'section3', true, false, '~Page1!.*is incomplete~'
666
            ],
667
            'Completion complete Section ignoring empty sections and activity completed' => [
668
                COMPLETION_COMPLETE, true, 'section3', true, false, '~Page1!.*is marked complete~'
669
            ],
670
            'Completion incomplete Section ignoring empty sections and activity completed' => [
671
                COMPLETION_INCOMPLETE, true, 'section3', false, true, '~Page1!.*is incomplete~'
672
            ],
673
            // Section 4 depending on section 3 -> Page 2 (no grading).
674
            'Completion complete Last section with previous activity incompleted' => [
675
                COMPLETION_COMPLETE, false, 'section4', false, true, '~Page2!.*is marked complete~'
676
            ],
677
            'Completion incomplete Last section with previous activity incompleted' => [
678
                COMPLETION_INCOMPLETE, false, 'section4', true, false, '~Page2!.*is incomplete~'
679
            ],
680
            'Completion complete Last section with previous activity completed' => [
681
                COMPLETION_COMPLETE, true, 'section4', false, true, '~Page2!.*is marked complete~'
682
            ],
683
            'Completion incomplete Last section with previous activity completed' => [
684
                COMPLETION_INCOMPLETE, true, 'section4', true, false, '~Page2!.*is incomplete~'
685
            ],
686
        ];
687
    }
688
 
689
    /**
690
     * Tests completion_value_used static function.
691
     */
11 efrain 692
    public function test_completion_value_used(): void {
1 efrain 693
        global $CFG, $DB;
694
        $this->resetAfterTest();
695
        $prevvalue = condition::OPTION_PREVIOUS;
696
 
697
        // Create course with completion turned on and some sections.
698
        $CFG->enablecompletion = true;
699
        $CFG->enableavailability = true;
700
        $generator = $this->getDataGenerator();
701
        $course = $generator->create_course(
702
                ['numsections' => 1, 'enablecompletion' => 1],
703
                ['createsections' => true]);
704
 
705
        // Create six pages with manual completion.
706
        $page1 = $generator->get_plugin_generator('mod_page')->create_instance(
707
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
708
        $page2 = $generator->get_plugin_generator('mod_page')->create_instance(
709
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
710
        $page3 = $generator->get_plugin_generator('mod_page')->create_instance(
711
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
712
        $page4 = $generator->get_plugin_generator('mod_page')->create_instance(
713
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
714
        $page5 = $generator->get_plugin_generator('mod_page')->create_instance(
715
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
716
        $page6 = $generator->get_plugin_generator('mod_page')->create_instance(
717
                ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]);
718
 
719
        // Set up page3 to depend on page1, and section1 to depend on page2.
720
        $DB->set_field('course_modules', 'availability',
721
                '{"op":"|","show":true,"c":[' .
722
                '{"type":"completion","e":1,"cm":' . $page1->cmid . '}]}',
723
                ['id' => $page3->cmid]);
724
        $DB->set_field('course_sections', 'availability',
725
                '{"op":"|","show":true,"c":[' .
726
                '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}',
727
                ['course' => $course->id, 'section' => 1]);
728
        // Set up page5 and page6 to depend on previous activity.
729
        $DB->set_field('course_modules', 'availability',
730
                '{"op":"|","show":true,"c":[' .
731
                '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}',
732
                ['id' => $page5->cmid]);
733
        $DB->set_field('course_modules', 'availability',
734
                '{"op":"|","show":true,"c":[' .
735
                '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}',
736
                ['id' => $page6->cmid]);
737
 
738
        // Check 1: nothing depends on page3 and page6 but something does on the others.
739
        $this->assertTrue(condition::completion_value_used(
740
                $course, $page1->cmid));
741
        $this->assertTrue(condition::completion_value_used(
742
                $course, $page2->cmid));
743
        $this->assertFalse(condition::completion_value_used(
744
                $course, $page3->cmid));
745
        $this->assertTrue(condition::completion_value_used(
746
                $course, $page4->cmid));
747
        $this->assertTrue(condition::completion_value_used(
748
                $course, $page5->cmid));
749
        $this->assertFalse(condition::completion_value_used(
750
                $course, $page6->cmid));
751
    }
752
 
753
    /**
754
     * Updates the grade of a user in the given assign module instance.
755
     *
756
     * @param \stdClass $assignrow Assignment row from database
757
     * @param int $userid User id
758
     * @param float $grade Grade
759
     */
760
    protected static function set_grade($assignrow, $userid, $grade) {
761
        $grades = [];
762
        $grades[$userid] = (object)[
763
            'rawgrade' => $grade, 'userid' => $userid
764
        ];
765
        $assignrow->cmidnumber = null;
766
        assign_grade_item_update($assignrow, $grades);
767
    }
768
 
769
    /**
770
     * Tests the update_dependency_id() function.
771
     */
11 efrain 772
    public function test_update_dependency_id(): void {
1 efrain 773
        $cond = new condition((object)[
774
            'cm' => 42, 'e' => COMPLETION_COMPLETE, 'selfid' => 43
775
        ]);
776
        $this->assertFalse($cond->update_dependency_id('frogs', 42, 540));
777
        $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34));
778
        $this->assertTrue($cond->update_dependency_id('course_modules', 42, 456));
779
        $after = $cond->save();
780
        $this->assertEquals(456, $after->cm);
781
 
782
        // Test selfid updating.
783
        $cond = new condition((object)[
784
            'cm' => 42, 'e' => COMPLETION_COMPLETE
785
        ]);
786
        $this->assertFalse($cond->update_dependency_id('frogs', 43, 540));
787
        $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34));
788
        $after = $cond->save();
789
        $this->assertEquals(42, $after->cm);
790
 
791
        // Test on previous activity.
792
        $cond = new condition((object)[
793
            'cm' => condition::OPTION_PREVIOUS,
794
            'e' => COMPLETION_COMPLETE
795
        ]);
796
        $this->assertFalse($cond->update_dependency_id('frogs', 43, 80));
797
        $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34));
798
        $after = $cond->save();
799
        $this->assertEquals(condition::OPTION_PREVIOUS, $after->cm);
800
    }
801
}