2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Contains unit tests for core_completion/cm_completion_details.
20 * @package core_completion
21 * @copyright 2021 Jun Pataleta <jun@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 declare(strict_types
= 1);
27 namespace core_completion
;
29 use advanced_testcase
;
33 defined('MOODLE_INTERNAL') ||
die();
36 require_once($CFG->libdir
. '/completionlib.php');
39 * Class for unit testing core_completion/cm_completion_details.
41 * @package core_completion
42 * @copyright 2021 Jun Pataleta <jun@moodle.com>
43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 * @coversDefaultClass \core_completion\cm_completion_details
46 class cm_completion_details_test
extends advanced_testcase
{
48 /** @var completion_info A completion object. */
49 protected $completioninfo = null;
52 * Fetches a mocked cm_completion_details instance.
54 * @param int|null $completion The completion tracking mode for the module.
55 * @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
56 * @param object $mockcompletiondata Mock data to be returned by get_data.
57 * @param string $modname The modname to set in the cm if a specific one is required.
58 * @return cm_completion_details
60 protected function setup_data(?
int $completion, array $completionoptions = [],
61 object $mockcompletiondata = null, $modname = 'somenonexistentmod'): cm_completion_details
{
62 if (is_null($completion)) {
63 $completion = COMPLETION_TRACKING_AUTOMATIC
;
66 // Mock a completion_info instance so we can simply mock the returns of completion_info::get_data() later.
67 $this->completioninfo
= $this->getMockBuilder(completion_info
::class)
68 ->disableOriginalConstructor()
71 // Mock return of completion_info's is_enabled() method to match the expected completion tracking for the module.
72 $this->completioninfo
->expects($this->any())
73 ->method('is_enabled')
74 ->willReturn($completion);
76 if (!empty($mockcompletiondata)) {
77 $this->completioninfo
->expects($this->any())
79 ->willReturn($mockcompletiondata);
82 // Build a mock cm_info instance.
83 $mockcminfo = $this->getMockBuilder(cm_info
::class)
84 ->disableOriginalConstructor()
85 ->onlyMethods(['__get'])
88 // Mock the return of the magic getter method when fetching the cm_info object's customdata and instance values.
89 $mockcminfo->expects($this->any())
91 ->will($this->returnValueMap([
92 ['completion', $completion],
94 ['modname', $modname],
95 ['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED
],
96 ['completiongradeitemnumber', $completionoptions['completionusegrade'] ??
null],
97 ['completionpassgrade', $completionoptions['completionpassgrade'] ??
null],
100 return new cm_completion_details($this->completioninfo
, $mockcminfo, 2);
104 * Provides data for test_has_completion().
108 public function has_completion_provider(): array {
111 COMPLETION_TRACKING_AUTOMATIC
, true
114 COMPLETION_TRACKING_MANUAL
, true
117 COMPLETION_TRACKING_NONE
, false
123 * Test for has_completion().
125 * @covers ::has_completion
126 * @dataProvider has_completion_provider
127 * @param int $completion The completion tracking mode.
128 * @param bool $expectedresult Expected result.
130 public function test_has_completion(int $completion, bool $expectedresult) {
131 $cmcompletion = $this->setup_data($completion);
133 $this->assertEquals($expectedresult, $cmcompletion->has_completion());
137 * Provides data for test_is_automatic().
141 public function is_automatic_provider(): array {
144 COMPLETION_TRACKING_AUTOMATIC
, true
147 COMPLETION_TRACKING_MANUAL
, false
150 COMPLETION_TRACKING_NONE
, false
156 * Test for is_available().
158 * @covers ::is_automatic
159 * @dataProvider is_automatic_provider
160 * @param int $completion The completion tracking mode.
161 * @param bool $expectedresult Expected result.
163 public function test_is_automatic(int $completion, bool $expectedresult) {
164 $cmcompletion = $this->setup_data($completion);
166 $this->assertEquals($expectedresult, $cmcompletion->is_automatic());
170 * Provides data for test_is_manual().
174 public function is_manual_provider(): array {
177 COMPLETION_TRACKING_AUTOMATIC
, false
180 COMPLETION_TRACKING_MANUAL
, true
183 COMPLETION_TRACKING_NONE
, false
189 * Test for is_manual().
191 * @covers ::is_manual
192 * @dataProvider is_manual_provider
193 * @param int $completion The completion tracking mode.
194 * @param bool $expectedresult Expected result.
196 public function test_is_manual(int $completion, bool $expectedresult) {
197 $cmcompletion = $this->setup_data($completion);
199 $this->assertEquals($expectedresult, $cmcompletion->is_manual());
203 * Data provider for test_get_overall_completion().
206 public function overall_completion_provider(): array {
208 'Complete' => [COMPLETION_COMPLETE
],
209 'Incomplete' => [COMPLETION_INCOMPLETE
],
214 * Test for get_overall_completion().
216 * @covers ::get_overall_completion
217 * @dataProvider overall_completion_provider
220 public function test_get_overall_completion(int $state) {
221 $completiondata = (object)['completionstate' => $state];
222 $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC
, [], $completiondata);
223 $this->assertEquals($state, $cmcompletion->get_overall_completion());
227 * Data provider for test_is_overall_complete().
230 public static function is_overall_complete_provider(): array {
232 'Automatic, require view, not viewed' => [
234 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
235 'completionstate' => COMPLETION_INCOMPLETE
,
236 'completionview' => COMPLETION_INCOMPLETE
,
237 'completiongrade' => null,
238 'completionpassgrade' => null,
240 'Automatic, require view, viewed' => [
242 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
243 'completionstate' => COMPLETION_COMPLETE
,
244 'completionview' => COMPLETION_COMPLETE
,
245 'completiongrade' => null,
246 'completionpassgrade' => null,
248 'Automatic, require grade, not graded' => [
250 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
251 'completionstate' => COMPLETION_INCOMPLETE
,
252 'completionview' => null,
253 'completiongrade' => COMPLETION_INCOMPLETE
,
254 'completionpassgrade' => null,
256 'Automatic, require grade, graded with fail' => [
258 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
259 'completionstate' => COMPLETION_COMPLETE_FAIL
,
260 'completionview' => null,
261 'completiongrade' => COMPLETION_COMPLETE_FAIL
,
262 'completionpassgrade' => null,
264 'Automatic, require grade, graded with passing' => [
266 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
267 'completionstate' => COMPLETION_COMPLETE_PASS
,
268 'completionview' => null,
269 'completiongrade' => COMPLETION_COMPLETE_PASS
,
270 'completionpassgrade' => null,
272 'Automatic, require passgrade, not graded' => [
274 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
275 'completionstate' => COMPLETION_INCOMPLETE
,
276 'completionview' => null,
277 'completiongrade' => null,
278 'completionpassgrade' => COMPLETION_INCOMPLETE
,
280 'Automatic, require passgrade, graded with fail' => [
282 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
283 'completionstate' => COMPLETION_COMPLETE_FAIL
,
284 'completionview' => null,
285 'completiongrade' => null,
286 'completionpassgrade' => COMPLETION_COMPLETE_FAIL
,
288 'Automatic, require passgrade, graded with passing' => [
290 'completion' => COMPLETION_TRACKING_AUTOMATIC
,
291 'completionstate' => COMPLETION_COMPLETE_PASS
,
292 'completionview' => null,
293 'completiongrade' => null,
294 'completionpassgrade' => COMPLETION_COMPLETE_PASS
,
296 'Manual, incomplete' => [
298 'completion' => COMPLETION_TRACKING_MANUAL
,
299 'completionstate' => COMPLETION_INCOMPLETE
,
301 'Manual, complete' => [
303 'completion' => COMPLETION_TRACKING_MANUAL
,
304 'completionstate' => COMPLETION_COMPLETE
,
306 'None, incomplete' => [
308 'completion' => COMPLETION_TRACKING_NONE
,
309 'completionstate' => COMPLETION_INCOMPLETE
,
311 'None, complete' => [
313 'completion' => COMPLETION_TRACKING_NONE
,
314 'completionstate' => COMPLETION_COMPLETE
,
320 * Test for is_overall_complete().
322 * @covers ::is_overall_complete
323 * @dataProvider is_overall_complete_provider
324 * @param bool $expected Expected result returned by is_overall_complete().
325 * @param int $completion The completion tracking mode.
326 * @param int $completionstate The overall completion state.
327 * @param int|null $completionview Completion status of the "view" completion condition.
328 * @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
329 * @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
331 public function test_is_overall_complete(
334 int $completionstate,
335 ?
int $completionview = null,
336 ?
int $completiongrade = null,
337 ?
int $completionpassgrade = null,
340 $getdatareturn = (object)[
341 'completionstate' => $completionstate,
342 'viewed' => $completionview,
343 'completiongrade' => $completiongrade,
344 'passgrade' => $completionpassgrade,
347 if (!is_null($completionview)) {
348 $options['completionview'] = true;
350 if (!is_null($completiongrade)) {
351 $options['completionusegrade'] = true;
353 if (!is_null($completionpassgrade)) {
354 $options['completionpassgrade'] = true;
357 $cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
358 $this->assertEquals($expected, $cmcompletion->is_overall_complete());
362 * Data provider for test_get_details().
365 public function get_details_provider() {
367 'No completion tracking' => [
368 COMPLETION_TRACKING_NONE
, null, null, null, []
370 'Manual completion tracking' => [
371 COMPLETION_TRACKING_MANUAL
, null, null, null, []
373 'Automatic, require view, not viewed' => [
374 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_INCOMPLETE
, null, null, [
375 'completionview' => (object)[
376 'status' => COMPLETION_INCOMPLETE
,
377 'description' => get_string('detail_desc:view', 'completion'),
381 'Automatic, require view, viewed' => [
382 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_COMPLETE
, null, null, [
383 'completionview' => (object)[
384 'status' => COMPLETION_COMPLETE
,
385 'description' => get_string('detail_desc:view', 'completion'),
389 'Automatic, require grade, incomplete' => [
390 COMPLETION_TRACKING_AUTOMATIC
, null, COMPLETION_INCOMPLETE
, null, [
391 'completionusegrade' => (object)[
392 'status' => COMPLETION_INCOMPLETE
,
393 'description' => get_string('detail_desc:receivegrade', 'completion'),
397 'Automatic, require grade, complete' => [
398 COMPLETION_TRACKING_AUTOMATIC
, null, COMPLETION_COMPLETE
, null, [
399 'completionusegrade' => (object)[
400 'status' => COMPLETION_COMPLETE
,
401 'description' => get_string('detail_desc:receivegrade', 'completion'),
405 'Automatic, require view (complete) and grade (incomplete)' => [
406 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_COMPLETE
, COMPLETION_INCOMPLETE
, null, [
407 'completionview' => (object)[
408 'status' => COMPLETION_COMPLETE
,
409 'description' => get_string('detail_desc:view', 'completion'),
411 'completionusegrade' => (object)[
412 'status' => COMPLETION_INCOMPLETE
,
413 'description' => get_string('detail_desc:receivegrade', 'completion'),
417 'Automatic, require view (incomplete) and grade (complete)' => [
418 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_INCOMPLETE
, COMPLETION_COMPLETE
, null, [
419 'completionview' => (object)[
420 'status' => COMPLETION_INCOMPLETE
,
421 'description' => get_string('detail_desc:view', 'completion'),
423 'completionusegrade' => (object)[
424 'status' => COMPLETION_COMPLETE
,
425 'description' => get_string('detail_desc:receivegrade', 'completion'),
429 'Automatic, require grade, require pass grade, complete' => [
430 COMPLETION_TRACKING_AUTOMATIC
, null, COMPLETION_COMPLETE
, COMPLETION_COMPLETE
, [
431 'completionusegrade' => (object)[
432 'status' => COMPLETION_COMPLETE
,
433 'description' => get_string('detail_desc:receivegrade', 'completion'),
435 'completionpassgrade' => (object)[
436 'status' => COMPLETION_COMPLETE
,
437 'description' => get_string('detail_desc:receivepassgrade', 'completion'),
441 'Automatic, require grade, require pass grade, incomplete' => [
442 COMPLETION_TRACKING_AUTOMATIC
, null, COMPLETION_COMPLETE
, COMPLETION_INCOMPLETE
, [
443 'completionusegrade' => (object)[
444 'status' => COMPLETION_COMPLETE
,
445 'description' => get_string('detail_desc:receivegrade', 'completion'),
447 'completionpassgrade' => (object)[
448 'status' => COMPLETION_INCOMPLETE
,
449 'description' => get_string('detail_desc:receivepassgrade', 'completion'),
453 'Automatic, require view (complete), require grade(complete), require pass grade(complete)' => [
454 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_COMPLETE
, COMPLETION_COMPLETE
, COMPLETION_COMPLETE
, [
455 'completionview' => (object)[
456 'status' => COMPLETION_COMPLETE
,
457 'description' => get_string('detail_desc:view', 'completion'),
459 'completionusegrade' => (object)[
460 'status' => COMPLETION_COMPLETE
,
461 'description' => get_string('detail_desc:receivegrade', 'completion'),
463 'completionpassgrade' => (object)[
464 'status' => COMPLETION_COMPLETE
,
465 'description' => get_string('detail_desc:receivepassgrade', 'completion'),
469 'Automatic, require view (incomplete), require grade(complete), require pass grade(complete)' => [
470 COMPLETION_TRACKING_AUTOMATIC
, COMPLETION_INCOMPLETE
, COMPLETION_COMPLETE
, COMPLETION_COMPLETE
, [
471 'completionview' => (object)[
472 'status' => COMPLETION_INCOMPLETE
,
473 'description' => get_string('detail_desc:view', 'completion'),
475 'completionusegrade' => (object)[
476 'status' => COMPLETION_COMPLETE
,
477 'description' => get_string('detail_desc:receivegrade', 'completion'),
479 'completionpassgrade' => (object)[
480 'status' => COMPLETION_COMPLETE
,
481 'description' => get_string('detail_desc:receivepassgrade', 'completion'),
489 * Test for \core_completion\cm_completion_details::get_details().
491 * @covers ::get_details
492 * @dataProvider get_details_provider
493 * @param int $completion The completion tracking mode.
494 * @param int|null $completionview Completion status of the "view" completion condition.
495 * @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
496 * @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
497 * @param array $expecteddetails Expected completion details returned by get_details().
499 public function test_get_details(int $completion, ?
int $completionview,
500 ?
int $completiongrade, ?
int $completionpassgrade, array $expecteddetails) {
502 $getdatareturn = (object)[
503 'viewed' => $completionview,
504 'completiongrade' => $completiongrade,
505 'passgrade' => $completionpassgrade,
508 if (!is_null($completionview)) {
509 $options['completionview'] = true;
511 if (!is_null($completiongrade)) {
512 $options['completionusegrade'] = true;
514 if (!is_null($completionpassgrade)) {
515 $options['completionpassgrade'] = true;
518 $cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
519 $this->assertEquals($expecteddetails, $cmcompletion->get_details());
523 * Data provider for test_get_details_custom_order().
526 public function get_details_custom_order_provider() {
528 'Custom and view/grade standard conditions, view first and grade last' => [
532 'completionsubmit' => true,
535 ['completionview', 'completionsubmit', 'completionusegrade'],
537 'Custom and view/grade standard conditions, grade not last' => [
541 'completionminattempts' => 2,
542 'completionusegrade' => 50,
543 'completionpassorattemptsexhausted' => 1,
546 ['completionview', 'completionminattempts', 'completionusegrade', 'completionpassorattemptsexhausted'],
548 'Custom and grade standard conditions only, no view condition' => [
552 'completionsubmit' => true,
555 ['completionsubmit', 'completionusegrade'],
557 'Custom and view standard conditions only, no grade condition' => [
561 'completionsubmit' => true
564 ['completionview', 'completionsubmit'],
566 'View and grade conditions only, activity with no custom conditions' => [
570 'completionview' => true,
571 'completionusegrade' => true
574 ['completionview', 'completionusegrade'],
576 'View condition only, activity with no custom conditions' => [
580 'completionview' => true,
589 * Test custom sort order is functioning in \core_completion\cm_completion_details::get_details().
591 * @covers ::get_details
592 * @dataProvider get_details_custom_order_provider
593 * @param bool $completionview Completion status of the "view" completion condition.
594 * @param bool $completiongrade Completion status of the "must receive grade" completion condition.
595 * @param array $customcompletionrules Custom completion requirements, along with their values.
596 * @param string $modname The name of the module having data fetched.
597 * @param array $expectedorder The expected order of completion conditions returned about the module.
599 public function test_get_details_custom_order(bool $completionview, bool $completiongrade, array $customcompletionrules,
600 string $modname, array $expectedorder) {
602 $options['customcompletion'] = [];
603 $customcompletiondata = [];
605 if ($completionview) {
606 $options['completionview'] = true;
609 if ($completiongrade) {
610 $options['completionusegrade'] = true;
613 // Set up the completion rules for the completion info.
614 foreach ($customcompletionrules as $customtype => $isenabled) {
615 $customcompletiondata[$customtype] = COMPLETION_COMPLETE
;
618 $getdatareturn = (object)[
619 'viewed' => $completionview ? COMPLETION_COMPLETE
: COMPLETION_INCOMPLETE
,
620 'completiongrade' => $completiongrade ? COMPLETION_COMPLETE
: COMPLETION_INCOMPLETE
,
621 'customcompletion' => $customcompletiondata,
624 $cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC
, $options, $getdatareturn, $modname);
626 $this->completioninfo
->expects($this->any())
628 ->willReturn($getdatareturn);
630 $fetcheddetails = $cmcompletion->get_details();
632 // Check the expected number of items are returned, and sorted in the correct order.
633 $this->assertCount(count($expectedorder), $fetcheddetails);
634 $this->assertTrue((array_keys($fetcheddetails) === $expectedorder));