Proyectos de Subversion Moodle

Rev

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