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