first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,199 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types = 1);
namespace core_completion;
use advanced_testcase;
use coding_exception;
use moodle_exception;
use PHPUnit\Framework\MockObject\MockObject;
/**
* Class for unit testing core_completion/activity_custom_completion.
*
* @package core_completion
* @copyright 2021 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_custom_completion_test extends advanced_testcase {
/**
* Fetches a mocked activity_custom_completion instance.
*
* @param string[] $methods List of methods to mock.
* @return activity_custom_completion|MockObject
*/
protected function setup_mock(array $methods) {
return $this->getMockBuilder(activity_custom_completion::class)
->disableOriginalConstructor()
->onlyMethods($methods)
->getMockForAbstractClass();
}
/**
* Data provider for test_get_overall_completion_state().
*/
public function overall_completion_state_provider(): array {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
return [
'First incomplete, second complete' => [
['completionsubmit', 'completioncreate'],
[COMPLETION_INCOMPLETE, COMPLETION_COMPLETE],
1,
COMPLETION_INCOMPLETE,
],
'First complete, second incomplete' => [
['completionsubmit', 'completioncreate'],
[COMPLETION_COMPLETE, COMPLETION_INCOMPLETE],
2,
COMPLETION_INCOMPLETE,
],
'First complete, second failed' => [
['completionsubmit', 'completioncreate'],
[COMPLETION_COMPLETE, COMPLETION_COMPLETE_FAIL],
2,
COMPLETION_COMPLETE_FAIL,
],
'First complete, second incomplete, third failed' => [
['completionsubmit', 'completioncreate'],
[COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE_FAIL],
2,
COMPLETION_INCOMPLETE,
],
'All complete' => [
['completionsubmit', 'completioncreate'],
[COMPLETION_COMPLETE, COMPLETION_COMPLETE],
2,
COMPLETION_COMPLETE,
],
'No rules' => [
[],
[],
0,
COMPLETION_COMPLETE,
],
];
}
/**
* Test for \core_completion\activity_custom_completion::get_overall_completion_state().
*
* @dataProvider overall_completion_state_provider
* @param string[] $rules The custom completion rules.
* @param int[] $rulestates The completion states of these custom completion rules.
* @param int $invokecount Expected invoke count of get_state().
* @param int $state The expected overall completion state
*/
public function test_get_overall_completion_state(array $rules, array $rulestates, int $invokecount, int $state): void {
$stub = $this->setup_mock([
'get_available_custom_rules',
'get_state',
]);
// Mock activity_custom_completion's get_available_custom_rules() method.
$stub->expects($this->once())
->method('get_available_custom_rules')
->willReturn($rules);
// Mock activity_custom_completion's get_state() method.
if ($invokecount > 0) {
$stub->expects($this->exactly($invokecount))
->method('get_state')
->withConsecutive(
[$rules[0]],
[$rules[1]]
)
->willReturn($rulestates[0], $rulestates[1]);
} else {
$stub->expects($this->never())
->method('get_state');
}
$this->assertEquals($state, $stub->get_overall_completion_state());
}
/**
* Data provider for test_validate_rule().
*
* @return array[]
*/
public function validate_rule_provider() {
return [
'Not defined' => [
false, true, coding_exception::class
],
'Not available' => [
true, false, moodle_exception::class
],
'Defined and available' => [
true, true, null
],
];
}
/**
* Test for validate_rule()
*
* @dataProvider validate_rule_provider
* @param bool $defined is_defined()'s mocked return value.
* @param bool $available is_available()'s mocked return value.
* @param string|null $expectedexception Expected expectation class name.
*/
public function test_validate_rule(bool $defined, bool $available, ?string $expectedexception): void {
$stub = $this->setup_mock([
'is_defined',
'is_available'
]);
// Mock activity_custom_completion's is_defined() method.
$stub->expects($this->any())
->method('is_defined')
->willReturn($defined);
// Mock activity_custom_completion's is_available() method.
$stub->expects($this->any())
->method('is_available')
->willReturn($available);
if ($expectedexception) {
$this->expectException($expectedexception);
}
$stub->validate_rule('customcompletionrule');
}
/**
* Test for is_available().
*/
public function test_is_available(): void {
$stub = $this->setup_mock([
'get_available_custom_rules',
]);
// Mock activity_custom_completion's get_available_custom_rules() method.
$stub->expects($this->any())
->method('get_available_custom_rules')
->willReturn(['rule1', 'rule2']);
// Rule is available.
$this->assertTrue($stub->is_available('rule1'));
// Rule is not available.
$this->assertFalse($stub->is_available('rule'));
}
}
+389
View File
@@ -0,0 +1,389 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
/**
* Test completion API.
*
* @package core_completion
* @category test
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api_test extends \advanced_testcase {
/**
* Test setup.
*/
public function setUp(): void {
$this->resetAfterTest();
}
public function test_update_completion_date_event(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Create the completion event.
$CFG->enablecompletion = true;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Check that there is now an event in the database.
$events = $DB->get_records('event');
$this->assertCount(1, $events);
// Get the event.
$event = reset($events);
// Confirm the event is correct.
$this->assertEquals('assign', $event->modulename);
$this->assertEquals($assign->id, $event->instance);
$this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type);
$this->assertEquals(\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED, $event->eventtype);
$this->assertEquals($time, $event->timestart);
$this->assertEquals($time, $event->timesort);
require_once($CFG->dirroot . '/course/lib.php');
// Delete the module.
course_delete_module($assign->cmid);
// Check we don't get a failure when called on a deleted module.
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', null, $time);
}
public function test_update_completion_date_event_update(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Create the event.
$CFG->enablecompletion = true;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Call it again, but this time with a different time.
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time + DAYSECS);
// Check that there is still only one event in the database.
$events = $DB->get_records('event');
$this->assertCount(1, $events);
// Get the event.
$event = reset($events);
// Confirm that the event has been updated.
$this->assertEquals('assign', $event->modulename);
$this->assertEquals($assign->id, $event->instance);
$this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type);
$this->assertEquals(\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED, $event->eventtype);
$this->assertEquals($time + DAYSECS, $event->timestart);
$this->assertEquals($time + DAYSECS, $event->timesort);
}
public function test_update_completion_date_event_delete(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Create the event.
$CFG->enablecompletion = true;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Call it again, but the time specified as null.
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, null);
// Check that there is no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
}
public function test_update_completion_date_event_completion_disabled(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Try and create the completion event with completion disabled.
$CFG->enablecompletion = false;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Check that there is no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
}
public function test_update_completion_date_event_update_completion_disabled(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Create the completion event.
$CFG->enablecompletion = true;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Disable completion.
$CFG->enablecompletion = false;
// Try and update the completion date.
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time + DAYSECS);
// Check that there is an event in the database.
$events = $DB->get_records('event');
$this->assertCount(1, $events);
// Get the event.
$event = reset($events);
// Confirm the event has not changed.
$this->assertEquals('assign', $event->modulename);
$this->assertEquals($assign->id, $event->instance);
$this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type);
$this->assertEquals(\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED, $event->eventtype);
$this->assertEquals($time, $event->timestart);
$this->assertEquals($time, $event->timesort);
}
public function test_update_completion_date_event_delete_completion_disabled(): void {
global $CFG, $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create an assign activity.
$time = time();
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Create the completion event.
$CFG->enablecompletion = true;
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Disable completion.
$CFG->enablecompletion = false;
// Should still be able to delete completion events even when completion is disabled.
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, null);
// Check that there is now no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
}
/**
* Test for mark_course_completions_activity_criteria().
*/
public function test_mark_course_completions_activity_criteria(): void {
global $DB, $CFG;
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$cm = get_coursemodule_from_instance('data', $data->id);
$c = new \completion_info($course);
// Add activity completion criteria.
$criteriadata = new \stdClass();
$criteriadata->id = $course->id;
$criteriadata->criteria_activity = array();
// Some activities.
$criteriadata->criteria_activity[$cmdata->id] = 1;
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
$this->setUser($teacher);
// Mark activity complete for both users.
$completion = new \stdClass();
$completion->coursemoduleid = $cm->id;
$completion->completionstate = COMPLETION_COMPLETE;
$completion->timemodified = time();
$completion->viewed = COMPLETION_NOT_VIEWED;
$completion->overrideby = null;
$completion->id = 0;
$completion->userid = $student1->id;
$c->internal_set_data($cm, $completion, true);
$completion->id = 0;
$completion->userid = $student2->id;
$c->internal_set_data($cm, $completion, true);
// Run instant course completions for student1. Only student1 will be marked as completed a course.
$userdata = ['userid' => $student1->id, 'courseid' => $course->id];
$actual = $DB->get_records('course_completions');
$this->assertEmpty($actual);
$coursecompletionid = \core_completion\api::mark_course_completions_activity_criteria($userdata);
$actual = $DB->get_records('course_completions');
$this->assertEquals(reset($actual)->id, $coursecompletionid);
$this->assertEquals(1, count($actual));
$this->assertEquals($student1->id, reset($actual)->userid);
// Run course completions cron. Both students will be marked as completed a course.
$coursecompletionid = \core_completion\api::mark_course_completions_activity_criteria();
$this->assertEquals(0, $coursecompletionid);
$actual = $DB->get_records('course_completions');
$students = [$student1->id, $student2->id];
$this->assertEquals(2, count($actual));
$this->assertContains(reset($actual)->userid, $students);
$this->assertContains(end($actual)->userid, $students);
}
/**
* Test for mark_course_completions_activity_criteria() with different completionpassgrade settings.
* @covers ::mark_course_completions_activity_criteria
*/
public function test_mark_course_completions_activity_criteria_completion_states(): void {
global $DB, $CFG;
require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php');
$this->resetAfterTest(true);
$courses[] = $this->getDataGenerator()->create_course(['shortname' => 'completionpassgradenotset',
'enablecompletion' => 1]);
$courses[] = $this->getDataGenerator()->create_course(['shortname' => 'completionpassgradeset',
'enablecompletion' => 1]);
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
foreach ($courses as $course) {
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
$completioncriteria = [
'completionusegrade' => 1,
'gradepass' => 50
];
if ($course->shortname == 'completionpassgradeset') {
$completioncriteria['completionpassgrade'] = 1;
}
/** @var \mod_assign_generator $assigngenerator */
$assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$assign = $assigngenerator->create_instance([
'course' => $course->id,
'completion' => COMPLETION_ENABLED,
] + $completioncriteria);
$cmassing = get_coursemodule_from_id('assign', $assign->cmid);
$cm = get_coursemodule_from_instance('assign', $assign->id);
$c = new \completion_info($course);
// Add activity completion criteria.
$criteriadata = new \stdClass();
$criteriadata->id = $course->id;
$criteriadata->criteria_activity = array();
// Some activities.
$criteriadata->criteria_activity[$cmassing->id] = 1;
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
$this->setUser($teacher);
// Mark user completions.
$completion = new \stdClass();
$completion->coursemoduleid = $cm->id;
$completion->timemodified = time();
$completion->viewed = COMPLETION_NOT_VIEWED;
$completion->overrideby = null;
// Student1 achieved passgrade.
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE_PASS;
$completion->userid = $student1->id;
$c->internal_set_data($cm, $completion, true);
// Student2 has not achieved passgrade.
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE_FAIL;
$completion->userid = $student2->id;
$c->internal_set_data($cm, $completion, true);
$actual = $DB->get_records('course_completions', ['course' => $course->id]);
$this->assertEmpty($actual);
// Run course completions cron.
$coursecompletionid = \core_completion\api::mark_course_completions_activity_criteria();
$this->assertEquals(0, $coursecompletionid);
$actual = $DB->get_records('course_completions', ['course' => $course->id]);
if ($course->shortname == 'completionpassgradeset') {
// Only student1 has completed a course.
$this->assertEquals(1, count($actual));
$this->assertEquals($student1->id, reset($actual)->userid);
} else {
// Both students completed a course.
$students = [$student1->id, $student2->id];
$this->assertEquals(2, count($actual));
$this->assertContains(reset($actual)->userid, $students);
$this->assertContains(end($actual)->userid, $students);
}
}
}
}
@@ -0,0 +1,146 @@
@core @core_completion
Feature: Allow to mark course as completed without cron for activity completion criteria
In order for students to see instant course completion updates
I need to be able update completion state without cron
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Completion course | CC1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
| teacher1 | Teacher | First | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | CC1 | student |
| student2 | CC1 | student |
| teacher1 | CC1 | editingteacher |
And the following "activity" exists:
| activity | assign |
| course | CC1 |
| name | Test assignment name |
| idnumber | assign1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | CC1 | course-view-* | side-pre |
And I am on the "Test assignment name" "assign activity editing" page logged in as admin
And I click on "Expand all" "link" in the "region-main" "region"
And I set the field "Add requirements" to "1"
And I set the field "completionusegrade" to "1"
And I press "Save and return to course"
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the field "Assignment - Test assignment name" to "1"
And I press "Save changes"
@javascript
Scenario: Update course completion when student marks activity as complete
Given I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I click on "Expand all" "link" in the "region-main" "region"
And I set the field "Students must manually mark the activity as done" to "1"
And I press "Save and return to course"
When I am on the "Completion course" course page logged in as student1
And I should see "Status: Not yet started"
And I press "Mark as done"
And I wait until "Done" "button" exists
And "Mark as done" "button" should not exist
And I reload the page
Then I should see "Status: Complete"
@javascript
Scenario: Update course completion when teacher grades a single assignment
Given I am on the "Test assignment name" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "student1@example.com" "table_row"
And I set the field "Grade out of 100" to "40"
And I click on "Save changes" "button"
And I am on "Completion course" course homepage
When I am on the "Completion course" course page logged in as student1
Then I should see "Status: Complete"
@javascript
Scenario: Update course completion with multiple activity criteria
Given the following "activity" exists:
| activity | assign |
| course | CC1 |
| name | Test assignment name2 |
| idnumber | assign2 |
And I am on the "Test assignment name2" "assign activity editing" page logged in as admin
And I click on "Expand all" "link" in the "region-main" "region"
And I set the field "Add requirements" to "1"
And I set the field "completionusegrade" to "1"
And I press "Save and return to course"
And I navigate to "Course completion" in current page administration
And I should see "Course completion settings" in the "tertiary-navigation" "region"
And I expand all fieldsets
And I set the field "Assignment - Test assignment name" to "1"
And I set the field "Assignment - Test assignment name2" to "1"
And I press "Save changes"
And I am on the "Test assignment name" "assign activity" page
And I follow "View all submissions"
And I click on "Grade" "link" in the "student1@example.com" "table_row"
And I set the field "Grade out of 100" to "40"
And I click on "Save changes" "button"
And I am on the "Completion course" course page logged in as student1
And I should see "Status: In progress"
And I am on the "Test assignment name2" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "student1@example.com" "table_row"
And I set the field "Grade out of 100" to "40"
And I click on "Save changes" "button"
When I am on the "Completion course" course page logged in as student1
Then I should see "Status: Complete"
@javascript
Scenario: Course completion should not be updated when teacher grades assignment on course grader report page
Given I am on the "Completion course" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I give the grade "57" to the user "Student First" for the grade item "Test assignment name"
And I press "Save changes"
When I am on the "Completion course" course page logged in as student1
Then I should see "Status: Pending"
And I run the scheduled task "core\task\completion_regular_task"
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
And I reload the page
And I should see "Status: Complete"
@javascript
Scenario: Course completion should not be updated when teacher grades assignment on activity grader report page
Given I am on the "Completion course" "grades > Single View > View" page logged in as "teacher1"
And I click on "Users" "link" in the ".page-toggler" "css_element"
And I turn editing mode on
And I click on "Student First" in the "user" search widget
And I set the field "Override for Test assignment name" to "1"
When I set the following fields to these values:
| Grade for Test assignment name | 10.00 |
| Feedback for Test assignment name | test data |
And I press "Save"
When I am on the "Completion course" course page logged in as student1
And I should see "Status: Pending"
And I run the scheduled task "core\task\completion_regular_task"
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
And I reload the page
Then I should see "Status: Complete"
@javascript @_file_upload
Scenario: Course completion should not be updated when teacher imports grades with csv file
Given I am on the "Completion course" course page logged in as teacher1
And I navigate to "CSV file" import page in the course gradebook
And I upload "lib/tests/fixtures/upload_grades.csv" file to "File" filemanager
And I press "Upload grades"
And I set the field "Map to" to "Email address"
And I set the field "Test assignment name" to "Assignment: Test assignment name"
And I press "Upload grades"
And I press "Continue"
And I should see "10.00" in the "Student First" "table_row"
And I am on the "Completion course" course page logged in as student1
And I should see "Status: Pending"
When I run the scheduled task "core\task\completion_regular_task"
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
And I reload the page
Then I should see "Status: Complete"
@@ -0,0 +1,59 @@
@core @core_completion
Feature: Backup and restore the activity with the completion
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| idnumber | a1 |
| name | Test assignment name |
| intro | Submit your online text |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionview | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
| completionpassgrade | 1 |
And the following config values are set as admin:
| enableasyncbackup | 0 |
And I am on the "Test assignment name" "assign activity" page logged in as student1
And I log out
@javascript @_file_upload
Scenario: Restore the legacy assignment with completion condition.
Given I am on the "Course 1" "restore" page logged in as "admin"
And I press "Manage course backups"
And I upload "completion/tests/fixtures/legacy_course_completion.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "legacy_course_completion.mbz" backup into a new course using this options:
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
When I am on the "Course 2" course page logged in as student1
Then the "View" completion condition of "Test assignment name" is displayed as "done"
And I am on the "Course 2" course page logged in as student2
And the "View" completion condition of "Test assignment name" is displayed as "todo"
@javascript @_file_upload
Scenario: Backup and restore the assignment with the viewed and not-viewed completion condition
Given I am on the "Course 1" course page logged in as admin
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
When I am on the "Course 2" course page logged in as student1
Then the "View" completion condition of "Test assignment name" is displayed as "done"
And I am on the "Course 2" course page logged in as student2
And the "View" completion condition of "Test assignment name" is displayed as "todo"
+429
View File
@@ -0,0 +1,429 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Completion steps definitions.
*
* @package core_completion
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
* Steps definitions to deal with course and activities completion.
*
* @package core_completion
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_completion extends behat_base {
/**
* Checks that the specified user has completed the specified activity of the current course.
*
* @Then /^"(?P<user_fullname_string>(?:[^"]|\\")*)" user has completed "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
* @param string $userfullname
* @param string $activityname
*/
public function user_has_completed_activity($userfullname, $activityname) {
// Will throw an exception if the element can not be hovered.
$titleliteral = $userfullname . ", " . $activityname . ": Completed";
$xpath = "//table[@id='completion-progress']";
$this->execute("behat_completion::go_to_the_current_course_activity_completion_report");
$this->execute("behat_general::should_exist_in_the",
array($titleliteral, "icon", $xpath, "xpath_element")
);
}
/**
* Checks that the specified user has not completed the specified activity of the current course.
*
* @Then /^"(?P<user_fullname_string>(?:[^"]|\\")*)" user has not completed "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
* @param string $userfullname
* @param string $activityname
*/
public function user_has_not_completed_activity($userfullname, $activityname) {
// Will throw an exception if the element can not be hovered.
$titleliteral = $userfullname . ", " . $activityname . ": Not completed";
$xpath = "//table[@id='completion-progress']";
$this->execute("behat_completion::go_to_the_current_course_activity_completion_report");
$this->execute("behat_general::should_exist_in_the",
array($titleliteral, "icon", $xpath, "xpath_element")
);
}
/**
* Goes to the current course activity completion report.
*
* @Given /^I go to the current course activity completion report$/
*/
public function go_to_the_current_course_activity_completion_report() {
$completionnode = get_string('pluginname', 'report_progress');
$reportsnode = get_string('reports');
$this->execute("behat_navigation::i_navigate_to_in_current_page_administration",
$reportsnode);
$this->execute("behat_general::i_click_on_in_the", [$completionnode, "link", "region-main", "region"]);
}
/**
* Toggles completion tracking for course being in the course page.
*
* @When /^completion tracking is "(?P<completion_status_string>Enabled|Disabled)" in current course$/
* @param string $completionstatus The status, enabled or disabled.
*/
public function completion_is_toggled_in_course($completionstatus) {
$toggle = strtolower($completionstatus) == 'enabled' ? get_string('yes') : get_string('no');
// Go to course editing.
$this->execute("behat_general::click_link", get_string('settings'));
// Expand all the form fields.
$this->execute("behat_forms::i_expand_all_fieldsets");
// Enable completion.
$this->execute("behat_forms::i_set_the_field_to",
array(get_string('enablecompletion', 'completion'), $toggle));
// Save course settings.
$this->execute("behat_forms::press_button", get_string('savechangesanddisplay'));
}
/**
* Checks if the activity with specified name is maked as complete.
*
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as complete$/
*/
public function activity_marked_as_complete($activityname, $activitytype, $completiontype) {
if ($completiontype == "manual") {
$imgalttext = get_string("completion-alt-manual-y", 'core_completion', $activityname);
} else {
$imgalttext = get_string("completion-alt-auto-y", 'core_completion', $activityname);
}
$activityxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
$activityxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
$this->execute("behat_general::should_exist_in_the",
array($imgalttext, "icon", $activityxpath, "xpath_element")
);
}
/**
* Checks if the activity with specified name is maked as complete.
*
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as not complete$/
*/
public function activity_marked_as_not_complete($activityname, $activitytype, $completiontype) {
if ($completiontype == "manual") {
$imgalttext = get_string("completion-alt-manual-n", 'core_completion', $activityname);
} else {
$imgalttext = get_string("completion-alt-auto-n", 'core_completion', $activityname);
}
$activityxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
$activityxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
$this->execute("behat_general::should_exist_in_the",
array($imgalttext, "icon", $activityxpath, "xpath_element")
);
}
/**
* Checks if the activity with specified name is maked as complete.
*
* @When the :conditionname completion condition of :activityname is displayed as :completionstatus
* @param string $conditionname The completion condition text.
* @param string $activityname The activity name.
* @param string $completionstatus The completion status. Must be either of the following: 'todo', 'done', 'failed'.
*/
public function activity_completion_condition_displayed_as(string $conditionname, string $activityname,
string $completionstatus): void {
if (!in_array($completionstatus, ['todo', 'done', 'failed'])) {
throw new coding_exception('Invalid completion status. It must be of type "todo", "done", or "failed".');
}
$text = get_string("completion_automatic:$completionstatus", 'core_course');
$conditionslistlabel = get_string('completionrequirements', 'core_course', $activityname);
$selector = "div[aria-label='$conditionslistlabel']";
try {
// If there is a dropdown, open it.
$dropdownnode = $this->find('css', $selector . ' .dropdown-menu');
if (!$dropdownnode->hasClass('show')) {
$params = ["button.dropdown-toggle", "css_element", $selector, "css_element"];
$this->execute("behat_general::i_click_on_in_the", $params);
}
} catch (ElementNotFoundException $e) {
// If the dropdown does not exist, we are in the activity page, all good.
}
$xpath = "//div[@aria-label='$conditionslistlabel']//span[text()='$conditionname']/..";
$this->execute("behat_general::assert_element_contains_text", [$text, $xpath, "xpath_element"]);
}
/**
* Checks if the activity with specified name is maked as complete.
*
* @When the :conditionname completion condition of :activityname overridden by :username is displayed as :completionstatus
* @param string $conditionname The completion condition text.
* @param string $activityname The activity name.
* @param string $username The full name of the user overriding the student's activity completion.
* @param string $completionstatus The override completion status. Must be either of the following: 'todo', 'done'.
*/
public function overridden_activity_completion_condition_displayed_as(string $conditionname, string $activityname,
string $username, string $completionstatus): void {
if (!in_array($completionstatus, ['todo', 'done'])) {
throw new coding_exception('Invalid override completion status. It must be of type "todo" or "done".');
}
$conditionlabel = get_string('completion_setby:auto:' . $completionstatus, 'core_course', (object)[
'condition' => $conditionname,
'setby' => $username,
]);
$conditionbadge = "div[aria-label='$conditionlabel']";
$conditionslistlabel = get_string('completionrequirements', 'core_course', $activityname);
$completionconditions = "div[aria-label='$conditionslistlabel']";
$params = [$conditionbadge, 'css_element', $completionconditions, 'css_element'];
$this->execute("behat_general::should_exist_in_the", $params);
}
/**
* Checks the manual completion state of an activity.
*
* @Given /^the manual completion button of "(?P<activityname>(?:[^"]|\\")*)" is displayed as "(?P<completionstatus>(?:[^"]|\\")*)"$/
* @param string $activityname The activity name.
* @param string $completionstatus The completion status shown on the manual completion button.
* Must be either 'Mark as done' or 'Done'.
*/
public function manual_completion_button_displayed_as(string $activityname, string $completionstatus): void {
if (!in_array($completionstatus, ['Mark as done', 'Done'])) {
throw new coding_exception('Invalid completion status. It must be "Mark as done" or "Done".');
}
$langstringkey = $completionstatus === 'Done' ? 'done' : 'markdone';
$conditionslistlabel = get_string('completion_manual:aria:' . $langstringkey, 'core_course', $activityname);
$selector = "button[aria-label='$conditionslistlabel']";
$this->execute("behat_general::assert_element_contains_text", [$completionstatus, $selector, "css_element"]);
}
/**
* Checks the manual completion state of an activity.
*
* @Given /^the manual completion button of "(?P<activityname>(?:[^"]|\\")*)" overridden by "(?P<username>(?:[^"]|\\")*)" is displayed as "(?P<completionstatus>(?:[^"]|\\")*)"$/
* @param string $activityname The activity name.
* @param string $username The full name of the user overriding the student's activity completion.
* @param string $completionstatus The completion status shown on the manual completion button.
* Must be either 'Mark as done' or 'Done'.
*/
public function overridden_manual_completion_button_displayed_as(string $activityname, string $username,
string $completionstatus): void {
if (!in_array($completionstatus, ['Mark as done', 'Done'])) {
throw new coding_exception('Invalid completion status. It must be "Mark as done" or "Done".');
}
$langstringkey = $completionstatus === 'Done' ? 'done' : 'markdone';
$conditionslistlabel = get_string('completion_setby:manual:' . $langstringkey, 'core_course', (object)[
'activityname' => $activityname,
'setby' => $username,
]);
$selector = "button[aria-label='$conditionslistlabel']";
$this->execute("behat_general::assert_element_contains_text", [$completionstatus, $selector, "css_element"]);
}
/**
* Toggles the manual completion button for a given activity.
*
* @Given /^I toggle the manual completion state of "(?P<activityname>(?:[^"]|\\")*)"$/
* @param string $activityname The activity name.
*/
public function toggle_the_manual_completion_state(string $activityname): void {
$selector = "button[data-action=toggle-manual-completion][data-activityname='{$activityname}']";
$this->execute("behat_general::i_click_on", [$selector, "css_element"]);
}
/**
* Check that the activity does show completion information.
*
* @Given /^there should be no completion information shown for "(?P<activityname>(?:[^"]|\\")*)"$/
* @param string $activityname The activity name.
*/
public function there_should_be_no_completion_for_activity(string $activityname): void {
$containerselector = "div[data-region=activity-information][data-activityname='$activityname']";
try {
$this->find('css_element', $containerselector);
} catch (ElementNotFoundException $e) {
// If activity information container does not exist (activity dates not shown, completion info not shown), all good.
return;
}
// Otherwise, ensure that the completion information does not exist.
$elementselector = "div[data-region=completion-info]";
$params = [$elementselector, "css_element", $containerselector, "css_element"];
$this->execute("behat_general::should_not_exist_in_the", $params);
}
/**
* Check that the manual completion button for the activity is disabled.
*
* @Given /^the manual completion button for "(?P<activityname>(?:[^"]|\\")*)" should be disabled$/
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_be_disabled(string $activityname): void {
$selector = "div[data-region='activity-information'][data-activityname='$activityname'] button";
$params = [$selector, "css_element"];
$this->execute("behat_general::the_element_should_be_disabled", $params);
}
/**
* Check that the manual completion button for the activity does not exist.
*
* @Given /^the manual completion button for "(?P<activityname>(?:[^"]|\\")*)" should not exist/
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_not_exist(string $activityname): void {
$selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_not_exist', $params);
}
/**
* Check that the manual completion button for the activity exists.
*
* @Given /^the manual completion button for "(?P<activityname>(?:[^"]|\\")*)" should exist/
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_exist(string $activityname): void {
$selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_exist', $params);
}
/**
* Check that the activity has the given automatic completion condition.
*
* @When :activityname should have the :conditionname completion condition
* @param string $activityname The activity name.
* @param string $conditionname The automatic condition name.
*/
public function activity_should_have_the_completion_condition(string $activityname, string $conditionname): void {
$containerselector = "div[data-region=activity-information][data-activityname='$activityname']";
try {
// If there is a dropdown, open it.
$dropdownnode = $this->find('css', $containerselector . ' .dropdown-menu');
if (!$dropdownnode->hasClass('show')) {
$params = ["button.dropdown-toggle", "css_element", $containerselector, "css_element"];
$this->execute("behat_general::i_click_on_in_the", $params);
}
} catch (ElementNotFoundException $e) {
// If the dropdown does not exist, we are in the activity page, all good.
}
$params = [$conditionname, $containerselector, 'css_element'];
$this->execute("behat_general::assert_element_contains_text", $params);
}
/**
* Checks if the activity with specified name shows a information completion checkbox (i.e. showing the completion tracking
* configuration).
*
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion shows a configuration completion checkbox/
* @param string $activityname The activity name.
* @param string $activitytype The activity type.
* @param string $completiontype The completion type.
*/
public function activity_has_configuration_completion_checkbox($activityname, $activitytype, $completiontype) {
if ($completiontype == "manual") {
$imgname = 'i/completion-manual-enabled';
} else {
$imgname = 'i/completion-auto-enabled';
}
$iconxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
$iconxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
$iconxpath .= "/descendant::div[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
$this->execute("behat_general::the_attribute_of_should_contain",
array("src", $iconxpath, "xpath_element", $imgname)
);
}
/**
* Checks if the activity with specified name shows a tracking completion checkbox (i.e. showing my completion tracking status)
*
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion shows a status completion checkbox/
* @param string $activityname The activity name.
* @param string $activitytype The activity type.
* @param string $completiontype The completion type.
*/
public function activity_has_status_completion_checkbox($activityname, $activitytype, $completiontype) {
if ($completiontype == "manual") {
$imgname = 'i/completion-manual-';
} else {
$imgname = 'i/completion-auto-';
}
$iconxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
$iconxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
$iconxpath .= "/descendant::div[@class='actions']/descendant::img[contains(@src, 'i/completion-')]";
$this->execute("behat_general::the_attribute_of_should_contain",
array("src", $iconxpath, "xpath_element", $imgname)
);
$this->execute("behat_general::the_attribute_of_should_not_contain",
array("src", $iconxpath, "xpath_element", '-enabled')
);
}
/**
* Checks if the activity with specified name does not show any completion checkbox.
*
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity does not show any completion checkbox/
* @param string $activityname The activity name.
* @param string $activitytype The activity type.
*/
public function activity_has_not_any_completion_checkbox($activityname, $activitytype) {
$iconxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
$iconxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
$iconxpath .= "/descendant::img[contains(@src, 'i/completion-')]";
$this->execute("behat_general::should_not_exist",
array($iconxpath, "xpath_element")
);
}
}
@@ -0,0 +1,87 @@
@core @core_completion @javascript
Feature: Allow teachers to bulk edit activity completion rules in a course.
In order to avoid editing single activities
As a teacher
I need to be able to edit the completion rules for a group of activities.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| assign | C1 | a1 | Test assignment one | Submit something! | 30 |
| assign | C1 | a2 | Test assignment two | Submit something! | 10 |
| assign | C1 | a3 | Test assignment three | Submit something! | 15 |
| assign | C1 | a4 | Test assignment four | Submit nothing! | 15 |
And I log out
# Given I am a teacher in a course with completion tracking enabled and activities present.
# When I bulk edit activity completion rules for activities of the same kind.
# Then the completion rules should be updated for all selected activities.
Scenario: Bulk edit activity completion rules
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Bulk edit activity completion"
And I click on "Test assignment one" "checkbox"
And I click on "Test assignment two" "checkbox"
And I click on "Edit" "button"
And I should see "The changes will affect the following 2 activities or resources:"
And I set the following fields to these values:
| Add requirements | 1 |
| View the activity | 1 |
| Make a submission | 1 |
| Receive a grade | 1 |
And I click on "Save changes" "button"
Then I should see "Changes saved"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "View the activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "Receive a grade" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "Make a submission" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "View the activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "Receive a grade" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "Make a submission" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
# Same conditions as above,
# However if completionpassgrade is set, only the completionpassgrade detail should be shown.
# It is implied requires grade is selected as it passgrade is dependent on it.
Scenario: Bulk edit passing grade completion
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Bulk edit activity completion"
And I click on "Test assignment one" "checkbox"
And I click on "Test assignment two" "checkbox"
And I click on "Edit" "button"
And I should see "The changes will affect the following 2 activities or resources:"
And I set the field "Add requirements" to "1"
And I should see "Make a submission"
And I set the field "Receive a grade" to "1"
And I set the field "Passing grade" to "1"
And I click on "Save changes" "button"
Then I should see "Changes saved"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "Passing grade" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "Passing grade" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
@accessibility
Scenario: Evaluate the accessibility of the bulk edit activity completion page
Given I am on the "Course 1" course page logged in as "teacher1"
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Bulk edit activity completion"
And the page should meet accessibility standards
@@ -0,0 +1,47 @@
@core @core_completion @javascript
Feature: Show activity completion status or activity completion configuration on the course page
In order to understand the configuration or status of an activity's completion
As a user
I need to see the appropriate completion information for each activity in the course homepage
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| teacher2 | Teacher | Second | teacher2@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | teacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro | completion | completionview | completionexpected |
| assign | C1 | assign1 | Test assignment name | Test assignment description | 2 | 1 | 0 |
| quiz | C1 | quiz1 | Test quiz name | Test quiz description | 0 | 0 | 0 |
| forum | C1 | forum1 | Test forum name | | 1 | 0 | 0 |
Scenario: Show completion status to students
Given I am on the "Course 1" course page logged in as student1
And the manual completion button of "Test forum name" is displayed as "Mark as done"
And the "View" completion condition of "Test assignment name" is displayed as "todo"
And there should be no completion information shown for "Test quiz name"
Scenario: Show completion configuration to editing teachers
Given I am on the "Course 1" course page logged in as teacher1
And "Test forum name" should have the "Mark as done" completion condition
And "Test assignment name" should have the "View" completion condition
And there should be no completion information shown for "Test quiz name"
And I am on "Course 1" course homepage with editing mode on
And "Test forum name" should have the "Mark as done" completion condition
And "Test assignment name" should have the "View" completion condition
And there should be no completion information shown for "Test quiz name"
Scenario: Show completion configuration to non-editing teachers
Given I am on the "Course 1" course page logged in as teacher2
And "Test forum name" should have the "Mark as done" completion condition
And "Test assignment name" should have the "View" completion condition
And there should be no completion information shown for "Test quiz name"
@@ -0,0 +1,40 @@
@core @core_completion
Feature: Completion with no calendar capabilites
In order to allow work effectively
As a teacher
I need to be able to create activities with completion enabled without calendar capabilities
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode | enablecompletion |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activity" exists:
| activity | forum |
| course | C1 |
| idnumber | 00001 |
| name | Test forum name |
| completion | 2 |
And I am on the "Test forum name" "forum activity editing" page logged in as admin
And I set the following fields to these values:
| id_completionexpected_enabled | 1 |
| id_completionexpected_day | 1 |
| id_completionexpected_month | 1 |
| id_completionexpected_year | 2017 |
And I press "Save and return to course"
And I am on the "Course 1" "permissions" page
And I override the system permissions of "Teacher" role with:
| capability | permission |
| moodle/calendar:manageentries | Prohibit |
Scenario: Editing completion date
When I am on the "Test forum name" "forum activity editing" page logged in as teacher1
And I set the following fields to these values:
| id_completionexpected_year | 2018 |
And I press "Save and return to course"
Then I should see "Test forum name"
@@ -0,0 +1,30 @@
@core @core_completion
Feature: Set completion of other courses as criteria for completion of current course
In order to set completion of other courses as criteria for completion of current course
As a user
I want to select the prerequisite courses in completion settings
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | One | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
@javascript
Scenario: Set completion of prerequisite course as completion criteria of current course
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Course completion" in current page administration
And I click on "Condition: Completion of other courses" "link"
And I set the field "Courses available" to "Course 2"
And I press "Save changes"
And I add the "Course completion status" block
And I click on "View course report" "link" in the "Course completion status" "block"
Then I should see "Course 2" in the "completion-progress" "table"
And I should see "Student One" in the "completion-progress" "table"
@@ -0,0 +1,117 @@
@block @block_completionstatus @core_completion @javascript
Feature: Course completion state should match completion criteria
In order to understand the configuration or status of an course's completion
As a user
I need to see the appropriate completion information on course and dashboard pages
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | T1 |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion | showcompletionconditions |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment name |
| assignsubmission_onlinetext_enabled | 1 |
| grade[modgrade_type] | Point |
| grade[modgrade_point] | 100 |
| gradepass | 70 |
| completion | 2 |
| completionusegrade | 1 |
| completionpassgrade | 1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | C1 | course-view-* | side-pre |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Course completion" in current page administration
And I click on "Condition: Activity completion" "link"
And I set the field "Assignment - Test assignment name" to "1"
And I press "Save changes"
Scenario: Completion status show match completion criteria when passgrage condition is set.
Given I am on the "Course 1" course page logged in as "student1"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "todo"
And I should see "Status: Not yet started" in the "Course completion status" "block"
When the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name | student1 | This is a submission for assignment |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 50 |
And I reload the page
Then I should see "Status: Not yet started" in the "Course completion status" "block"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "My courses" page
And I should not see "100%" in the "Course overview" "block"
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Not completed" "icon" should exist in the "Student 1" "table_row"
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 75 |
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And I am on the "Course 1" course page logged in as "student1"
And I should see "Status: Complete" in the "Course completion status" "block"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
And I am on the "My courses" page
And I should see "100%" in the "Course overview" "block"
Scenario: Completion status show match completion criteria when passgrage condition is not set.
Given I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I set the following fields to these values:
| completionpassgrade | 0 |
And I press "Save and return to course"
And I am on the "Course 1" course page logged in as "student1"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And I should see "Status: Not yet started" in the "Course completion status" "block"
When the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name | student1 | I'm the student1 submission |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 50 |
And I reload the page
# TODO: Expected status is Complete but activity is marked as completed with a failed icon.
# Then I should see "Status: Complete" in the "Course completion status" "block"
Then I should see "Status: Pending" in the "Course completion status" "block"
# Once MDL-75582 is fixed "failed" should be changed to "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "My courses" page
And I should see "100%" in the "Course overview" "block"
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 75 |
And I navigate to "Reports > Activity completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And I navigate to "Reports > Course completion" in current page administration
And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row"
And I am on the "Course 1" course page logged in as "student1"
And I should see "Status: Complete" in the "Course completion status" "block"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And I am on the "My courses" page
And I should see "100%" in the "Course overview" "block"
@@ -0,0 +1,83 @@
@core @core_completion
Feature: Allow teachers to edit the visibility of completion conditions in a course
In order to show students the course completion conditions in a course
As a teacher
I need to be able to edit completion conditions settings
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | enablecompletion | showcompletionconditions |
| Course 1 | C1 | 1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | idnumber | name | completion | completionsubmit |
| choice | C1 | c1m | Test choice manual| 1 | 0 |
| choice | C1 | c1a | Test choice auto | 2 | 1 |
@javascript
Scenario: Completion condition displaying for manual and auto completion
Given I log in as "teacher1"
When I am on "Course 1" course homepage
# The manual completion "Mark as done" criteria should displayed in the dropdown in the course homepage.
Then "Test choice manual" should have the "Mark as done" completion condition
And I follow "Test choice manual"
# The manual completion toggle button should be displayed in activity view.
And the manual completion button for "Test choice manual" should be disabled
# Automatic completion conditions should be displayed on both activity view page and course homepage if show completion conditions is enabled.
And I am on "Course 1" course homepage
And "Test choice auto" should have the "Make a choice" completion condition
And I follow "Test choice auto"
And "Test choice auto" should have the "Make a choice" completion condition
Scenario: Completion condition displaying setting can be disabled at course level
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Settings" in current page administration
When I set the following fields to these values:
| Show activity completion conditions | No |
And I click on "Save and display" "button"
# Automatic completion conditions should not be displayed on the course homepage if show completion conditions is disabled.
And there should be no completion information shown for "Test choice auto"
# Completion conditions are always shown in the module's view page.
And I follow "Test choice auto"
Then "Test choice auto" should have the "Make a choice" completion condition
# The manual completion toggle button should not be displayed in the course homepage when completion is disabled.
And I am on "Course 1" course homepage
And the manual completion button for "Test choice manual" should not exist
# The manual completion toggle button should always be displayed in the activity view.
And I follow "Test choice manual"
And the manual completion button for "Test choice manual" should be disabled
Scenario Outline: Default showcompletionconditions value in course form on course creation
Given I log in as "admin"
And I navigate to "Courses > Default settings > Course default settings" in site administration
And I set the field "Show activity completion conditions" to "<siteshowcompletion>"
And I press "Save changes"
When I navigate to "Courses > Add a new course" in site administration
Then the field "showcompletionconditions" matches value "<expected>"
Examples:
| siteshowcompletion | expected |
| Yes | Yes |
| No | No |
Scenario Outline: Default showcompletionconditions displayed when editing a course with disabled completion tracking
Given I log in as "admin"
And I navigate to "Courses > Default settings > Course default settings" in site administration
And I set the field "Show activity completion conditions" to "<siteshowcompletion>"
And I press "Save changes"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Settings" in current page administration
And I set the field "Enable completion tracking" to "No"
And I press "Save and display"
And I navigate to "Settings" in current page administration
Then the field "Show activity completion conditions" matches value "<expected>"
Examples:
| siteshowcompletion | expected |
| Yes | Yes |
| No | No |
@@ -0,0 +1,237 @@
@core @core_completion @javascript
Feature: Allow teachers to edit the default activity completion rules in a course.
In order to set the activity completion defaults for new activities
As a teacher
I need to be able to edit the completion rules for a group of activities.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion | numsections |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
# Given I am a teacher in a course with completion tracking enabled and activities present.
# When I edit activity completion defaults for activity types.
# Then the completion rule defaults should apply only to activities created from that point onwards.
Scenario: Edit default activity completion rules for assignment
Given the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment one |
| completion | 0 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| id_completion_assign_2 | 1 |
| completionview_assign | 1 |
| completionusegrade_assign | 1 |
| completionsubmit_assign | 1 |
And I should not see "Cancel" in the "[data-region='activitycompletion-forum']" "css_element"
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
Then I should see "Changes saved"
And I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| completionview_assign | 0 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
And I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue"
And I expand all fieldsets
# Completion tracking 2 = Add requirements.
And the field "Add requirements" matches value "1"
And the field "completionview" matches value "0"
And the field "completionusegrade" matches value "1"
And the field "completionsubmit" matches value "1"
But I am on the "Test assignment one" Activity page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
# Completion tracking 0 = Do not indicate activity completion.
And the field "None" matches value "1"
Scenario: Edit default activity completion rules for forum
Given the following "activity" exists:
| activity | forum |
| course | C1 |
| name | Test forum one |
| completion | 0 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Forum" "button"
And I set the following fields to these values:
# 2 = Add requeriments.
| id_completion_forum_2 | 1 |
| completionview_forum | 0 |
# 0 = Rating.
| completionusegrade_forum | 1 |
| completiongradeitemnumber_forum | 0 |
# 1 = Passing grade.
| completionpassgrade_forum | 1 |
| completionpostsenabled_forum | 1 |
| completionposts_forum | 2 |
| completionrepliesenabled_forum | 1 |
| completionreplies_forum | 3 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-forum']" "css_element"
Then I should see "Changes saved"
And I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I click on "Add a new Forum" "link" in the "Add an activity or resource" "dialogue"
And I expand all fieldsets
# Completion tracking 2 = Add requirements.
And the field "Add requirements" matches value "1"
And the field "completionview" matches value "0"
# Value 0 for completiongradeitemnumber is "Rating".
And the field "completiongradeitemnumber" matches value "0"
And the field "completionpassgrade" matches value "1"
And the field "completionpostsenabled" matches value "1"
And the field "completionposts" matches value "2"
And the field "completionrepliesenabled" matches value "1"
And the field "completionreplies" matches value "3"
But I am on the "Test forum one" Activity page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
# None checked = Do not indicate activity completion.
And the field "None" matches value "1"
Scenario: Edit default activity completion rules for glossary
Given the following "activity" exists:
| activity | glossary |
| course | C1 |
| name | Test glossary one |
| completion | 0 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Glossary" "button"
And I set the following fields to these values:
# Add requirements = 2.
| id_completion_glossary_2 | 1 |
| completionview_glossary | 0 |
| completionusegrade_glossary | 1 |
| completionentriesenabled_glossary | 1 |
| completionentries_glossary | 2 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-glossary']" "css_element"
Then I should see "Changes saved"
And I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I click on "Add a new Glossary" "link" in the "Add an activity or resource" "dialogue"
And I expand all fieldsets
# Completion tracking 2 = Add requirements.
And the field "Add requirements" matches value "1"
And the field "completionview" matches value "0"
And the field "completionusegrade" matches value "1"
And the field "completionentriesenabled" matches value "1"
And the field "completionentries" matches value "2"
But I am on the "Test glossary one" Activity page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
# Completion tracking 0 = Do not indicate activity completion.
And the field "None" matches value "1"
Scenario: Edit default activity completion rules for several activities
Given I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| id_completion_assign_2 | 1 |
| completionview_assign | 0 |
| completionusegrade_assign | 0 |
| completionsubmit_assign | 1 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
And I should see "Changes saved"
And I click on "Expand Forum" "button"
And I set the following fields to these values:
| id_completion_forum_2 | 1 |
| completionview_forum | 0 |
| completionpostsenabled_forum | 1 |
| completionposts_forum | 3 |
| completiondiscussionsenabled_forum | 0 |
| completionrepliesenabled_forum | 0 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-forum']" "css_element"
And I should see "Changes saved"
And I click on "Expand SCORM package" "button"
And I set the following fields to these values:
| id_completion_scorm_2 | 1 |
| completionview_scorm | 0 |
| completionscoreenabled_scorm | 1 |
| completionscorerequired_scorm | 3 |
| completionstatusrequired_scorm[2] | 1 |
| completionstatusrequired_scorm[4] | 0 |
| completionstatusallscos_scorm | 1 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-scorm']" "css_element"
And I should see "Changes saved"
And I click on "Expand Book" "button"
And I set the following fields to these values:
| completion_book | Do not indicate activity completion |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-book']" "css_element"
And I should see "Changes saved"
And I click on "Expand Lesson" "button"
And I set the following fields to these values:
# Students must manually mark the activity as done = 1.
| id_completion_lesson_1 | 1 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-lesson']" "css_element"
And I should see "Changes saved"
# Change current page and go back to "Default activity completion", to confirm the form values have been saved properly.
And I set the field "Course completion tertiary navigation" to "Course completion settings"
And I set the field "Course completion tertiary navigation" to "Default activity completion"
Then the field "id_completion_lesson_1" matches value "1"
# Check that the rules for book, assignment and forum are still the same.
And I click on "Expand Book" "button"
And the field "id_completion_book_0" matches value "1"
And I click on "Expand Assignment" "button"
And the field "id_completion_assign_2" matches value "1"
And the field "completionview_assign" matches value "0"
And the field "completionusegrade_assign" matches value "0"
And the field "completionsubmit_assign" matches value "1"
And I click on "Expand Forum" "button"
And the field "id_completion_forum_2" matches value "1"
And the field "completionview_forum" matches value "0"
And the field "completionpostsenabled_forum" matches value "1"
And the field "completionposts_forum" matches value "3"
And the field "completiondiscussionsenabled_forum" matches value "0"
And the field "completionrepliesenabled_forum" matches value "0"
And I click on "Expand SCORM package" "button"
And the field "id_completion_scorm_2" matches value "1"
And the field "completionview_scorm" matches value "0"
And the field "completionscoreenabled_scorm" matches value "1"
And the field "completionscorerequired_scorm" matches value "3"
And the field "completionstatusrequired_scorm[2]" matches value "1"
And the field "completionstatusrequired_scorm[4]" matches value "0"
And the field "completionstatusallscos_scorm" matches value "1"
Scenario: Edit default activity completion without rules for automatic completion
Given I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| id_completion_assign_2 | 1 |
| completionview_assign | 0 |
| completionusegrade_assign | 0 |
| completionsubmit_assign | 0 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
Then I should see "You must select at least one condition"
And I should not see "Changes saved"
Scenario: Activities in Default activity completion are ordered alphabetically
Given I am on the "Course 1" course page logged in as teacher1
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
Then "Quiz" "text" should appear before "Text and media area" "text"
@accessibility
Scenario: Evaluate the accessibility of the default activity completion page
Given I am on the "Course 1" course page logged in as "teacher1"
When I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And the page should meet accessibility standards
@@ -0,0 +1,44 @@
@core @core_completion @javascript
Feature: Students will be marked as completed if they have achieved a passing grade.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activity" exists:
| idnumber | a1 |
| activity | assign |
| course | C1 |
| name | Test assignment name |
| intro | Submit your online text |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionpassgrade | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has not completed "Test assignment name" activity
Scenario: Passing grade completion
Given I am on the "Course 1" "grades > Grader report > View" page
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I press "Save changes"
When I am on the "Course 1" course page logged in as student1
Then the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "Course 1" course page logged in as student2
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
@@ -0,0 +1,72 @@
@core @core_completion @javascript
Feature: Students will be marked as completed and pass/fail
if they have viewed an activity and achieved a grade.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
| student3 | Student | Third | student3@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| idnumber | a1 |
| name | Test assignment name |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionview | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
| completionpassgrade | 1 |
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has not completed "Test assignment name" activity
And I am on the "Test assignment name" "assign activity" page logged in as student2
And I am on the "Test assignment name" "assign activity" page logged in as student1
Scenario: Confirm completion (incomplete/pass/fail) are set correctly
Given the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 21.00 |
| Test assignment name | student2 | 50.00 |
| Test assignment name | student3 | 30.00 |
When I am on "Course 1" course homepage
Then the "View" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "Course 1" course page logged in as student2
And the "View" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
And I am on the "Course 1" course page logged in as student3
And the "View" completion condition of "Test assignment name" is displayed as "todo"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
@javascript
Scenario: Keep current view completion condition when the teacher does the action 'Unlock completion settings'.
Given the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 21.00 |
| Test assignment name | student2 | 50.00 |
And I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I expand all fieldsets
And I press "Unlock completion settings"
And I expand all fieldsets
And I should see "Completion options unlocked"
And I click on "Save and display" "button"
When I am on the "Course 1" course page logged in as student1
Then the "View" completion condition of "Test assignment name" is displayed as "done"
And I am on the "Course 1" course page logged in as student2
And the "View" completion condition of "Test assignment name" is displayed as "done"
@@ -0,0 +1,31 @@
@core @core_completion
Feature: Allow students to manually mark an activity as complete
In order to let students decide when an activity is completed
As a teacher
I need to allow students to mark activities as completed
@javascript
Scenario: Mark an activity as completed
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | forum |
| course | C1 |
| name | Test forum name |
| completion | 1 |
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has not completed "Test forum name" activity
And I am on the "Course 1" course page logged in as student1
When I toggle the manual completion state of "Test forum name"
Then the manual completion button of "Test forum name" is displayed as "Done"
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has completed "Test forum name" activity
@@ -0,0 +1,115 @@
@core @core_completion @javascript
Feature: Students will be shown relevant completion state based on grade item visibility.
In order to understand completion states of course modules
As a student
I need to see relevant completion information for various combination of activity passgrade settings
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment name |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionpassgrade | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has not completed "Test assignment name" activity
And I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "todo"
Scenario: Passing grade and receive a grade completions for visible grade item (passgrade completion enabled)
Given the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name | student1 | 21.00 |
| Test assignment name | student2 | 50.00 |
And I am on the "Course 1" course page logged in as teacher1
And "Student First" user has completed "Test assignment name" activity
And "Student Second" user has completed "Test assignment name" activity
When I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I am on the "Course 1" course page logged in as student2
Then the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
Scenario: Passing grade and receive a grade completions for hidden grade item (passgrade completion enabled)
Given I am on the "Course 1" "grades > gradebook setup" page logged in as "teacher1"
And I hide the grade item "Test assignment name" of type "gradeitem" on "setup" page
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I press "Save changes"
And I am on "Course 1" course homepage
And "Student First" user has not completed "Test assignment name" activity
And "Student Second" user has completed "Test assignment name" activity
And I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "todo"
And I am on the "Course 1" course page logged in as student2
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
Scenario: Receive a grade completion for visible grade item (passgrade completion disabled)
Given I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I set the following fields to these values:
| completionpassgrade | 0 |
And I press "Save and display"
And I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And I should not see "Receive a passing grade"
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I press "Save changes"
And I am on "Course 1" course homepage
And "Student First" user has completed "Test assignment name" activity
And "Student Second" user has completed "Test assignment name" activity
When I am on the "Course 1" course page logged in as student1
# Once MDL-75582 is fixed "failed" should be changed to "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "failed"
And I should not see "Receive a passing grade"
And I am on the "Course 1" course page logged in as student2
Then the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
Scenario: Receive a grade completion for hidden grade item (passgrade completion disabled)
Given I am on the "Test assignment name" "assign activity editing" page logged in as teacher1
And I set the following fields to these values:
| completionpassgrade | 0 |
And I press "Save and display"
And I am on the "Course 1" course page logged in as student1
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo"
And I should not see "Receive a passing grade"
And I am on the "Course 1" "grades > gradebook setup" page logged in as "teacher1"
And I hide the grade item "Test assignment name" of type "gradeitem" on "setup" page
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I press "Save changes"
And I am on "Course 1" course homepage
And "Student First" user has completed "Test assignment name" activity
And "Student Second" user has completed "Test assignment name" activity
When I am on the "Course 1" course page logged in as student1
Then the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And I should not see "Receive a passing grade"
And I am on the "Course 1" course page logged in as student2
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
@@ -0,0 +1,59 @@
@core @core_completion
Feature: Restrict activity availability through date conditions
In order to control activity access through date condition
As a teacher
I need to set allow access dates to restrict activity access
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| section | 1 |
| name | Test assignment 1 |
| intro | This assignment is restricted by date |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
And I am on the "Test assignment 1" "assign activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
@javascript
Scenario: Show activity greyed-out to students when available from date is in future
Given I click on "Add restriction..." "button"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the following fields to these values:
| x[day] | 31 |
| x[month] | 12 |
| x[year] | 2037 |
And I press "Save and return to course"
And I log out
When I am on the "Course 1" course page logged in as student1
Then I should see "Available from 31 December 2037"
And "Test assignment 1" "link" should not exist in the "page" "region"
@javascript
Scenario: Show activity hidden to students when available until date is in past
Given I click on "Add restriction..." "button"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the following fields to these values:
| x[day] | 1 |
| x[month] | 2 |
| x[year] | 2013 |
| Direction | until |
# Click eye icon to hide it when not available.
And I click on ".availability-item .availability-eye img" "css_element"
And I press "Save and return to course"
And I log out
When I am on the "Course 1" course page logged in as student1
Then I should not see "Test assignment 1" in the "page" "region"
@@ -0,0 +1,55 @@
@core @core_completion
Feature: Restrict activity availability through grade conditions
In order to control activity access through grade condition
As a teacher
I need to set grade condition to restrict activity access
@javascript
Scenario: Show activity greyed-out to students when grade condition is not satisfied
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| course | activity | idnumber | name | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | submissiondrafts |
| C1 | assign | Grade assignment | Grade assignment | 1 | 0 | 0 |
| C1 | page | Grade page | Test page name | | | |
# Adding the page like this because id_availableform_enabled needs to be clicked to trigger the action.
And I am on the "Test page name" "page activity editing" page logged in as "teacher1"
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Grade" "button" in the "Add restriction..." "dialogue"
And I click on "min" "checkbox"
And I set the following fields to these values:
| id | Grade assignment |
| minval | 20 |
And I press "Save and return to course"
When I am on the "Course 1" course page logged in as student1
Then I should see "Not available unless: You achieve higher than a certain score in Grade assignment"
And I should see "Test page name"
And "Test page name" "link" should not exist in the "region-main" "region"
And I am on the "Grade assignment" "assign activity" page
And I press "Add submission"
And I set the following fields to these values:
| Online text | I'm the student submission |
And I press "Save changes"
And I should see "Submitted for grading"
And I am on the "Grade assignment" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student First" "table_row"
And I set the following fields to these values:
| Grade | 21 |
And I press "Save changes"
And I am on the "Course 1" course page logged in as student1
And "Test page name" activity should be visible
And I should not see "Not available unless: You achieve higher than a certain score in Grade assignment"
@@ -0,0 +1,78 @@
@core @core_completion
Feature: Restrict sections availability through completion or grade conditions
In order to control section's contents access through activities completion or grade condition
As a teacher
I need to restrict sections availability using different conditions
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | First | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | section | name | intro | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | submissiondrafts | content |
| assign | C1 | 1 | Grade assignment | Grade this assignment to revoke restriction on restricted assignment | 1 | 0 | 0 | |
| page | C1 | 2 | Test page name | Restricted section page resource, till grades in Grade assignment is at least 20% | | | | Test page contents |
@javascript
Scenario: Show section greyed-out to student when completion condition is not satisfied
Given the following "activities" exist:
| activity | course | section | intro | completion | idnumber |
| label | C1 | 1 | Test label | 1 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I edit the section "2"
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
And I set the following fields to these values:
| cm | Test label |
| Required completion status | must be marked complete |
And I press "Save changes"
And I am on the "Course 1" course page logged in as "student1"
Then I should see "Not available unless: The activity Test label is marked complete"
And I should not see "Test page name"
And I toggle the manual completion state of "Test label"
And I should see "Test page name"
And I should not see "Not available unless: The activity Test label is marked complete"
@javascript
Scenario: Show section greyed-out to student when grade condition is not satisfied
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I edit the section "2"
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Grade" "button" in the "Add restriction..." "dialogue"
And I set the following fields to these values:
| id | Grade assignment |
| min | 1 |
| minval | 20 |
And I press "Save changes"
When I am on the "Course 1" course page logged in as "student1"
Then I should see "Not available unless: You achieve higher than a certain score in Grade assignment"
And "Test page name" activity should be hidden
And I am on the "Grade assignment" "assign activity" page
And I press "Add submission"
And I set the following fields to these values:
| Online text | I'm the student submission |
And I press "Save changes"
And I should see "Submitted for grading"
And I log out
And I am on the "Grade assignment" "assign activity" page logged in as teacher1
And I follow "View all submissions"
And I click on "Grade" "link" in the "Student First" "table_row"
And I set the following fields to these values:
| Grade | 21 |
And I press "Save changes"
And I follow "Edit settings"
And I am on the "Course 1" Course page logged in as student1
And "Test page name" activity should be visible
And I should not see "Not available unless: You achieve higher than a certain score in Grade assignment"
@@ -0,0 +1,85 @@
@core @core_completion
Feature: Allow admins to edit the default activity completion rules at site level.
In order to set the activity completion defaults for new activities
As an admin
I need to be able to edit the completion rules for a group of activities at site level.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And I log in as "admin"
@javascript
Scenario: Default activity completion rules with no site or course default completion
Given the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment one |
| completion | 1 |
When I add a assign activity to course "Course 1" section "0"
And I expand all fieldsets
# Completion tracking 0 = Do not indicate activity completion.
Then the field "None" matches value "1"
# Default values don't affect existing activities.
But I am on the "Test assignment one" "assign activity editing" page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "Students must manually mark the activity as done" matches value "1"
And the field "None" matches value "0"
@javascript
Scenario: Default activity completion rules with site default completion but with no course default completion
Given the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment one |
| completion | 0 |
And the following "core_completion > Course default" exist:
| course | module | completion | completionview | completionusegrade | completionsubmit |
| Acceptance test site | assign | 2 | 0 | 1 | 1 |
When I add a assign activity to course "Course 1" section "0"
And I expand all fieldsets
Then the field "Add requirements" matches value "1"
And the field "completionview" matches value "0"
And the field "completionusegrade" matches value "1"
And the field "completionsubmit" matches value "1"
# Default values don't affect existing activities.
But I am on the "Test assignment one" "assign activity editing" page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "Add requirements" matches value "0"
And the field "None" matches value "1"
@javascript
Scenario: Default activity completion rules with site default completion and course default completion
Given the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment one |
| completion | 0 |
And the following "core_completion > Course defaults" exist:
| course | module | completion | completionview | completionusegrade | completionsubmit |
| Acceptance test site | assign | 2 | 0 | 1 | 1 |
| C1 | assign | 2 | 1 | 0 | 1 |
When I add a assign activity to course "Course 1" section "0"
And I expand all fieldsets
Then the field "Add requirements" matches value "1"
And the field "completionview" matches value "1"
And the field "completionusegrade" matches value "0"
And the field "completionsubmit" matches value "1"
# Default values don't affect existing activities.
But I am on the "Test assignment one" "assign activity editing" page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "Add requirements" matches value "0"
And the field "None" matches value "1"
Scenario: Navigate to site default activity completion
Given I navigate to "Courses > Default settings > Default activity completion" in site administration
When I should see "Default activity completion"
Then I should see "These are the default completion conditions for activities in all courses."
And the following config values are set as admin:
| enablecompletion | 0 |
And I navigate to "Courses > Default settings" in site administration
And I should not see "Default activity completion"
@@ -0,0 +1,38 @@
@core @core_completion
Feature: Allow teachers to manually mark users as complete when configured
In order for teachers to mark students as complete
As a teacher
I need to be able to use the completion report mark complete functionality
Scenario: Mark a student as complete using the completion report
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Completion course | CC1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | First | student1@example.com |
| teacher1 | Teacher | First | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | CC1 | student |
| teacher1 | CC1 | editingteacher |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| completionstatus | Course | CC1 | course-view-* | side-pre |
And I am on the "Completion course" course page logged in as admin
And I navigate to "Course completion" in current page administration
And I set the field "Teacher" to "1"
And I press "Save changes"
And I am on the "Completion course" course page logged in as student1
And I should see "Status: Not yet started"
When I am on the "Completion course" course page logged in as teacher1
And I follow "View course report"
And I should see "Student First"
And I follow "Click to mark user complete"
# Running completion task just after clicking sometimes fail, as record
# should be created before the task runs.
And I wait "1" seconds
And I run the scheduled task "core\task\completion_regular_task"
And I am on site homepage
Then I am on the "Completion course" course page logged in as student1
And I should see "Status: Complete"
+278
View File
@@ -0,0 +1,278 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
use core_completion_bulkedit_form;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
/**
* External completion functions unit tests
*
* @package core_completion
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class bulk_update_test extends \advanced_testcase {
/**
* Provider for test_bulk_form_submit_single
* @return array
*/
public function bulk_form_submit_single_provider() {
return [
'assign-1' => ['assign', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionsubmit' => 1]],
'assign-2' => ['assign', ['completion' => COMPLETION_TRACKING_MANUAL]],
'book-1' => ['book', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'book-2' => ['book', ['completion' => COMPLETION_TRACKING_MANUAL]],
'chat-1' => ['chat', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'chat-2' => ['chat', ['completion' => COMPLETION_TRACKING_MANUAL]],
'choice-1' => ['choice', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionsubmit' => 1]],
'choice-2' => ['choice', ['completion' => COMPLETION_TRACKING_MANUAL]],
'data-1' => ['data', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'data-2' => ['data', ['completion' => COMPLETION_TRACKING_MANUAL]],
'data-3' => ['data',
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1, 'completionentries' => 3,
'completionentriesenabled' => 1],
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1, 'completionentries' => 3]],
'feedback-1' => ['feedback', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 0,
'completionsubmit' => 1]],
'feedback-2' => ['feedback', ['completion' => COMPLETION_TRACKING_MANUAL]],
'folder-1' => ['folder', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'folder-2' => ['folder', ['completion' => COMPLETION_TRACKING_MANUAL]],
'forum-1' => ['forum',
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completiondiscussions' => 1,
'completiondiscussionsenabled' => 1],
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completiondiscussions' => 1]],
'forum-2' => ['forum', ['completion' => COMPLETION_TRACKING_MANUAL]],
'glossary-1' => ['glossary',
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1, 'completionentries' => 3,
'completionentriesenabled' => 1],
['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1, 'completionentries' => 3]],
'glossary-2' => ['glossary', ['completion' => COMPLETION_TRACKING_MANUAL]],
'imscp-1' => ['imscp', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'imscp-2' => ['imscp', ['completion' => COMPLETION_TRACKING_MANUAL]],
'label-1' => ['label', ['completion' => COMPLETION_TRACKING_MANUAL]],
'lesson-1' => ['lesson', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionendreached' => 1]],
'lesson-2' => ['lesson', ['completion' => COMPLETION_TRACKING_MANUAL]],
'lti-1' => ['lti', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'lti-2' => ['lti', ['completion' => COMPLETION_TRACKING_MANUAL]],
'page-1' => ['page', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'page-2' => ['page', ['completion' => COMPLETION_TRACKING_MANUAL]],
'quiz-1' => ['quiz', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionpassgrade' => 1]],
'quiz-2' => ['quiz', ['completion' => COMPLETION_TRACKING_MANUAL]],
'resource-1' => ['resource', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'resource-2' => ['resource', ['completion' => COMPLETION_TRACKING_MANUAL]],
'scorm-1' => ['scorm',
[
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionscoreenabled' => 1,
'completionscorerequired' => 1,
'completionstatusrequired' => [2 => 'passed'],
],
[
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionscorerequired' => 1,
'completionstatusrequired' => 2,
],
],
'scorm-2' => ['scorm', ['completion' => COMPLETION_TRACKING_MANUAL]],
'survey-1' => ['survey', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionsubmit' => 1]],
'survey-2' => ['survey', ['completion' => COMPLETION_TRACKING_MANUAL]],
'url-1' => ['url', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'url-2' => ['url', ['completion' => COMPLETION_TRACKING_MANUAL]],
'wiki-1' => ['wiki', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'wiki-2' => ['wiki', ['completion' => COMPLETION_TRACKING_MANUAL]],
'workshop-1' => ['workshop', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'workshop-2' => ['workshop', ['completion' => COMPLETION_TRACKING_MANUAL]],
];
}
/**
* Creates an instance of bulk edit completion form for one activity, validates and saves it
*
* @dataProvider bulk_form_submit_single_provider
* @param string $modname
* @param array $submitdata data to use in mock form submit
* @param array|null $validatedata data to validate the
*/
public function test_bulk_form_submit_single($modname, $submitdata, $validatedata = null): void {
global $DB;
if ($validatedata === null) {
$validatedata = $submitdata;
}
$this->resetAfterTest();
$this->setAdminUser();
list($course, $cms) = $this->create_course_and_modules([$modname]);
// Submit the bulk completion form with the provided data and make sure it returns the same data.
core_completion_bulkedit_form::mock_submit(['id' => $course->id, 'cmid' => array_keys($cms)] + $submitdata, []);
$form = new core_completion_bulkedit_form(null, ['cms' => $cms]);
$this->assertTrue($form->is_validated());
$data = $form->get_data();
foreach ($validatedata as $key => $value) {
$this->assertEquals($value, $data->$key);
}
// Apply completion rules to the modules.
$manager = new manager($course->id);
$manager->apply_completion($data, $form->has_custom_completion_rules());
// Make sure either course_modules or instance table was respectfully updated.
$cm = reset($cms);
$cmrec = $DB->get_record('course_modules', ['id' => $cm->id]);
$instancerec = $DB->get_record($modname, ['id' => $cm->instance]);
foreach ($validatedata as $key => $value) {
if (property_exists($cmrec, $key)) {
$this->assertEquals($value, $cmrec->$key);
} else {
$this->assertEquals($value, $instancerec->$key);
}
}
}
/**
* Creates a course and the number of modules
* @param array $modulenames
* @return array array of two elements - course and list of cm_info objects
*/
protected function create_course_and_modules($modulenames) {
global $CFG, $PAGE;
// Chat and Survey modules are disabled by default, enable them for testing.
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
$manager::enable_plugin('chat', 1);
$manager::enable_plugin('survey', 1);
$CFG->enablecompletion = true;
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1], ['createsections' => true]);
$PAGE->set_course($course);
$cmids = [];
foreach ($modulenames as $modname) {
$module = $this->getDataGenerator()->create_module($modname, ['course' => $course->id]);
$cmids[] = $module->cmid;
}
$modinfo = get_fast_modinfo($course);
$cms = [];
foreach ($cmids as $cmid) {
$cms[$cmid] = $modinfo->get_cm($cmid);
}
return [$course, $cms];
}
/**
* Provider for test_bulk_form_submit_multiple
* @return array
*/
public function bulk_form_submit_multiple_provider() {
return [
'Several modules with the same module type (choice)' => [
[
'modulenames' => ['choice', 'choice', 'choice'],
'submitdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionsubmit' => 1],
'validatedata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionsubmit' => 1],
'cmdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC],
'instancedata' => [['completionsubmit' => 1], ['completionsubmit' => 1], ['completionsubmit' => 1]]
]
],
'Several modules with different module type' => [
[
'modulenames' => ['choice', 'forum'],
'submitdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1],
'validatedata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1],
'cmdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC],
'instancedata' => null
]
],
'Setting manual completion (completionview shoud be ignored)' => [
[
'modulenames' => ['scorm', 'forum', 'label', 'assign'],
'submitdata' => ['completion' => COMPLETION_TRACKING_MANUAL, 'completionview' => 1],
'validatedata' => [],
'cmdata' => ['completion' => COMPLETION_TRACKING_MANUAL, 'completionview' => 0],
'instancedata' => null
]
],
'If at least one module does not support completionsubmit it can\'t be set' => [
[
'modulenames' => ['survey', 'wiki'],
'submitdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1, 'completionsubmit' => 1],
'validatedata' => [],
'cmdata' => ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1],
'instancedata' => [['completionsubmit' => 0], []]
]
]
];
}
/**
* Use bulk completion edit for updating multiple modules
*
* @dataProvider bulk_form_submit_multiple_provider
* @param array $providerdata
*/
public function test_bulk_form_submit_multiple($providerdata): void {
global $DB;
$modulenames = $providerdata['modulenames'];
$submitdata = $providerdata['submitdata'];
$validatedata = $providerdata['validatedata'];
$cmdata = $providerdata['cmdata'];
$instancedata = $providerdata['instancedata'];
$this->resetAfterTest();
$this->setAdminUser();
list($course, $cms) = $this->create_course_and_modules($modulenames);
// Submit the bulk completion form with the provided data and make sure it returns the same data.
core_completion_bulkedit_form::mock_submit(['id' => $course->id, 'cmid' => array_keys($cms)] + $submitdata, []);
$form = new core_completion_bulkedit_form(null, ['cms' => $cms]);
$this->assertTrue($form->is_validated());
$data = $form->get_data();
foreach ($validatedata as $key => $value) {
$this->assertEquals($value, $data->$key);
}
// Apply completion rules to the modules.
$manager = new manager($course->id);
$manager->apply_completion($data, $form->has_custom_completion_rules());
// Make sure either course_modules or instance table was respectfully updated.
$cnt = 0;
foreach ($cms as $cm) {
$cmrec = $DB->get_record('course_modules', ['id' => $cm->id]);
$instancerec = $DB->get_record($cm->modname, ['id' => $cm->instance]);
foreach ($cmdata as $key => $value) {
$this->assertEquals($value, $cmrec->$key, 'Error asserting that value for the field ' . $key.' ' .
$cmrec->$key . ' matches expected value ' . $value);
}
if ($instancedata) {
foreach ($instancedata[$cnt] as $key => $value) {
$this->assertEquals($value, $instancerec->$key, 'Error asserting that value for the field ' . $key . ' '.
$instancerec->$key . ' matches expected value ' . $value);
}
}
$cnt++;
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
/**
* Tests that completion works without requiring unnecessary capabilities.
*
* @package core_completion
* @copyright 2018 University of Nottingham
* @author Neill Magill <neill.magill@nottingham.ac.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class capabilities_test extends \advanced_testcase {
/**
* A user who does not have capabilities to add events to the calendar should be able to create activities.
*/
public function test_creation_with_no_calendar_capabilities(): void {
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course(['enablecompletion' => 1]);
$context = \context_course::instance($course->id);
$user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
$roleid = self::getDataGenerator()->create_role();
self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
$generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
// Create an instance as a user without the calendar capabilities.
$this->setUser($user);
$params = array(
'course' => $course->id,
'completionexpected' => time() + 2000,
);
$generator->create_instance($params);
}
}
@@ -0,0 +1,636 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains unit tests for core_completion/cm_completion_details.
*
* @package core_completion
* @copyright 2021 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_completion;
use advanced_testcase;
use cm_info;
use completion_info;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
/**
* Class for unit testing core_completion/cm_completion_details.
*
* @package core_completion
* @copyright 2021 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_completion\cm_completion_details
*/
class cm_completion_details_test extends advanced_testcase {
/** @var completion_info A completion object. */
protected $completioninfo = null;
/**
* Fetches a mocked cm_completion_details instance.
*
* @param int|null $completion The completion tracking mode for the module.
* @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
* @param object $mockcompletiondata Mock data to be returned by get_data.
* @param string $modname The modname to set in the cm if a specific one is required.
* @return cm_completion_details
*/
protected function setup_data(?int $completion, array $completionoptions = [],
object $mockcompletiondata = null, $modname = 'somenonexistentmod'): cm_completion_details {
if (is_null($completion)) {
$completion = COMPLETION_TRACKING_AUTOMATIC;
}
// Mock a completion_info instance so we can simply mock the returns of completion_info::get_data() later.
$this->completioninfo = $this->getMockBuilder(completion_info::class)
->disableOriginalConstructor()
->getMock();
// Mock return of completion_info's is_enabled() method to match the expected completion tracking for the module.
$this->completioninfo->expects($this->any())
->method('is_enabled')
->willReturn($completion);
if (!empty($mockcompletiondata)) {
$this->completioninfo->expects($this->any())
->method('get_data')
->willReturn($mockcompletiondata);
}
// Build a mock cm_info instance.
$mockcminfo = $this->getMockBuilder(cm_info::class)
->disableOriginalConstructor()
->onlyMethods(['__get'])
->getMock();
// Mock the return of the magic getter method when fetching the cm_info object's customdata and instance values.
$mockcminfo->expects($this->any())
->method('__get')
->will($this->returnValueMap([
['completion', $completion],
['instance', 1],
['modname', $modname],
['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED],
['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null],
['completionpassgrade', $completionoptions['completionpassgrade'] ?? null],
]));
return new cm_completion_details($this->completioninfo, $mockcminfo, 2);
}
/**
* Provides data for test_has_completion().
*
* @return array[]
*/
public function has_completion_provider(): array {
return [
'Automatic' => [
COMPLETION_TRACKING_AUTOMATIC, true
],
'Manual' => [
COMPLETION_TRACKING_MANUAL, true
],
'None' => [
COMPLETION_TRACKING_NONE, false
],
];
}
/**
* Test for has_completion().
*
* @covers ::has_completion
* @dataProvider has_completion_provider
* @param int $completion The completion tracking mode.
* @param bool $expectedresult Expected result.
*/
public function test_has_completion(int $completion, bool $expectedresult): void {
$cmcompletion = $this->setup_data($completion);
$this->assertEquals($expectedresult, $cmcompletion->has_completion());
}
/**
* Provides data for test_is_automatic().
*
* @return array[]
*/
public function is_automatic_provider(): array {
return [
'Automatic' => [
COMPLETION_TRACKING_AUTOMATIC, true
],
'Manual' => [
COMPLETION_TRACKING_MANUAL, false
],
'None' => [
COMPLETION_TRACKING_NONE, false
],
];
}
/**
* Test for is_available().
*
* @covers ::is_automatic
* @dataProvider is_automatic_provider
* @param int $completion The completion tracking mode.
* @param bool $expectedresult Expected result.
*/
public function test_is_automatic(int $completion, bool $expectedresult): void {
$cmcompletion = $this->setup_data($completion);
$this->assertEquals($expectedresult, $cmcompletion->is_automatic());
}
/**
* Provides data for test_is_manual().
*
* @return array[]
*/
public function is_manual_provider(): array {
return [
'Automatic' => [
COMPLETION_TRACKING_AUTOMATIC, false
],
'Manual' => [
COMPLETION_TRACKING_MANUAL, true
],
'None' => [
COMPLETION_TRACKING_NONE, false
],
];
}
/**
* Test for is_manual().
*
* @covers ::is_manual
* @dataProvider is_manual_provider
* @param int $completion The completion tracking mode.
* @param bool $expectedresult Expected result.
*/
public function test_is_manual(int $completion, bool $expectedresult): void {
$cmcompletion = $this->setup_data($completion);
$this->assertEquals($expectedresult, $cmcompletion->is_manual());
}
/**
* Data provider for test_get_overall_completion().
* @return array[]
*/
public function overall_completion_provider(): array {
return [
'Complete' => [COMPLETION_COMPLETE],
'Incomplete' => [COMPLETION_INCOMPLETE],
];
}
/**
* Test for get_overall_completion().
*
* @covers ::get_overall_completion
* @dataProvider overall_completion_provider
* @param int $state
*/
public function test_get_overall_completion(int $state): void {
$completiondata = (object)['completionstate' => $state];
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, [], $completiondata);
$this->assertEquals($state, $cmcompletion->get_overall_completion());
}
/**
* Data provider for test_is_overall_complete().
* @return array[]
*/
public static function is_overall_complete_provider(): array {
return [
'Automatic, require view, not viewed' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_INCOMPLETE,
'completionview' => COMPLETION_INCOMPLETE,
'completiongrade' => null,
'completionpassgrade' => null,
],
'Automatic, require view, viewed' => [
'expected' => true,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_COMPLETE,
'completionview' => COMPLETION_COMPLETE,
'completiongrade' => null,
'completionpassgrade' => null,
],
'Automatic, require grade, not graded' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_INCOMPLETE,
'completionview' => null,
'completiongrade' => COMPLETION_INCOMPLETE,
'completionpassgrade' => null,
],
'Automatic, require grade, graded with fail' => [
'expected' => true,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_COMPLETE_FAIL,
'completionview' => null,
'completiongrade' => COMPLETION_COMPLETE_FAIL,
'completionpassgrade' => null,
],
'Automatic, require grade, graded with passing' => [
'expected' => true,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_COMPLETE_PASS,
'completionview' => null,
'completiongrade' => COMPLETION_COMPLETE_PASS,
'completionpassgrade' => null,
],
'Automatic, require passgrade, not graded' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_INCOMPLETE,
'completionview' => null,
'completiongrade' => null,
'completionpassgrade' => COMPLETION_INCOMPLETE,
],
'Automatic, require passgrade, graded with fail' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_COMPLETE_FAIL,
'completionview' => null,
'completiongrade' => null,
'completionpassgrade' => COMPLETION_COMPLETE_FAIL,
],
'Automatic, require passgrade, graded with passing' => [
'expected' => true,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionstate' => COMPLETION_COMPLETE_PASS,
'completionview' => null,
'completiongrade' => null,
'completionpassgrade' => COMPLETION_COMPLETE_PASS,
],
'Manual, incomplete' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_MANUAL,
'completionstate' => COMPLETION_INCOMPLETE,
],
'Manual, complete' => [
'expected' => true,
'completion' => COMPLETION_TRACKING_MANUAL,
'completionstate' => COMPLETION_COMPLETE,
],
'None, incomplete' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_NONE,
'completionstate' => COMPLETION_INCOMPLETE,
],
'None, complete' => [
'expected' => false,
'completion' => COMPLETION_TRACKING_NONE,
'completionstate' => COMPLETION_COMPLETE,
],
];
}
/**
* Test for is_overall_complete().
*
* @covers ::is_overall_complete
* @dataProvider is_overall_complete_provider
* @param bool $expected Expected result returned by is_overall_complete().
* @param int $completion The completion tracking mode.
* @param int $completionstate The overall completion state.
* @param int|null $completionview Completion status of the "view" completion condition.
* @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
* @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
*/
public function test_is_overall_complete(
bool $expected,
int $completion,
int $completionstate,
?int $completionview = null,
?int $completiongrade = null,
?int $completionpassgrade = null,
): void {
$options = [];
$getdatareturn = (object)[
'completionstate' => $completionstate,
'viewed' => $completionview,
'completiongrade' => $completiongrade,
'passgrade' => $completionpassgrade,
];
if (!is_null($completionview)) {
$options['completionview'] = true;
}
if (!is_null($completiongrade)) {
$options['completionusegrade'] = true;
}
if (!is_null($completionpassgrade)) {
$options['completionpassgrade'] = true;
}
$cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
$this->assertEquals($expected, $cmcompletion->is_overall_complete());
}
/**
* Data provider for test_get_details().
* @return array[]
*/
public function get_details_provider() {
return [
'No completion tracking' => [
COMPLETION_TRACKING_NONE, null, null, null, []
],
'Manual completion tracking' => [
COMPLETION_TRACKING_MANUAL, null, null, null, []
],
'Automatic, require view, not viewed' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, null, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
]
]
],
'Automatic, require view, viewed' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, null, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
]
]
],
'Automatic, require grade, incomplete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, null, [
'completionusegrade' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
]
]
],
'Automatic, require grade, complete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, null, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
]
]
],
'Automatic, require view (complete) and grade (incomplete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, null, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
]
]
],
'Automatic, require view (incomplete) and grade (complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, null, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
]
]
],
'Automatic, require grade, require pass grade, complete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require grade, require pass grade, incomplete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require view (complete), require grade(complete), require pass grade(complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require view (incomplete), require grade(complete), require pass grade(complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
];
}
/**
* Test for \core_completion\cm_completion_details::get_details().
*
* @covers ::get_details
* @dataProvider get_details_provider
* @param int $completion The completion tracking mode.
* @param int|null $completionview Completion status of the "view" completion condition.
* @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
* @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
* @param array $expecteddetails Expected completion details returned by get_details().
*/
public function test_get_details(int $completion, ?int $completionview,
?int $completiongrade, ?int $completionpassgrade, array $expecteddetails): void {
$options = [];
$getdatareturn = (object)[
'viewed' => $completionview,
'completiongrade' => $completiongrade,
'passgrade' => $completionpassgrade,
];
if (!is_null($completionview)) {
$options['completionview'] = true;
}
if (!is_null($completiongrade)) {
$options['completionusegrade'] = true;
}
if (!is_null($completionpassgrade)) {
$options['completionpassgrade'] = true;
}
$cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
$this->assertEquals($expecteddetails, $cmcompletion->get_details());
}
/**
* Data provider for test_get_details_custom_order().
* @return array[]
*/
public function get_details_custom_order_provider() {
return [
'Custom and view/grade standard conditions, view first and grade last' => [
true,
true,
[
'completionsubmit' => true,
],
'assign',
['completionview', 'completionsubmit', 'completionusegrade'],
],
'Custom and view/grade standard conditions, grade not last' => [
true,
true,
[
'completionminattempts' => 2,
'completionusegrade' => 50,
'completionpassorattemptsexhausted' => 1,
],
'quiz',
['completionview', 'completionminattempts', 'completionusegrade', 'completionpassorattemptsexhausted'],
],
'Custom and grade standard conditions only, no view condition' => [
false,
true,
[
'completionsubmit' => true,
],
'assign',
['completionsubmit', 'completionusegrade'],
],
'Custom and view standard conditions only, no grade condition' => [
true,
false,
[
'completionsubmit' => true
],
'assign',
['completionview', 'completionsubmit'],
],
'View and grade conditions only, activity with no custom conditions' => [
true,
true,
[
'completionview' => true,
'completionusegrade' => true
],
'workshop',
['completionview', 'completionusegrade'],
],
'View condition only, activity with no custom conditions' => [
true,
false,
[
'completionview' => true,
],
'workshop',
['completionview'],
],
];
}
/**
* Test custom sort order is functioning in \core_completion\cm_completion_details::get_details().
*
* @covers ::get_details
* @dataProvider get_details_custom_order_provider
* @param bool $completionview Completion status of the "view" completion condition.
* @param bool $completiongrade Completion status of the "must receive grade" completion condition.
* @param array $customcompletionrules Custom completion requirements, along with their values.
* @param string $modname The name of the module having data fetched.
* @param array $expectedorder The expected order of completion conditions returned about the module.
*/
public function test_get_details_custom_order(bool $completionview, bool $completiongrade, array $customcompletionrules,
string $modname, array $expectedorder): void {
$options['customcompletion'] = [];
$customcompletiondata = [];
if ($completionview) {
$options['completionview'] = true;
}
if ($completiongrade) {
$options['completionusegrade'] = true;
}
// Set up the completion rules for the completion info.
foreach ($customcompletionrules as $customtype => $isenabled) {
$customcompletiondata[$customtype] = COMPLETION_COMPLETE;
}
$getdatareturn = (object)[
'viewed' => $completionview ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
'completiongrade' => $completiongrade ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
'customcompletion' => $customcompletiondata,
];
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, $options, $getdatareturn, $modname);
$this->completioninfo->expects($this->any())
->method('get_data')
->willReturn($getdatareturn);
$fetcheddetails = $cmcompletion->get_details();
// Check the expected number of items are returned, and sorted in the correct order.
$this->assertCount(count($expectedorder), $fetcheddetails);
$this->assertTrue((array_keys($fetcheddetails) === $expectedorder));
}
}
@@ -0,0 +1,291 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
/**
* Test completion criteria.
*
* @package core_completion
* @category test
* @copyright 2021 Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_criteria_test extends \advanced_testcase {
/**
* Test setup.
*/
public function setUp(): void {
global $CFG;
require_once($CFG->dirroot.'/completion/criteria/completion_criteria.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
$this->setAdminUser();
$this->resetAfterTest();
}
/**
* Test that activity completion dates are used when activity criteria is marked as completed.
*/
public function test_completion_criteria_activity(): void {
global $DB;
$timestarted = time();
// Create a course, an activity and enrol a user.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['completion' => 1]);
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Set completion criteria and mark the user to complete the criteria.
$criteriadata = (object) [
'id' => $course->id,
'criteria_activity' => [$assign->cmid => 1],
];
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
$cmassign = get_coursemodule_from_id('assign', $assign->cmid);
$completion = new \completion_info($course);
$completion->update_state($cmassign, COMPLETION_COMPLETE, $user->id);
// Completion criteria for the user is supposed to be marked as completed at now().
$result = \core_completion_external::get_activities_completion_status($course->id, $user->id);
$actual = reset($result['statuses']);
$this->assertEquals(1, $actual['state']);
$this->assertGreaterThanOrEqual($timestarted, $actual['timecompleted']);
// And the whole course is marked as completed at now().
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertGreaterThanOrEqual($timestarted, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
}
/**
* Test that enrolment timestart are used when duration criteria is marked as completed.
*/
public function test_completion_criteria_duration_timestart(): void {
global $DB;
$timestarted = 1610000000;
$durationperiod = DAYSECS;
// Create a course and users.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestarted);
// Set completion criteria.
$criteriadata = (object) [
'id' => $course->id,
'criteria_duration' => 1,
'criteria_duration_days' => $durationperiod,
];
$criterion = new \completion_criteria_duration();
$criterion->update_config($criteriadata);
// Run completion scheduled task.
$task = new \core\task\completion_regular_task();
$this->expectOutputRegex("/Marking complete/");
$task->execute();
// Hopefully, some day MDL-33320 will be fixed and all these sleeps
// and double cron calls in behat and unit tests will be removed.
sleep(1);
$task->execute();
// The course for User is supposed to be marked as completed at $timestarted + $durationperiod.
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertEquals($timestarted + $durationperiod, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
// Now we want to check the scenario where "now" sits in the middle of the timestart + duration
// and timecreated + duration window.
$nowtime = time();
$timestarted = $nowtime - $durationperiod + (2 * DAYSECS);
$timecreated = $nowtime - $durationperiod - (2 * DAYSECS);
// Using a new user for this.
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestarted);
// We need to manually update the enrollment's time created.
$DB->set_field('user_enrolments', 'timecreated', $timecreated, ['userid' => $user->id]);
// Run the completion cron. See MDL-33320.
$task->execute();
sleep(1);
$task->execute();
// We do NOT expect the user to be complete currently.
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertFalse($ccompletion->is_complete());
// Now, finally, we will move the timestart to be in the past, but still after the timecreated.
$timestarted = $timecreated + DAYSECS;
$DB->set_field('user_enrolments', 'timestart', $timestarted, ['userid' => $user->id]);
// Run the completion cron. See MDL-33320.
$task->execute();
sleep(1);
$task->execute();
// Now they should be complete.
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertEquals($timestarted + $durationperiod, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
}
/**
* Test that enrolment timecreated are used when duration criteria is marked as completed.
*/
public function test_completion_criteria_duration_timecreated(): void {
global $DB;
$timecreated = 1620000000;
$durationperiod = DAYSECS;
// Create a course and users.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
// Create and enrol user with an empty time start, but update the record like it was created at $timecreated.
$user = $this->getDataGenerator()->create_and_enrol($course);
$DB->set_field('user_enrolments', 'timecreated', $timecreated, ['userid' => $user->id]);
// Set completion criteria.
$criteriadata = (object) [
'id' => $course->id,
'criteria_duration' => 1,
'criteria_duration_days' => $durationperiod,
];
$criterion = new \completion_criteria_duration();
$criterion->update_config($criteriadata);
// Run completion scheduled task.
$task = new \core\task\completion_regular_task();
$this->expectOutputRegex("/Marking complete/");
$task->execute();
// Hopefully, some day MDL-33320 will be fixed and all these sleeps
// and double cron calls in behat and unit tests will be removed.
sleep(1);
$task->execute();
// The course for user is supposed to be marked as completed at $timecreated + $durationperiod.
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertEquals($timecreated + $durationperiod, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
}
/**
* Test that criteria date is used as a course completion date.
*/
public function test_completion_criteria_date(): void {
global $DB;
$timeend = 1610000000;
// Create a course and enrol a user.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Set completion criteria.
$criteriadata = (object) [
'id' => $course->id,
'criteria_date' => 1,
'criteria_date_value' => $timeend,
];
$criterion = new \completion_criteria_date();
$criterion->update_config($criteriadata);
// Run completion scheduled task.
$task = new \core\task\completion_regular_task();
$this->expectOutputRegex("/Marking complete/");
$task->execute();
// Hopefully, some day MDL-33320 will be fixed and all these sleeps
// and double cron calls in behat and unit tests will be removed.
sleep(1);
$task->execute();
// The course is supposed to be marked as completed at $timeend.
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$this->assertEquals($timeend, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
}
/**
* Test that grade timemodified is used when grade criteria is marked as completed.
*/
public function test_completion_criteria_grade(): void {
global $DB;
$timegraded = 1610000000;
// Create a course and enrol a couple of users.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id);
// Set completion criteria.
$criteriadata = (object) [
'id' => $course->id,
'criteria_grade' => 1,
'criteria_grade_value' => 66,
];
$criterion = new \completion_criteria_grade();
$criterion->update_config($criteriadata);
$coursegradeitem = \grade_item::fetch_course_item($course->id);
// Grade User 1 with a passing grade.
$grade1 = new \grade_grade();
$grade1->itemid = $coursegradeitem->id;
$grade1->timemodified = $timegraded;
$grade1->userid = $user1->id;
$grade1->finalgrade = 80;
$grade1->insert();
// Grade User 2 with a non-passing grade.
$grade2 = new \grade_grade();
$grade2->itemid = $coursegradeitem->id;
$grade2->timemodified = $timegraded;
$grade2->userid = $user2->id;
$grade2->finalgrade = 40;
$grade2->insert();
// Run completion scheduled task.
$task = new \core\task\completion_regular_task();
$this->expectOutputRegex("/Marking complete/");
$task->execute();
// Hopefully, some day MDL-33320 will be fixed and all these sleeps
// and double cron calls in behat and unit tests will be removed.
sleep(1);
$task->execute();
// The course for User 1 is supposed to be marked as completed when the user was graded.
$ccompletion = new \completion_completion(['userid' => $user1->id, 'course' => $course->id]);
$this->assertEquals($timegraded, $ccompletion->timecompleted);
$this->assertTrue($ccompletion->is_complete());
// The course for User 2 is supposed to be marked as not completed.
$ccompletion = new \completion_completion(['userid' => $user2->id, 'course' => $course->id]);
$this->assertFalse($ccompletion->is_complete());
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
/**
* Coverage information for the core_completion.
*
* @package core
* @category phpunit
* @copyright 2022 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Coverage information for the core subsystem.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
/** @var array The list of folders relative to the plugin root to include in coverage generation. */
protected $includelistfolders = [
'criteria',
];
/** @var array The list of files relative to the plugin root to include in coverage generation. */
protected $includelistfiles = [
'completion_aggregation.php',
'completion_completion.php',
'completion_criteria_completion.php',
'data_object.php'
];
};
+607
View File
@@ -0,0 +1,607 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
use core_completion_external;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External completion functions unit tests
*
* @package core_completion
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.9
* @coversDefaultClass \core_completion_external
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Test update_activity_completion_status_manually
*/
public function test_update_activity_completion_status_manually(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$CFG->enablecompletion = true;
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$cm = get_coursemodule_from_id('data', $data->cmid);
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
$this->setUser($user);
$result = core_completion_external::update_activity_completion_status_manually($data->cmid, true);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::update_activity_completion_status_manually_returns(), $result);
// Check in DB.
$this->assertEquals(1, $DB->get_field('course_modules_completion', 'completionstate',
array('coursemoduleid' => $data->cmid)));
// Check using the API.
$completion = new \completion_info($course);
$completiondata = $completion->get_data($cm);
$this->assertEquals(1, $completiondata->completionstate);
$this->assertTrue($result['status']);
$result = core_completion_external::update_activity_completion_status_manually($data->cmid, false);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::update_activity_completion_status_manually_returns(), $result);
$this->assertEquals(0, $DB->get_field('course_modules_completion', 'completionstate',
array('coursemoduleid' => $data->cmid)));
$completiondata = $completion->get_data($cm);
$this->assertEquals(0, $completiondata->completionstate);
$this->assertTrue($result['status']);
}
/**
* Test update_activity_completion_status
*/
public function test_get_activities_completion_status(): void {
global $DB, $CFG, $PAGE;
$this->resetAfterTest(true);
$CFG->enablecompletion = true;
$student = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
'groupmode' => SEPARATEGROUPS,
'groupmodeforce' => 1));
\availability_completion\condition::wipe_static_cache();
$data = $this->getDataGenerator()->create_module('data',
['course' => $course->id],
['completion' => COMPLETION_TRACKING_MANUAL],
);
$forum = $this->getDataGenerator()->create_module('forum',
['course' => $course->id],
['completion' => COMPLETION_TRACKING_MANUAL],
);
$forumautocompletion = $this->getDataGenerator()->create_module('forum',
['course' => $course->id],
['showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC],
);
$availability = '{"op":"&","c":[{"type":"completion","cm":' . $forum->cmid .',"e":1}],"showc":[true]}';
$assign = $this->getDataGenerator()->create_module('assign',
['course' => $course->id],
['availability' => $availability],
);
$assignautocompletion = $this->getDataGenerator()->create_module('assign',
['course' => $course->id], [
'showdescription' => true,
'completionview' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completiongradeitemnumber' => 1,
'completionpassgrade' => 1,
],
);
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
array('completion' => 1, 'visible' => 0));
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$cmforum = get_coursemodule_from_id('forum', $forum->cmid);
$cmforumautocompletion = get_coursemodule_from_id('forum', $forumautocompletion->cmid);
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
// Teacher and student in different groups initially.
groups_add_member($group1->id, $student->id);
groups_add_member($group2->id, $teacher->id);
$this->setUser($student);
// Forum complete.
$completion = new \completion_info($course);
$completion->update_state($cmforum, COMPLETION_COMPLETE);
$result = core_completion_external::get_activities_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 6 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 6;
$numberofhidden = 1;
$numberofcompletions = $numberofactivities - $numberofhidden;
$numberofstatusstudent = 4;
$this->assertCount($numberofstatusstudent, $result['statuses']);
$activitiesfound = 0;
foreach ($result['statuses'] as $status) {
if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_COMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertTrue($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertFalse($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(0, $details);
$this->assertTrue($status['isoverallcomplete']);
} else if ($status['cmid'] == $forumautocompletion->cmid) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertTrue($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(1, $details);
$this->assertEquals('completionview', $details[0]['rulename']);
$this->assertEquals(0, $details[0]['rulevalue']['status']);
$this->assertFalse($status['isoverallcomplete']);
} else if ($status['cmid'] == $assignautocompletion->cmid) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertTrue($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$this->assertFalse($status['isoverallcomplete']);
$details = $status['details'];
$this->assertCount(3, $details);
$expecteddetails = [
'completionview',
'completionusegrade',
'completionpassgrade',
];
foreach ($expecteddetails as $index => $name) {
$this->assertEquals($name, $details[$index]['rulename']);
$this->assertEquals(0, $details[$index]['rulevalue']['status']);
}
} else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertFalse($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(0, $details);
$this->assertFalse($status['isoverallcomplete']);
}
}
$this->assertEquals(4, $activitiesfound);
// Teacher should see students status, they are in different groups but the teacher can access all groups.
$this->setUser($teacher);
$result = core_completion_external::get_activities_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
$this->assertCount($numberofcompletions, $result['statuses']);
// Override status by teacher.
$completion->update_state($cmforum, COMPLETION_INCOMPLETE, $student->id, true);
$result = core_completion_external::get_activities_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// Check forum has been overriden by the teacher.
foreach ($result['statuses'] as $status) {
if ($status['cmid'] == $forum->cmid) {
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertEquals($teacher->id, $status['overrideby']);
$this->assertFalse($status['isoverallcomplete']);
break;
}
}
// Teacher should see his own completion status.
// Forum complete for teacher.
$completion = new \completion_info($course);
$completion->update_state($cmforum, COMPLETION_COMPLETE);
$result = core_completion_external::get_activities_completion_status($course->id, $teacher->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
$this->assertCount($numberofcompletions, $result['statuses']);
$activitiesfound = 0;
foreach ($result['statuses'] as $status) {
if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_COMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertTrue($status['isoverallcomplete']);
} else if (in_array($status['cmid'], [$forumautocompletion->cmid, $assignautocompletion->cmid])) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
$this->assertFalse($status['isoverallcomplete']);
} else {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertFalse($status['isoverallcomplete']);
}
}
$this->assertEquals(5, $activitiesfound);
// Change teacher role capabilities (disable access all groups).
$context = \context_course::instance($course->id);
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
accesslib_clear_all_caches_for_unit_testing();
try {
$result = core_completion_external::get_activities_completion_status($course->id, $student->id);
$this->fail('Exception expected due to groups permissions.');
} catch (\moodle_exception $e) {
$this->assertEquals('accessdenied', $e->errorcode);
}
// Now add the teacher in the same group.
groups_add_member($group1->id, $teacher->id);
$result = core_completion_external::get_activities_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
$this->assertCount($numberofcompletions, $result['statuses']);
}
/**
* Test override_activity_completion_status
*/
public function test_override_activity_completion_status(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
// Create course with teacher and student enrolled.
$CFG->enablecompletion = true;
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$student = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
// Create 2 activities, one with manual completion (data), one with automatic completion triggered by viewing it (forum).
$data = $this->getDataGenerator()->create_module('data', ['course' => $course->id], ['completion' => 1]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id],
['completion' => 2, 'completionview' => 1]);
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$cmforum = get_coursemodule_from_id('forum', $forum->cmid);
// Manually complete the data activity as the student.
$this->setUser($student);
$completion = new \completion_info($course);
$completion->update_state($cmdata, COMPLETION_COMPLETE);
// Test overriding the status of the manual-completion-activity 'incomplete'.
$this->setUser($teacher);
$result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_INCOMPLETE);
$result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
$this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
$completiondata = $completion->get_data($cmdata, false, $student->id);
$this->assertEquals(COMPLETION_INCOMPLETE, $completiondata->completionstate);
// Test overriding the status of the manual-completion-activity back to 'complete'.
$result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_COMPLETE);
$result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
$this->assertEquals($result['state'], COMPLETION_COMPLETE);
$completiondata = $completion->get_data($cmdata, false, $student->id);
$this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate);
// Test overriding the status of the auto-completion-activity to 'complete'.
$result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
$result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
$this->assertEquals($result['state'], COMPLETION_COMPLETE);
$completionforum = $completion->get_data($cmforum, false, $student->id);
$this->assertEquals(COMPLETION_COMPLETE, $completionforum->completionstate);
// Test overriding the status of the auto-completion-activity to 'incomplete'.
$result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_INCOMPLETE);
$result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
$this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
$completionforum = $completion->get_data($cmforum, false, $student->id);
$this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
// Test overriding the status of the auto-completion-activity to an invalid state.
$this->expectException('moodle_exception');
core_completion_external::override_activity_completion_status($student->id, $forum->cmid, 3);
}
/**
* Test overriding the activity completion status as a user without the capability to do so.
*/
public function test_override_status_user_without_capability(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
// Create course with teacher and student enrolled.
$CFG->enablecompletion = true;
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$student = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$coursecontext = \context_course::instance($course->id);
// Create an activity with automatic completion (a forum).
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id],
['completion' => 2, 'completionview' => 1]);
// Test overriding the status of the activity for a user without the capability.
$this->setUser($teacher);
assign_capability('moodle/course:overridecompletion', CAP_PREVENT, $teacherrole->id, $coursecontext);
$this->expectException('required_capability_exception');
core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
}
/**
* Test get_course_completion_status
*/
public function test_get_course_completion_status(): void {
global $DB, $CFG, $COMPLETION_CRITERIA_TYPES;
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_unenrol.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_role.php');
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
$this->resetAfterTest(true);
$CFG->enablecompletion = true;
$student = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
'groupmode' => SEPARATEGROUPS,
'groupmodeforce' => 1));
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$cmforum = get_coursemodule_from_id('forum', $forum->cmid);
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
// Teacher and student in different groups initially.
groups_add_member($group1->id, $student->id);
groups_add_member($group2->id, $teacher->id);
// Set completion rules.
$completion = new \completion_info($course);
// Loop through each criteria type and run its update_config() method.
$criteriadata = new \stdClass();
$criteriadata->id = $course->id;
$criteriadata->criteria_activity = array();
// Some activities.
$criteriadata->criteria_activity[$cmdata->id] = 1;
$criteriadata->criteria_activity[$cmforum->id] = 1;
// In a week criteria date value.
$criteriadata->criteria_date_value = time() + WEEKSECS;
// Self completion.
$criteriadata->criteria_self = 1;
foreach ($COMPLETION_CRITERIA_TYPES as $type) {
$class = 'completion_criteria_'.$type;
$criterion = new $class();
$criterion->update_config($criteriadata);
}
// Handle overall aggregation.
$aggdata = array(
'course' => $course->id,
'criteriatype' => null
);
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
$aggregation->save();
$aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ACTIVITY;
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
$aggregation->save();
$this->setUser($student);
$result = core_completion_external::get_course_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$studentresult = external_api::clean_returnvalue(
core_completion_external::get_course_completion_status_returns(), $result);
// 3 different criteria.
$this->assertCount(3, $studentresult['completionstatus']['completions']);
$this->assertEquals(COMPLETION_AGGREGATION_ALL, $studentresult['completionstatus']['aggregation']);
$this->assertFalse($studentresult['completionstatus']['completed']);
$this->assertEquals('No', $studentresult['completionstatus']['completions'][0]['status']);
$this->assertEquals('No', $studentresult['completionstatus']['completions'][1]['status']);
$this->assertEquals('No', $studentresult['completionstatus']['completions'][2]['status']);
// Teacher should see students status, they are in different groups but the teacher can access all groups.
$this->setUser($teacher);
$result = core_completion_external::get_course_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$teacherresult = external_api::clean_returnvalue(
core_completion_external::get_course_completion_status_returns(), $result);
$this->assertEquals($studentresult, $teacherresult);
// Change teacher role capabilities (disable access al goups).
$context = \context_course::instance($course->id);
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
accesslib_clear_all_caches_for_unit_testing();
try {
$result = core_completion_external::get_course_completion_status($course->id, $student->id);
$this->fail('Exception expected due to groups permissions.');
} catch (\moodle_exception $e) {
$this->assertEquals('accessdenied', $e->errorcode);
}
// Now add the teacher in the same group.
groups_add_member($group1->id, $teacher->id);
$result = core_completion_external::get_course_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$teacherresult = external_api::clean_returnvalue(
core_completion_external::get_course_completion_status_returns(), $result);
$this->assertEquals($studentresult, $teacherresult);
}
/**
* Test mark_course_self_completed
*/
public function test_mark_course_self_completed(): void {
global $DB, $CFG;
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
$this->resetAfterTest(true);
$CFG->enablecompletion = true;
$student = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
// Set completion rules.
$completion = new \completion_info($course);
$criteriadata = new \stdClass();
$criteriadata->id = $course->id;
$criteriadata->criteria_activity = array();
// Self completion.
$criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
$class = 'completion_criteria_self';
$criterion = new $class();
$criterion->update_config($criteriadata);
// Handle overall aggregation.
$aggdata = array(
'course' => $course->id,
'criteriatype' => null
);
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
$aggregation->save();
$this->setUser($student);
$result = core_completion_external::mark_course_self_completed($course->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::mark_course_self_completed_returns(), $result);
// We expect a valid result.
$this->assertEquals(true, $result['status']);
$result = core_completion_external::get_course_completion_status($course->id, $student->id);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_course_completion_status_returns(), $result);
// Course must be completed.
$this->assertEquals(COMPLETION_COMPLETE, $result['completionstatus']['completions'][0]['complete']);
try {
$result = core_completion_external::mark_course_self_completed($course->id);
$this->fail('Exception expected due course already self completed.');
} catch (\moodle_exception $e) {
$this->assertEquals('useralreadymarkedcomplete', $e->errorcode);
}
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Trait for course completion creation in unit tests
*
* @package core_completion
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/completion/criteria/completion_criteria.php');
require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php');
require_once($CFG->dirroot . '/completion/criteria/completion_criteria_role.php');
/**
* Trait for unit tests and completion.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait completion_creation {
/** @var stdClass The course object. */
public $course;
/** @var context The course context object. */
public $coursecontext;
/** @var stdClass The course module object */
public $cm;
/**
* Create completion information.
*/
public function create_course_completion() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$coursecontext = context_course::instance($course->id);
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'completion' => 1]);
$modulecontext = context_module::instance($assign->cmid);
$cm = get_coursemodule_from_id('assign', $assign->cmid);
// Set completion rules.
$completion = new \completion_info($course);
$criteriadata = (object) [
'id' => $course->id,
'criteria_activity' => [
$cm->id => 1
]
];
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
$criteriadata = (object) [
'id' => $course->id,
'criteria_role' => [3 => 3]
];
$criterion = new \completion_criteria_role();
$criterion->update_config($criteriadata);
// Handle overall aggregation.
$aggdata = array(
'course' => $course->id,
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY
);
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
$aggregation->save();
$aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ROLE;
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ANY);
$aggregation->save();
// Set variables for access in tests.
$this->course = $course;
$this->coursecontext = $coursecontext;
$this->cm = $cm;
}
/**
* Complete some of the course completion criteria.
*
* @param stdClass $user The user object
* @param bool $modulecompletion If true will complete the activity module completion thing.
*/
public function complete_course($user, $modulecompletion = true) {
$this->getDataGenerator()->enrol_user($user->id, $this->course->id, 'student');
$completion = new \completion_info($this->course);
$criteriacompletions = $completion->get_completions($user->id, COMPLETION_CRITERIA_TYPE_ROLE);
$criteria = completion_criteria::factory(['id' => 3, 'criteriatype' => COMPLETION_CRITERIA_TYPE_ROLE]);
foreach ($criteriacompletions as $ccompletion) {
$criteria->complete($ccompletion);
}
if ($modulecompletion) {
// Set activity as complete.
$completion->update_state($this->cm, COMPLETION_COMPLETE, $user->id);
}
}
}
Binary file not shown.
@@ -0,0 +1,59 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Completion test generator for Behat
*
* @package core_completion
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_completion_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created for completion
*
* @return array[]
*/
protected function get_creatable_entities(): array {
return [
'Course defaults' => [
'singular' => 'Course default',
'datagenerator' => 'default_completion',
'required' => [
'course',
'module',
],
'switchids' => [
'course' => 'course',
'module' => 'module',
],
],
];
}
/**
* Look up module ID from given name
*
* @param string $name
* @return int
*/
protected function get_module_id(string $name): int {
global $DB;
return (int) $DB->get_field('modules', 'id', ['name' => $name], MUST_EXIST);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Completion test generator
*
* @package core_completion
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_completion_generator extends component_generator_base {
/**
* Create default completion
*
* @param array|stdClass $record
* @return stdClass
*/
public function create_default_completion($record): stdClass {
global $DB;
$record = (array) $record;
if (!array_key_exists('course', $record) || !is_numeric($record['course'])) {
throw new moodle_exception('courserequired');
}
if (!$DB->get_record('course', ['id' => $record['course']])) {
throw new moodle_exception('invalidcourseid');
}
if (!array_key_exists('module', $record) || !is_numeric($record['module'])) {
throw new moodle_exception('modulerequired');
}
if (!$DB->get_record('modules', ['id' => $record['module']])) {
throw new moodle_exception('invalidmoduleid', 'error', '', $record['module']);
}
$record = (object) array_merge([
'completion' => 0,
'completionview' => 0,
'completionusegrade' => 0,
'completionpassgrade' => 0,
'completionexpected' => 0,
'customrules' => '',
], $record);
$record->id = $DB->insert_record('course_completion_defaults', $record);
return $record;
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
/**
* PHPUnit data generator testcase
*
* @package core_completion
* @category test
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_completion_generator
*/
class generator_test extends \advanced_testcase {
/**
* Test create_default_completion.
*
* @dataProvider create_default_completion_provider
*
* @param int|null|string $course The course to add the default activities conditions to.
* @param int|null|string $module The module to add the default activities conditions to.
* @param bool $exception Whether an exception is expected or not.
* @param int $count The number of default activity completions to be created.
* @param int $completion The value for completion setting.
*
* @covers ::create_default_completion
*/
public function test_create_default_completion($course, $module, bool $exception, int $count, int $completion = 0): void {
global $DB;
$this->resetAfterTest(true);
$generator = $this->getDataGenerator()->get_plugin_generator('core_completion');
$record = [
'course' => $course,
'module' => $module,
'completion' => $completion,
];
$result = (object) array_merge([
'completion' => 0,
'completionview' => 0,
'completionusegrade' => 0,
'completionpassgrade' => 0,
'completionexpected' => 0,
'customrules' => '',
], $record);
if ($exception) {
$this->expectException('moodle_exception');
}
$defaultcompletion = $generator->create_default_completion($record);
if (!$exception) {
foreach ($result as $key => $value) {
$this->assertEquals($defaultcompletion->{$key}, $value);
}
}
$this->assertEquals(
$count,
$DB->count_records('course_completion_defaults', ['course' => $course, 'module' => $module])
);
}
/**
* Data provider for test_create_default_completion().
* @return array[]
*/
public function create_default_completion_provider(): array {
global $SITE;
return [
'Null course' => [
'course' => null,
'module' => null,
'exception' => true,
'count' => 0,
],
'Empty course' => [
'course' => '',
'module' => null,
'exception' => true,
'count' => 0,
],
'Invalid course' => [
'course' => 0,
'module' => null,
'exception' => true,
'count' => 0,
],
'Null module' => [
'course' => $SITE->id,
'module' => null,
'exception' => true,
'count' => 0,
],
'Empty module' => [
'course' => $SITE->id,
'module' => null,
'exception' => true,
'count' => 0,
],
'Invalid module' => [
'course' => $SITE->id,
'module' => 0,
'exception' => true,
'count' => 0,
],
'Default activity completion: NONE' => [
'course' => $SITE->id,
'module' => 1,
'exception' => false,
'count' => 1,
],
'Default activity completion: AUTOMATIC' => [
'course' => $SITE->id,
'module' => 1,
'exception' => false,
'count' => 1,
'completion' => 2,
],
];
}
}
+224
View File
@@ -0,0 +1,224 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit Tests for the request helper.
*
* @package core_completion
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_completion\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/completion/tests/fixtures/completion_creation.php');
/**
* Tests for the \core_completion API's provider functionality.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
use \completion_creation;
/**
* Test joining course completion data to an sql statement.
*/
public function test_get_course_completion_join_sql(): void {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user, false);
list($join, $where, $params) = \core_completion\privacy\provider::get_course_completion_join_sql($user->id, 'comp', 'c.id');
$sql = "SELECT DISTINCT c.id
FROM {course} c
{$join}
WHERE {$where}";
$records = $DB->get_records_sql($sql, $params);
$data = array_shift($records);
$this->assertEquals($this->course->id, $data->id);
}
/**
* Test fetching users' course completion by context and adding to a userlist.
*/
public function test_add_course_completion_users_to_userlist(): void {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
// User1 and user2 complete course.
$this->create_course_completion();
$this->complete_course($user1);
$this->complete_course($user2);
// User3 is enrolled but has not completed course.
$this->getDataGenerator()->enrol_user($user3->id, $this->course->id, 'student');
$userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'test');
\core_completion\privacy\provider::add_course_completion_users_to_userlist($userlist);
// Ensure only users that have course completion are returned.
$expected = [$user1->id, $user2->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);
$this->assertCount(2, $actual);
$this->assertEquals($expected, $actual);
}
/**
* Test getting course completion information.
*/
public function test_get_course_completion_info(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $this->course);
$this->assertEquals('Complete', $coursecompletion['status']);
$this->assertCount(2, $coursecompletion['criteria']);
}
/**
* Test getting activity completion information.
*/
public function test_get_activity_completion_info(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
$activitycompletion = \core_completion\privacy\provider::get_activity_completion_info($user, $this->course,
$this->cm);
$this->assertEquals($user->id, $activitycompletion->userid);
$this->assertEquals($this->cm->id, $activitycompletion->coursemoduleid);
$this->assertEquals(1, $activitycompletion->completionstate);
}
/**
* Test deleting activity completion information for a user.
*/
public function test_delete_completion_activity_user(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
\core_completion\privacy\provider::delete_completion($user, null, $this->cm->id);
$activitycompletion = \core_completion\privacy\provider::get_activity_completion_info($user, $this->course,
$this->cm);
$this->assertEquals(0, $activitycompletion->completionstate);
}
/**
* Test deleting course completion information.
*/
public function test_delete_completion_course(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
\core_completion\privacy\provider::delete_completion(null, $this->course->id);
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $this->course);
foreach ($coursecompletion['criteria'] as $criterion) {
$this->assertEquals('No', $criterion['completed']);
}
}
/**
* Test deleting course completion information by approved userlist.
*/
public function test_delete_completion_by_approved_userlist(): void {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$user4 = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user1);
$this->complete_course($user2);
$this->complete_course($user3);
$this->complete_course($user4);
// Prepare approved userlist (context/component are irrelevant for this test).
$approveduserids = [$user1->id, $user3->id];
$userlist = new \core_privacy\local\request\approved_userlist($this->coursecontext, 'completion', $approveduserids);
// Test deleting activity completion information only affects approved userlist.
\core_completion\privacy\provider::delete_completion_by_approved_userlist(
$userlist, null, $this->cm->id);
$activitycompletion1 = \core_completion\privacy\provider::get_activity_completion_info($user1, $this->course,
$this->cm);
$this->assertEquals(0, $activitycompletion1->completionstate);
$activitycompletion2 = \core_completion\privacy\provider::get_activity_completion_info($user2, $this->course,
$this->cm);
$this->assertNotEquals(0, $activitycompletion2->completionstate);
$activitycompletion3 = \core_completion\privacy\provider::get_activity_completion_info($user3, $this->course,
$this->cm);
$this->assertEquals(0, $activitycompletion3->completionstate);
$activitycompletion4 = \core_completion\privacy\provider::get_activity_completion_info($user4, $this->course,
$this->cm);
$this->assertNotEquals(0, $activitycompletion4->completionstate);
// Prepare different approved userlist (context/component are irrelevant for this test).
$approveduserids = [$user2->id, $user4->id];
$userlist = new \core_privacy\local\request\approved_userlist($this->coursecontext, 'completion', $approveduserids);
// Test deleting course completion information only affects approved userlist.
\core_completion\privacy\provider::delete_completion_by_approved_userlist($userlist, $this->course->id);
$coursecompletion1 = \core_completion\privacy\provider::get_course_completion_info($user1, $this->course);
$hasno = array_search('No', $coursecompletion1['criteria'], true);
$this->assertFalse($hasno);
$coursecompletion2 = \core_completion\privacy\provider::get_course_completion_info($user2, $this->course);
$hasyes = array_search('Yes', $coursecompletion2['criteria'], true);
$this->assertFalse($hasyes);
$coursecompletion3 = \core_completion\privacy\provider::get_course_completion_info($user3, $this->course);
$hasno = array_search('No', $coursecompletion3['criteria'], true);
$this->assertFalse($hasno);
$coursecompletion4 = \core_completion\privacy\provider::get_course_completion_info($user4, $this->course);
$hasyes = array_search('Yes', $coursecompletion4['criteria'], true);
$this->assertFalse($hasyes);
}
/**
* Test getting course completion information with completion disabled.
*/
public function test_get_course_completion_info_completion_disabled(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 0]);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $course);
$this->assertTrue(is_array($coursecompletion));
$this->assertEmpty($coursecompletion);
}
}
+286
View File
@@ -0,0 +1,286 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_completion;
use completion_completion;
/**
* Test completion progress API.
*
* @package core_completion
* @category test
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class progress_test extends \advanced_testcase {
/**
* Test setup.
*/
public function setUp(): void {
global $CFG;
$CFG->enablecompletion = true;
$this->resetAfterTest();
}
/**
* Tests that the course progress percentage is returned correctly when we have only activity completion.
*/
public function test_course_progress_percentage_with_just_activities(): void {
global $DB;
// Add a course that supports completion.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Enrol a user in the course.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Add four activities that use completion.
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id),
array('completion' => 1));
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
$this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
// Add an activity that does *not* use completion.
$this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Mark two of them as completed for a user.
$cmassign = get_coursemodule_from_id('assign', $assign->cmid);
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$completion = new \completion_info($course);
$completion->update_state($cmassign, COMPLETION_COMPLETE, $user->id);
$completion->update_state($cmdata, COMPLETION_COMPLETE, $user->id);
// Check we have received valid data.
// Note - only 4 out of the 5 activities support completion, and the user has completed 2 of those.
$this->assertEquals('50', \core_completion\progress::get_course_progress_percentage($course, $user->id));
}
/**
* Tests that the course progress percentage is returned correctly when we have a course and activity completion.
*/
public function test_course_progress_percentage_with_activities_and_course(): void {
global $DB;
// Add a course that supports completion.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Enrol a user in the course.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Add four activities that use completion.
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id),
array('completion' => 1));
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
$this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
// Add an activity that does *not* use completion.
$this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Mark two of them as completed for a user.
$cmassign = get_coursemodule_from_id('assign', $assign->cmid);
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$completion = new \completion_info($course);
$completion->update_state($cmassign, COMPLETION_COMPLETE, $user->id);
$completion->update_state($cmdata, COMPLETION_COMPLETE, $user->id);
// Now, mark the course as completed.
$ccompletion = new completion_completion(array('course' => $course->id, 'userid' => $user->id));
$ccompletion->mark_complete();
// Check we have received valid data.
// The course completion takes priority, so should return 100.
$this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id));
}
/**
* Tests that the course progress percentage is returned correctly for various grade to pass settings
*
* @covers \core_completion\progress::get_course_progress_percentage.
*/
public function test_course_progress_percentage_completion_state(): void {
global $DB, $CFG;
require_once("{$CFG->dirroot}/completion/criteria/completion_criteria_activity.php");
// Add a course that supports completion.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
// Enrol a user in the course.
$teacher = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
// Add three activities that use completion.
/** @var \mod_assign_generator $assigngenerator */
$assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$assign['passgragepassed'] = $assigngenerator->create_instance([
'course' => $course->id,
'completion' => COMPLETION_ENABLED,
'completionusegrade' => 1,
'gradepass' => 50,
'completionpassgrade' => 1
]);
$assign['passgragefailed'] = $assigngenerator->create_instance([
'course' => $course->id,
'completion' => COMPLETION_ENABLED,
'completionusegrade' => 1,
'gradepass' => 50,
'completionpassgrade' => 1
]);
$assign['passgragenotused'] = $assigngenerator->create_instance([
'course' => $course->id,
'completion' => COMPLETION_ENABLED,
'completionusegrade' => 1,
'gradepass' => 50,
]);
$assign['nograde'] = $assigngenerator->create_instance([
'course' => $course->id,
'completion' => COMPLETION_ENABLED,
]);
$c = new \completion_info($course);
foreach ($assign as $item) {
$cmassing = get_coursemodule_from_id('assign', $item->cmid);
// Add activity completion criteria.
$criteriadata = new \stdClass();
$criteriadata->id = $course->id;
$criteriadata->criteria_activity = [];
// Some activities.
$criteriadata->criteria_activity[$cmassing->id] = 1;
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
}
$this->setUser($teacher);
foreach ($assign as $key => $item) {
$cm = get_coursemodule_from_instance('assign', $item->id);
// Mark user completions.
$completion = new \stdClass();
$completion->coursemoduleid = $cm->id;
$completion->timemodified = time();
$completion->viewed = COMPLETION_NOT_VIEWED;
$completion->overrideby = null;
if ($key == 'passgragepassed') {
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE_PASS;
$completion->userid = $user->id;
$c->internal_set_data($cm, $completion, true);
} else if ($key == 'passgragefailed') {
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE_FAIL;
$completion->userid = $user->id;
$c->internal_set_data($cm, $completion, true);
} else if ($key == 'passgragenotused') {
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE;
$completion->userid = $user->id;
$c->internal_set_data($cm, $completion, true);
} else if ($key == 'nograde') {
$completion->id = 0;
$completion->completionstate = COMPLETION_COMPLETE;
$completion->userid = $user->id;
$c->internal_set_data($cm, $completion, true);
}
}
// Run course completions cron.
\core_completion\api::mark_course_completions_activity_criteria();
// Check we have received valid data.
// Only assign2 is not completed.
$this->assertEquals('75', \core_completion\progress::get_course_progress_percentage($course, $user->id));
}
/**
* Tests that the course progress returns null when the course does not support it.
*/
public function test_course_progress_course_not_using_completion(): void {
// Create a course that does not use completion.
$course = $this->getDataGenerator()->create_course();
// Check that the result was null.
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course));
}
/**
* Tests that the course progress returns null when there are no activities that support it.
*/
public function test_course_progress_no_activities_using_completion(): void {
// Create a course that does support completion.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Add an activity that does *not* support completion.
$this->getDataGenerator()->create_module('assign', array('course' => $course->id));
// Check that the result was null.
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course));
}
/**
* Tests that the course progress returns null for a not tracked for completion user in a course.
*/
public function test_course_progress_not_tracked_user(): void {
global $DB;
// Add a course that supports completion.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Enrol a user in the course.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Now, mark the course as completed.
$ccompletion = new completion_completion(array('course' => $course->id, 'userid' => $user->id));
$ccompletion->mark_complete();
// The course completion should return 100.
$this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id));
// Now make the user's role to be not tracked for completion.
unassign_capability('moodle/course:isincompletionreports', $studentrole->id);
// Check that the result is null now.
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course, $user->id));
}
}