diff --git a/course/format/tests/local/overview/overviewfactory_test.php b/course/format/tests/local/overview/overviewfactory_test.php index 7efa1d263f6..faa339efec7 100644 --- a/course/format/tests/local/overview/overviewfactory_test.php +++ b/course/format/tests/local/overview/overviewfactory_test.php @@ -121,7 +121,7 @@ final class overviewfactory_test extends \advanced_testcase { ], 'lesson' => [ 'resourcetype' => 'lesson', - 'expected' => resourceoverview::class, + 'expected' => \mod_lesson\courseformat\overview::class, ], 'lti' => [ 'resourcetype' => 'lti', diff --git a/course/format/tests/output/local/overview/missingoverviewnotice_test.php b/course/format/tests/output/local/overview/missingoverviewnotice_test.php index 3b4dbe5e42f..41e59f4ebae 100644 --- a/course/format/tests/output/local/overview/missingoverviewnotice_test.php +++ b/course/format/tests/output/local/overview/missingoverviewnotice_test.php @@ -75,7 +75,7 @@ final class missingoverviewnotice_test extends \advanced_testcase { 'h5pactivity' => ['modname' => 'h5pactivity', 'expectempty' => true], 'imscp' => ['modname' => 'imscp', 'expectempty' => false], 'label' => ['modname' => 'label', 'expectempty' => false], - 'lesson' => ['modname' => 'lesson', 'expectempty' => false], + 'lesson' => ['modname' => 'lesson', 'expectempty' => true], 'lti' => ['modname' => 'lti', 'expectempty' => false], 'page' => ['modname' => 'page', 'expectempty' => false], 'qbank' => ['modname' => 'qbank', 'expectempty' => false], diff --git a/mod/lesson/classes/courseformat/overview.php b/mod/lesson/classes/courseformat/overview.php new file mode 100644 index 00000000000..54ad4a0013f --- /dev/null +++ b/mod/lesson/classes/courseformat/overview.php @@ -0,0 +1,166 @@ +. + +namespace mod_lesson\courseformat; + +use core\output\action_link; +use core\output\local\properties\button; +use core\output\local\properties\text_align; +use core\url; +use core_courseformat\output\local\overview\overviewdialog; +use lesson; +use core_calendar\output\humandate; +use core_courseformat\local\overview\overviewitem; +use cm_info; + +/** + * Class overview + * + * @package mod_lesson + * @copyright 2025 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview extends \core_courseformat\activityoverviewbase { + /** @var array $deadlines user's deadline for all lessons in the course. */ + private array $deadlines; + + /** @var lesson $lesson the lesson instance. */ + private lesson $lesson; + + /** + * Constructor. + * + * @param cm_info $cm the course module instance. + * @param \core\output\renderer_helper $rendererhelper the renderer helper. + * @param \core_string_manager $stringmanager the string manager. + */ + public function __construct( + cm_info $cm, + /** @var \core\output\renderer_helper $rendererhelper the renderer helper */ + protected readonly \core\output\renderer_helper $rendererhelper, + /** @var \core_string_manager $sm the string manager */ + protected readonly \core_string_manager $stringmanager, + ) { + global $CFG; + require_once($CFG->dirroot . '/mod/lesson/locallib.php'); + parent::__construct($cm); + $this->lesson = new lesson($this->cm->get_instance_record()); + $this->deadlines = lesson_get_user_deadline($this->cm->get_course()->id); + } + + #[\Override] + public function get_due_date_overview(): ?overviewitem { + $duedate = null; + if (isset($this->deadlines[$this->lesson->id])) { + $duedate = $this->deadlines[$this->lesson->id]->userdeadline; + } + + return new overviewitem( + name: $this->stringmanager->get_string('duedate', 'mod_lesson'), + value: $duedate, + content: $duedate ? humandate::create_from_timestamp($duedate) : '-', + ); + } + + #[\Override] + public function get_actions_overview(): ?overviewitem { + if (!has_capability('mod/lesson:manage', $this->context)) { + return null; + } + + $content = new action_link( + url: new url('/mod/lesson/report.php', ['id' => $this->cm->id, 'action' => 'reportoverview']), + text: $this->stringmanager->get_string('view', 'mod_lesson'), + attributes: ['class' => button::SECONDARY_OUTLINE->classes()], + ); + + return new overviewitem( + name: $this->stringmanager->get_string('actions'), + value: '', + content: $content, + ); + } + + #[\Override] + public function get_extra_overview_items(): array { + return [ + 'attemptedstudents' => $this->get_extra_attemptedstudents_overview(), + 'totalattempts' => $this->get_extra_totalattempts_overview(), + ]; + } + + /** + * Get the extra overview for attempted students. + * + * @return overviewitem|null The overview item (or null if the user cannot manage lesson activity). + */ + protected function get_extra_attemptedstudents_overview(): ?overviewitem { + if (!has_capability('mod/lesson:manage', $this->context)) { + return null; + } + + $attemptedusers = $this->lesson->count_submitted_participants(); + $totalusers = $this->lesson->count_all_participants(); + + return new overviewitem( + name: $this->stringmanager->get_string('studentswhoattempted', 'mod_lesson'), + value: $attemptedusers, + content: $this->stringmanager->get_string( + 'count_of_total', + 'core', + ['count' => $attemptedusers, 'total' => $totalusers] + ), + ); + } + + /** + * Get the extra overview for attempted students. + * + * @return overviewitem|null The overview item (or null if the user cannot manage lesson activity). + */ + protected function get_extra_totalattempts_overview(): ?overviewitem { + if (!has_capability('mod/lesson:manage', $this->context)) { + return null; + } + + $totalattempts = $this->lesson->count_all_submissions(); + + if ($this->lesson->retake) { + $attemptedusers = $this->lesson->count_submitted_participants(); + + $overviewdialog = new overviewdialog( + buttoncontent: $totalattempts, + description: $this->stringmanager->get_string('retakesallowedinfo', 'mod_lesson'), + definition: ['buttonclasses' => button::SECONDARY_OUTLINE->classes() . ' dropdown-toggle'], + ); + + $averageattempts = $totalattempts ? round($totalattempts / $attemptedusers) : 0; + $overviewdialog->add_item( + $this->stringmanager->get_string('averageattempts', 'mod_lesson'), + $averageattempts + ); + } + + // If the lesson does not allow retakes, do not show the dialog, only the total attepmts number. + // And also, set the value to null, that will hide the whole column if every lesson does not allow retakes. + return new overviewitem( + name: $this->stringmanager->get_string('totalattepmts', 'mod_lesson'), + value: !empty($overviewdialog) ? $totalattempts : null, + content: $overviewdialog ?? $totalattempts, + textalign: text_align::CENTER, + ); + } +} diff --git a/mod/lesson/index.php b/mod/lesson/index.php index 10fed60a097..27a0a80f775 100644 --- a/mod/lesson/index.php +++ b/mod/lesson/index.php @@ -23,100 +23,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later **/ -/** Include required files */ require_once("../../config.php"); -require_once($CFG->dirroot.'/mod/lesson/locallib.php'); -$id = required_param('id', PARAM_INT); // course +$courseid = required_param('id', PARAM_INT); -$PAGE->set_url('/mod/lesson/index.php', array('id'=>$id)); - -if (!$course = $DB->get_record("course", array("id" => $id))) { - throw new \moodle_exception('invalidcourseid'); -} - -require_login($course); -$PAGE->set_pagelayout('incourse'); - -// Trigger instances list viewed event. -$params = array( - 'context' => context_course::instance($course->id) -); -$event = \mod_lesson\event\course_module_instance_list_viewed::create($params); -$event->add_record_snapshot('course', $course); -$event->trigger(); - -/// Get all required strings - -$strlessons = get_string("modulenameplural", "lesson"); -$strlesson = get_string("modulename", "lesson"); - - -/// Print the header -$PAGE->navbar->add($strlessons); -$PAGE->set_title("$course->shortname: $strlessons"); -$PAGE->set_heading($course->fullname); -echo $OUTPUT->header(); -echo $OUTPUT->heading($strlessons, 2); - -/// Get all the appropriate data - -if (! $lessons = get_all_instances_in_course("lesson", $course)) { - notice(get_string('thereareno', 'moodle', $strlessons), "../../course/view.php?id=$course->id"); - die; -} - -$usesections = course_format_uses_sections($course->format); - -/// Print the list of instances (your module will probably extend this) - -$timenow = time(); -$strname = get_string("name"); -$strgrade = get_string("gradenoun"); -$strdeadline = get_string("deadline", "lesson"); -$strnodeadline = get_string("nodeadline", "lesson"); -$table = new html_table(); - -if ($usesections) { - $strsectionname = course_get_format($course)->get_generic_section_name(); - $table->head = array ($strsectionname, $strname, $strgrade, $strdeadline); - $table->align = array ("center", "left", "center", "center"); -} else { - $table->head = array ($strname, $strgrade, $strdeadline); - $table->align = array ("left", "center", "center"); -} -// Get all deadlines. -$deadlines = lesson_get_user_deadline($course->id); -foreach ($lessons as $lesson) { - $cm = get_coursemodule_from_instance('lesson', $lesson->id); - $context = context_module::instance($cm->id); - - $class = $lesson->visible ? null : array('class' => 'dimmed'); // Hidden modules are dimmed. - $link = html_writer::link(new moodle_url('view.php', array('id' => $cm->id)), format_string($lesson->name, true), $class); - - $deadline = $deadlines[$lesson->id]->userdeadline; - if ($deadline == 0) { - $due = $strnodeadline; - } else if ($deadline > $timenow) { - $due = userdate($deadline); - } else { - $due = html_writer::tag('span', userdate($deadline), array('class' => 'text-danger')); - } - - if ($usesections) { - if (has_capability('mod/lesson:manage', $context)) { - $grade_value = $lesson->grade; - } else { - // it's a student, show their grade - $grade_value = 0; - if ($return = lesson_get_user_grades($lesson, $USER->id)) { - $grade_value = $return[$USER->id]->rawgrade; - } - } - $table->data[] = array (get_section_name($course, $lesson->section), $link, $grade_value, $due); - } else { - $table->data[] = array ($link, $lesson->grade, $due); - } -} -echo html_writer::table($table); -echo $OUTPUT->footer(); +\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, 'lesson'); diff --git a/mod/lesson/lang/en/lesson.php b/mod/lesson/lang/en/lesson.php index ac26a880505..b97c1cb6d65 100644 --- a/mod/lesson/lang/en/lesson.php +++ b/mod/lesson/lang/en/lesson.php @@ -61,6 +61,7 @@ $string['addtruefalse'] = 'Create a True/false question page'; $string['allotheranswers'] = 'All other answers'; $string['allotheranswersjump'] = 'All other answers jump'; $string['allotheranswersscore'] = 'All other answers score'; +$string['allowedattempts'] = 'Allowed attempts per student'; $string['allowofflineattempts'] = 'Allow lesson to be attempted offline using the mobile app'; $string['allowofflineattempts_help'] = 'If enabled, a mobile app user can download the lesson and attempt it offline. All the possible answers and correct responses will be downloaded as well. @@ -79,6 +80,7 @@ $string['attempts'] = 'Attempts'; $string['attemptsdeleted'] = 'Deleted attempts'; $string['attemptsremaining'] = 'You have {$a} attempt(s) remaining'; $string['available'] = 'Available from'; +$string['averageattempts'] = 'Average attempts per student'; $string['averagescore'] = 'Average score'; $string['averagetime'] = 'Average time'; $string['branch'] = 'Content'; @@ -175,6 +177,7 @@ $string['displayscorewithessays'] = '

You earned {$a->score} out of {$a->tempm

Your {$a->essayquestions} essay question(s) will be graded and added into your final score at a later date.

Your current grade without the essay question(s) is {$a->score} out of {$a->grade}.

'; $string['displayscorewithoutessays'] = 'Your score is {$a->score} (out of {$a->grade}).'; +$string['duedate'] = 'Due date'; $string['duplicatepagenamed'] = 'Duplicate page: {$a}'; $string['edit'] = 'Edit'; $string['editbranchtable'] = 'Editing a content page'; @@ -522,6 +525,7 @@ $string['reports'] = 'Reports'; $string['response'] = 'Response'; $string['retakesallowed'] = 'Allow multiple attempts'; $string['retakesallowed_help'] = 'Allow students to attempt the lesson more than once.'; +$string['retakesallowedinfo'] = 'This Lesson allows students to do multiple attempts.'; $string['returnto'] = 'Return to {$a}'; $string['returntocourse'] = 'Return to the course'; $string['reverttodefaults'] = 'Revert to lesson defaults'; @@ -560,6 +564,7 @@ $string['studentname'] = '{$a} Name'; $string['studentoneminwarning'] = 'Warning: You have 1 minute or less to finish the lesson.'; $string['studentoutoftimeforreview'] = 'Attention: You ran out of time for reviewing this lesson'; $string['studentresponse'] = '{$a}\'s response'; +$string['studentswhoattempted'] = 'Students who attempted'; $string['submit'] = 'Submit'; $string['submitname'] = 'Submit name'; $string['teacherjumpwarning'] = 'A {$a->cluster} jump or an {$a->unseen} jump is being used in this lesson. The next page jump will be used instead. Log in as a student to test these jumps.'; @@ -577,6 +582,7 @@ $string['timeremaining'] = 'Time remaining'; $string['timespenterror'] = 'Spend at least {$a} minutes in the lesson'; $string['timespentminutes'] = 'Time spent (minutes)'; $string['timetaken'] = 'Time taken'; +$string['totalattepmts'] = 'Total attempts'; $string['totalpagesviewedheader'] = 'Number of pages viewed'; $string['true'] = 'True'; $string['truefalse'] = 'True/false'; @@ -593,6 +599,7 @@ $string['usepassword'] = 'Password protected lesson'; $string['usepassword_help'] = 'If enabled, a password is required in order to access the lesson.'; $string['useroverrides'] = 'User overrides'; $string['usersnone'] = 'No students have access to this lesson'; +$string['view'] = 'View'; $string['viewessayanswers'] = 'View essay answers'; $string['viewgrades'] = 'View grades'; $string['viewreports'] = 'View {$a->attempts} completed {$a->student} attempts'; diff --git a/mod/lesson/tests/behat/overview_report.feature b/mod/lesson/tests/behat/overview_report.feature new file mode 100644 index 00000000000..99abab74b97 --- /dev/null +++ b/mod/lesson/tests/behat/overview_report.feature @@ -0,0 +1,103 @@ +@mod @mod_lesson +Feature: Testing overview_report in mod_lesson + In order to list all lessons in a course + As a user + I need to be able to see the lesson overview + + Background: + Given the following "users" exist: + | username | firstname | lastname | + | student1 | Username | 1 | + | student2 | Username | 2 | + | student3 | Username | 3 | + | teacher1 | Teacher | T | + And the following "courses" exist: + | fullname | shortname | groupmode | + | Course 1 | C1 | 1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | teacher1 | C1 | editingteacher | + And the following "activities" exist: + | activity | name | course | idnumber | retake | deadline | + | lesson | Lesson 1 | C1 | lesson1 | 1 | ##tomorrow## | + | lesson | Lesson 2 | C1 | lesson2 | 0 | 0 | + And the following "mod_lesson > pages" exist: + | lesson | qtype | title | content | + | lesson1 | truefalse | Question 1 | The number 10 is greater than 5 | + And the following "mod_lesson > answers" exist: + | page | answer | jumpto | score | + | Question 1 | True | End of lesson | 1 | + | Question 1 | False | End of lesson | 0 | + And the following "mod_lesson > submissions" exist: + | lesson | user | grade | + | Lesson 1 | student1 | 50 | + | Lesson 1 | student1 | 60 | + | Lesson 1 | student1 | 100 | + | Lesson 1 | student2 | 90 | + + @javascript + Scenario: Teacher can see the lesson relevant information in the lesson overview + When I am on the "Course 1" "course > activities > lesson" page logged in as "teacher1" + Then the following should exist in the "Table listing all Lesson activities" table: + | Name | Students who attempted | Total attempts | Due date | + | Lesson 1 | 2 of 3 | 4 | Tomorrow | + | Lesson 2 | 0 of 3 | 0 | - | + And I click on "4" "button" in the "Lesson 1" "table_row" + And I should see "This Lesson allows students to do multiple attempts." + And I should see "Average attempts per student: 2" + And I press the escape key + And "0" "button" should not exist in the "Lesson 2" "table_row" + And I click on "View" "link" in the "Lesson 1" "table_row" + And I should see "Reports" in the "page-header" "region" + + Scenario: Teacher can see the lesson overview with all lessons with retakes disabled + Given the following "courses" exist: + | fullname | shortname | + | Course 2 | C2 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C2 | student | + | teacher1 | C2 | editingteacher | + And the following "activities" exist: + | activity | name | course | idnumber | retake | + | lesson | Lesson 1 | C2 | lesson1 | 0 | + | lesson | Lesson 2 | C2 | lesson2 | 0 | + When I am on the "Course 2" "course > activities > lesson" page logged in as "teacher1" + Then I should not see "Total attempts" in the "lesson_overview_collapsible" "region" + And the following should exist in the "Table listing all Lesson activities" table: + | Name | Students who attempted | Due date | + | Lesson 1 | 0 of 1 | - | + | Lesson 2 | 0 of 1 | - | + + Scenario: Students can see the lesson relevant information in the lesson overview + When I am on the "Course 1" "course > activities > lesson" page logged in as "student1" + Then I should not see "Actions" in the "lesson_overview_collapsible" "region" + And I should not see "Students who attempted" in the "lesson_overview_collapsible" "region" + And I should not see "Total attempts" in the "lesson_overview_collapsible" "region" + And the following should exist in the "Table listing all Lesson activities" table: + | Name | Due date | + | Lesson 1 | Tomorrow | + | Lesson 2 | - | + + Scenario: The lesson index redirect to the activities overview + When I log in as "admin" + And I am on "Course 1" course homepage with editing mode on + And I add the "Activities" block + And I click on "Lessons" "link" in the "Activities" "block" + Then I should see "An overview of all activities in the course, with dates and other information." + And I should see "Name" in the "lesson_overview_collapsible" "region" + And I should see "Students who attempted" in the "lesson_overview_collapsible" "region" + And I should see "Total attempts" in the "lesson_overview_collapsible" "region" + And I should see "Actions" in the "lesson_overview_collapsible" "region" + + Scenario: The lesson overview report should generate log events + Given I am on the "Course 1" "course > activities > lesson" page logged in as "teacher1" + When I am on the "Course 1" "course" page logged in as "teacher1" + And I navigate to "Reports" in current page administration + And I click on "Logs" "link" + And I click on "Get these logs" "button" + Then I should see "Course activities overview page viewed" + And I should see "viewed the instance list for the module 'lesson'" diff --git a/mod/lesson/tests/courseformat/overview_test.php b/mod/lesson/tests/courseformat/overview_test.php new file mode 100644 index 00000000000..0dd138429ca --- /dev/null +++ b/mod/lesson/tests/courseformat/overview_test.php @@ -0,0 +1,366 @@ +. + +namespace mod_lesson\courseformat; + +use core_courseformat\local\overview\overviewfactory; +use lesson; + +/** + * Tests for Lesson overview. + * + * @package mod_lesson + * @category test + * @copyright 2025 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class overview_test extends \advanced_testcase { + /** + * Helper function to create lesson pages with multichoice questions. + * + * @param lesson $lesson The lesson object. + * @param int $count The number of multichoice questions to create. + */ + private function create_lesson_pages(lesson $lesson, int $count): void { + /** @var \mod_lesson_generator $lessongenerator */ + $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); + + for ($i = 0; $i < $count; $i++) { + $lessongenerator->create_page([ + 'title' => 'Multichoice question' . ($i + 1), + 'content' => 'Question content', + 'qtype' => 'multichoice', + 'lessonid' => $lesson->id, + ]); + $lessongenerator->create_answer(['page' => 'Multichoice question' . ($i + 1), 'answer' => 'A', 'score' => 1]); + $lessongenerator->create_answer(['page' => 'Multichoice question' . ($i + 1), 'answer' => 'B']); + } + $lessongenerator->finish_generate_answer(); + } + + /** + * Test get_due_date_overview. + * + * @covers ::get_due_date_overview + * @dataProvider provider_test_get_due_date_overview + * + * @param int|null $timeincrement the time increment in seconds to add to the current time for the deadline. + * @return void + */ + public function test_get_due_date_overview(?int $timeincrement): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $deadline = $timeincrement ? $this->mock_clock_with_frozen()->time() + $timeincrement : 0; + $lesson = $this->getDataGenerator()->create_module( + 'lesson', + [ + 'course' => $course->id, + 'deadline' => $deadline, + ], + ); + + $this->setUser($student); + + $cm = get_fast_modinfo($course)->get_cm($lesson->cmid); + $item = overviewfactory::create($cm)->get_due_date_overview(); + + $this->assertEquals(get_string('duedate', 'lesson'), $item->get_name()); + $this->assertEquals($deadline, $item->get_value()); + } + + /** + * Provider for test_get_due_date_overview. + * + * @return array + */ + public static function provider_test_get_due_date_overview(): array { + return [ + 'no_due' => [ + 'timeincrement' => null, + ], + 'past_due' => [ + 'timeincrement' => -1 * (4 * DAYSECS), + ], + 'future_due' => [ + 'timeincrement' => (4 * DAYSECS), + ], + ]; + } + + /** + * Test get_actions_overview. + * + * @covers ::get_actions_overview + * @dataProvider provider_test_get_actions_overview + * + * @param string $role + * @param array|null $expected + * @return void + */ + public function test_get_actions_overview( + string $role, + ?array $expected + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + $lesson = $this->getDataGenerator()->create_module( 'lesson', ['course' => $course->id]); + + $this->setUser($currentuser); + + $cm = get_fast_modinfo($course)->get_cm($lesson->cmid); + $item = overviewfactory::create($cm)->get_actions_overview(); + + if ($expected === null) { + $this->assertNull($item); + return; + } + + $this->assertEquals( + $expected, + ['name' => $item->get_name(), 'value' => $item->get_value()] + ); + } + + /** + * Data provider for test_get_actions_overview. + * + * @return array + */ + public static function provider_test_get_actions_overview(): array { + return [ + 'Student' => [ + 'role' => 'student', + 'expected' => null, + ], + 'Teacher' => [ + 'role' => 'editingteacher', + 'expected' => [ + 'name' => get_string('actions'), + 'value' => '', + ], + ], + ]; + } + + /** + * Test get_extra_totalattempts_overview. + * + * @covers ::get_extra_totalattempts_overview + * @dataProvider provider_test_get_extra_totalattempts_overview + * + * @param string $role + * @param bool $hasentries + * @param bool $hasretakes + * @param array|null $expected + * @return void + */ + public function test_get_extra_totalattempts_overview( + string $role, + bool $hasentries, + bool $hasretakes, + ?array $expected + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + + $lessonmodule = $this->getDataGenerator()->create_module( + 'lesson', + ['course' => $course, 'retake' => $hasretakes] + ); + $cm = get_fast_modinfo($course)->get_cm($lessonmodule->cmid); + $lesson = new lesson($lessonmodule); + /** @var \mod_lesson_generator $lessongenerator */ + $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); + $this->create_lesson_pages($lesson, 2); + + if ($hasentries) { + $lessongenerator->create_submission([ + 'lessonid' => $lesson->id, + 'userid' => $student1->id, + 'grade' => 100, + ]); + $lessongenerator->create_submission([ + 'lessonid' => $lesson->id, + 'userid' => $currentuser->id, + 'grade' => 100, + ]); + } + + $this->setUser($currentuser); + $overview = overviewfactory::create($cm); + $reflection = new \ReflectionClass($overview); + $method = $reflection->getMethod('get_extra_totalattempts_overview'); + $method->setAccessible(true); + $item = $method->invoke($overview); + + if ($expected === null) { + $this->assertNull($item); + return; + } + + $this->assertEquals( + $expected, + ['name' => $item->get_name(), 'value' => $item->get_value()] + ); + } + + /** + * Data provider for test_get_extra_totalattempts_overview. + * + * @return array + */ + public static function provider_test_get_extra_totalattempts_overview(): array { + return [ + 'Teacher (with attempts)' => [ + 'role' => 'editingteacher', + 'hasentries' => true, + 'hasretakes' => true, + 'expected' => [ + 'name' => get_string('totalattepmts', 'mod_lesson'), + 'value' => 2, + ], + ], + 'Teacher (with attempts without retakes)' => [ + 'role' => 'editingteacher', + 'hasentries' => true, + 'hasretakes' => false, + 'expected' => [ + 'name' => get_string('totalattepmts', 'mod_lesson'), + 'value' => null, + ], + ], + 'Teacher (without attempts)' => [ + 'role' => 'editingteacher', + 'hasentries' => false, + 'hasretakes' => true, + 'expected' => [ + 'name' => get_string('totalattepmts', 'mod_lesson'), + 'value' => 0, + ], + ], + 'Student' => [ + 'role' => 'student', + 'hasentries' => true, + 'hasretakes' => true, + 'expected' => null, + ], + ]; + } + + /** + * Test get_extra_attemptedstudents_overview. + * + * @covers ::get_extra_attemptedstudents_overview + * @dataProvider provider_test_get_extra_attemptedstudents_overview + * + * @param string $role + * @param bool $hasentries + * @param array|null $expected + * @return void + */ + public function test_get_extra_attemptedstudents_overview( + string $role, + bool $hasentries, + ?array $expected + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + + $lessonmodule = $this->getDataGenerator()->create_module('lesson', ['course' => $course]); + $cm = get_fast_modinfo($course)->get_cm($lessonmodule->cmid); + $lesson = new lesson($lessonmodule); + /** @var \mod_lesson_generator $lessongenerator */ + $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); + $this->create_lesson_pages($lesson, 2); + + if ($hasentries) { + $lessongenerator->create_submission([ + 'lessonid' => $lesson->id, + 'userid' => $student1->id, + 'grade' => 100, + ]); + $lessongenerator->create_submission([ + 'lessonid' => $lesson->id, + 'userid' => $currentuser->id, + 'grade' => 100, + ]); + } + + $this->setUser($currentuser); + $overview = overviewfactory::create($cm); + $reflection = new \ReflectionClass($overview); + $method = $reflection->getMethod('get_extra_attemptedstudents_overview'); + $method->setAccessible(true); + $item = $method->invoke($overview); + + if ($expected === null) { + $this->assertNull($item); + return; + } + + $this->assertEquals( + $expected, + ['name' => $item->get_name(), 'value' => (int)$item->get_value()] + ); + } + + /** + * Data provider for test_get_extra_attemptedstudents_overview. + * + * @return array + */ + public static function provider_test_get_extra_attemptedstudents_overview(): array { + return [ + 'Teacher (with attempts)' => [ + 'role' => 'editingteacher', + 'hasentries' => true, + 'expected' => [ + 'name' => get_string('studentswhoattempted', 'mod_lesson'), + 'value' => 2, + ], + ], + 'Teacher (without attempts)' => [ + 'role' => 'editingteacher', + 'hasentries' => false, + 'expected' => [ + 'name' => get_string('studentswhoattempted', 'mod_lesson'), + 'value' => 0, + ], + ], + 'Student' => [ + 'role' => 'student', + 'hasentries' => true, + 'expected' => null, + ], + ]; + } +}