diff --git a/.upgradenotes/MDL-83889-2025080108444783.yml b/.upgradenotes/MDL-83889-2025080108444783.yml new file mode 100644 index 00000000000..ae518842735 --- /dev/null +++ b/.upgradenotes/MDL-83889-2025080108444783.yml @@ -0,0 +1,5 @@ +issueNumber: MDL-83889 +notes: + mod_bigbluebuttonbn: + - message: Add activity_dates class to BigblueButton module. + type: improved diff --git a/public/course/format/tests/local/overview/overviewfactory_test.php b/public/course/format/tests/local/overview/overviewfactory_test.php index 9c1256d8983..3270fc14edb 100644 --- a/public/course/format/tests/local/overview/overviewfactory_test.php +++ b/public/course/format/tests/local/overview/overviewfactory_test.php @@ -92,7 +92,7 @@ final class overviewfactory_test extends \advanced_testcase { ], 'bigbluebuttonbn' => [ 'resourcetype' => 'bigbluebuttonbn', - 'expected' => resourceoverview::class, + 'expected' => \mod_bigbluebuttonbn\courseformat\overview::class, ], 'choice' => [ 'resourcetype' => 'choice', @@ -198,7 +198,7 @@ final class overviewfactory_test extends \advanced_testcase { public static function activity_has_overview_integration_provider(): array { return [ 'assign' => ['modname' => 'assign', 'hasintegration' => true], - 'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'hasintegration' => false], + 'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'hasintegration' => true], 'book' => ['modname' => 'book', 'hasintegration' => false], 'choice' => ['modname' => 'choice', 'hasintegration' => true], 'data' => ['modname' => 'data', 'hasintegration' => true], diff --git a/public/course/format/tests/output/local/overview/missingoverviewnotice_test.php b/public/course/format/tests/output/local/overview/missingoverviewnotice_test.php index 2f946bd260d..820336873a8 100644 --- a/public/course/format/tests/output/local/overview/missingoverviewnotice_test.php +++ b/public/course/format/tests/output/local/overview/missingoverviewnotice_test.php @@ -63,7 +63,7 @@ final class missingoverviewnotice_test extends \advanced_testcase { public static function overview_integrations_provider(): array { return [ 'assign' => ['modname' => 'assign', 'expectempty' => true], - 'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'expectempty' => false], + 'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'expectempty' => true], 'book' => ['modname' => 'book', 'expectempty' => false], 'choice' => ['modname' => 'choice', 'expectempty' => true], 'data' => ['modname' => 'data', 'expectempty' => true], diff --git a/public/mod/bigbluebuttonbn/classes/courseformat/overview.php b/public/mod/bigbluebuttonbn/classes/courseformat/overview.php new file mode 100644 index 00000000000..6f7fccc6864 --- /dev/null +++ b/public/mod/bigbluebuttonbn/classes/courseformat/overview.php @@ -0,0 +1,183 @@ +. + +namespace mod_bigbluebuttonbn\courseformat; + +use core_calendar\output\humandate; +use cm_info; +use core_courseformat\local\overview\overviewitem; +use core\output\action_link; +use core\output\local\properties\text_align; +use core\output\local\properties\button; +use core\url; +use mod_bigbluebuttonbn\dates; +use mod_bigbluebuttonbn\instance; +use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy; +use mod_bigbluebuttonbn\recording; + +/** + * bigbluebuttonbn overview integration. + * + * @package mod_bigbluebuttonbn + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview extends \core_courseformat\activityoverviewbase { + /** @var instance $bigbluebuttonbn the bigbluebuttonbn instance. */ + private instance $bigbluebuttonbn; + + /** + * Constructor. + * + * @param cm_info $cm the course module instance. + */ + public function __construct( + cm_info $cm, + ) { + parent::__construct($cm); + $this->bigbluebuttonbn = instance::get_from_cmid($cm->id); + } + + #[\Override] + public function get_actions_overview(): ?overviewitem { + if (!$this->bigbluebuttonbn->is_moderator()) { + return null; + } + + $content = new action_link( + url: new url('/mod/bigbluebuttonbn/view.php', ['id' => $this->cm->id]), + text: get_string('view'), + attributes: ['class' => button::BODY_OUTLINE->classes()], + ); + + return new overviewitem( + name: get_string('actions'), + value: get_string('view'), + content: $content, + textalign: text_align::CENTER, + ); + } + + #[\Override] + public function get_extra_overview_items(): array { + return [ + 'opens' => $this->get_extra_date_open(), + 'closes' => $this->get_extra_date_close(), + 'roomtype' => $this->get_extra_room_type_overview(), + 'recordings' => $this->get_extra_recordings_overview(), + ]; + } + + /** + * Retrieves the open date overview item. + * + * @return overviewitem|null An overview item with the open date, or null if the user lacks the required capability. + */ + public function get_extra_date_open(): ?overviewitem { + global $USER; + + $dates = new dates($this->cm, $USER->id); + $opendate = $dates->get_open_date(); + + if (empty($opendate)) { + return new overviewitem( + name: get_string('opens', 'bigbluebuttonbn'), + value: null, + content: '-', + ); + } + + $content = humandate::create_from_timestamp($opendate); + + return new overviewitem( + name: get_string('opens', 'bigbluebuttonbn'), + value: $opendate, + content: $content, + ); + } + + /** + * Retrieves the close date overview item. + * + * @return overviewitem|null An overview item with the open date, or null if the user lacks the required capability. + */ + public function get_extra_date_close(): ?overviewitem { + global $USER; + + $dates = new dates($this->cm, $USER->id); + $closedate = $dates->get_close_date(); + + if (empty($closedate)) { + return new overviewitem( + name: get_string('closes', 'bigbluebuttonbn'), + value: null, + content: '-', + ); + } + + $content = humandate::create_from_timestamp($closedate); + + return new overviewitem( + name: get_string('closes', 'bigbluebuttonbn'), + value: $closedate, + content: $content, + ); + } + /** + * Retrieves the recording count overview item. + * + * @return overviewitem|null An overview item c, or null if the user lacks the required capability. + */ + private function get_extra_room_type_overview(): ?overviewitem { + if (!$this->bigbluebuttonbn->is_moderator()) { + return null; + } + $typeprofiles = bigbluebutton_proxy::get_instance_type_profiles(); + $profilename = $typeprofiles[$this->bigbluebuttonbn->get_type()]['name']; + return new overviewitem( + name: get_string('mod_form_field_instanceprofiles', 'mod_bigbluebuttonbn'), + value: $profilename, + content: $profilename, + textalign: text_align::START, + ); + } + /** + * Retrieves the recording count overview item. + * + * @return overviewitem|null An overview item c, or null if the user lacks the required capability. + */ + private function get_extra_recordings_overview(): ?overviewitem { + if (!$this->bigbluebuttonbn->is_moderator()) { + return null; + } + $content = '-'; + $recordingcount = 0; + if ($this->bigbluebuttonbn->get_type() !== strval(instance::TYPE_ROOM_ONLY)) { + $recordings = recording::get_recordings_for_instance( + $this->bigbluebuttonbn, + includeimported: true, + ); + $recordingcount = count($recordings); + $content = strval($recordingcount); + } + return new overviewitem( + name: get_string('recordings', 'mod_bigbluebuttonbn'), + value: $recordingcount, + content: $content, + textalign: text_align::END, + ); + } +} diff --git a/public/mod/bigbluebuttonbn/classes/dates.php b/public/mod/bigbluebuttonbn/classes/dates.php new file mode 100644 index 00000000000..bfc214b9099 --- /dev/null +++ b/public/mod/bigbluebuttonbn/classes/dates.php @@ -0,0 +1,116 @@ +. + +declare(strict_types=1); + +namespace mod_bigbluebuttonbn; + +use cm_info; +use core\activity_dates; + +/** + * Class for fetching the important dates in mod_bigbluebuttonbn for a given module instance and a user. + * + * @package mod_bigbluebuttonbn + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class dates extends activity_dates { + /** + * Returns the activity due date. + * + * @var int|null $timeclose the activity due date + */ + protected ?int $timeclose = null; + /** + * @var int|null $timeopen the activity open date + */ + protected ?int $timeopen = null; + + /** + * @var instance the instance of the activity + */ + protected instance $instance; + + /** + * activity_dates constructor. + * + * @param cm_info $cm course module + * @param int $userid user id + */ + public function __construct(cm_info $cm, int $userid) { + parent::__construct($cm, $userid); + $this->instance = instance::get_from_cmid((int) $cm->id); + } + + /** + * Returns a list of important dates in mod_choice + * + * @return array + */ + protected function get_dates(): array { + $timeopen = $this->instance->get_instance_var('openingtime'); + $timeclose = $this->instance->get_instance_var('closingtime'); + $now = time(); + $dates = []; + + if ($timeopen) { + $openlabelid = $timeopen > $now ? 'activitydate:opens' : 'activitydate:opened'; + $dates[] = [ + 'dataid' => 'timeopen', + 'label' => get_string($openlabelid, 'course'), + 'timestamp' => (int) $timeopen, + ]; + $this->timeopen = (int) $timeopen; + } + + if ($timeclose) { + $closelabelid = $timeclose > $now ? 'activitydate:closes' : 'activitydate:closed'; + $dates[] = [ + 'dataid' => 'timeclose', + 'label' => get_string($closelabelid, 'course'), + 'timestamp' => (int) $timeclose, + ]; + $this->timeclose = (int) $timeclose; + } + + return $dates; + } + + /** + * Returns the activity due date. + * + * @return int|null + */ + public function get_close_date(): ?int { + if (!isset($this->timeclose)) { + $this->get_dates(); + } + return $this->timeclose; + } + + /** + * Returns the activity open date. + * + * @return int|null + */ + public function get_open_date(): ?int { + if (!isset($this->timeopen)) { + $this->get_dates(); + } + return $this->timeopen; + } +} diff --git a/public/mod/bigbluebuttonbn/classes/event/course_module_instance_list_viewed.php b/public/mod/bigbluebuttonbn/classes/event/course_module_instance_list_viewed.php new file mode 100644 index 00000000000..d1ca1a9e39c --- /dev/null +++ b/public/mod/bigbluebuttonbn/classes/event/course_module_instance_list_viewed.php @@ -0,0 +1,40 @@ +. + +namespace mod_bigbluebuttonbn\event; +/** + * The mod_bigbluebuttonbn instance list viewed event class. + * + * @package mod_bigbluebuttonbn + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { + /** + * Create the event from course record. + * + * @param \stdClass $course + * @return course_module_instance_list_viewed + */ + public static function create_from_course(\stdClass $course) { + $params = [ + 'context' => \context_course::instance($course->id), + ]; + $event = self::create($params); + $event->add_record_snapshot('course', $course); + return $event; + } +} diff --git a/public/mod/bigbluebuttonbn/index.php b/public/mod/bigbluebuttonbn/index.php index 3055bc30daf..abc20ffb877 100644 --- a/public/mod/bigbluebuttonbn/index.php +++ b/public/mod/bigbluebuttonbn/index.php @@ -24,36 +24,8 @@ * @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com) */ -use core\notification; -use mod_bigbluebuttonbn\instance; -use mod_bigbluebuttonbn\output\index; -use mod_bigbluebuttonbn\plugin; +require_once("../../config.php"); -require(__DIR__.'/../../config.php'); -global $PAGE, $OUTPUT; -$id = required_param('id', PARAM_INT); -$course = get_course($id); -require_login($course, true); +$courseid = required_param('id', PARAM_INT); -$PAGE->set_url('/mod/bigbluebuttonbn/index.php', ['id' => $id]); -$PAGE->set_title(get_string('modulename', plugin::COMPONENT)); -$PAGE->set_heading($course->fullname); -$PAGE->set_cacheable(false); -$PAGE->set_pagelayout('incourse'); - -$PAGE->navbar->add($PAGE->title, $PAGE->url); - -$instances = instance::get_all_instances_in_course($course->id); -if (empty($instances)) { - notification::add( - get_string('index_error_noinstances', plugin::COMPONENT), - notification::ERROR - ); - redirect(new moodle_url('/course/view.php', ['id' => $course->id])); -} - -echo $OUTPUT->header(); -echo $OUTPUT->heading(get_string('index_heading', plugin::COMPONENT)); -$renderer = $PAGE->get_renderer(plugin::COMPONENT); -echo $renderer->render(new index($course, $instances)); -echo $OUTPUT->footer(); +\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, 'bigbluebuttonbn'); diff --git a/public/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php b/public/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php index 9628ee25d93..0a246f88c0c 100644 --- a/public/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php +++ b/public/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php @@ -299,6 +299,7 @@ $string['config_hideuserlist_editable'] = 'Hide user list can be edited'; $string['config_hideuserlist_editable_description'] = 'Hide user list by default can be edited when the instance is added or updated.'; $string['config_experimental_features'] = 'Experimental features'; +$string['closes'] = 'Closes'; $string['config_experimental_features_description'] = 'Configuration for experimental features.'; $string['general_error_unable_connect'] = 'Unable to connect. Please check the url of the BigBlueButton server AND check to see if the BigBlueButton server is running. @@ -420,6 +421,7 @@ $string['mod_form_field_disablepublicchat'] = 'Disable public chat'; $string['mod_form_field_disablenote'] = 'Disable shared notes'; $string['mod_form_field_hideuserlist'] = 'Hide user list'; $string['mod_form_locksettings'] = 'Lock settings'; +$string['opens'] = 'Opens'; $string['report_join_info'] = '{$a} meeting(s)'; $string['report_play_recording_info'] = '{$a} recording(s) played'; $string['report_room_view'] = 'viewed'; @@ -645,10 +647,9 @@ $string['taskname:check_pending_recordings'] = 'Fetch pending recordings'; $string['taskname:check_dismissed_recordings'] = 'Check for recordings that haven\'t been found yet'; $string['userlimitreached'] = 'The number of users allowed in a session has been reached.'; $string['waitformoderator'] = 'Waiting for a moderator to join.'; - $string['recordingnotfound'] = 'The recording was not found.'; +$string['recordings'] = 'Recordings'; $string['recordingurlnotfound'] = 'The recording URL is invalid.'; - $string['subplugintype_bbbext'] = 'BigBlueButton activity extension'; $string['subplugintype_bbbext_plural'] = 'BigBlueButton activity extensions'; diff --git a/public/mod/bigbluebuttonbn/templates/room_view.mustache b/public/mod/bigbluebuttonbn/templates/room_view.mustache index 2aebf5bd8c6..d55e1e489d4 100644 --- a/public/mod/bigbluebuttonbn/templates/room_view.mustache +++ b/public/mod/bigbluebuttonbn/templates/room_view.mustache @@ -40,25 +40,6 @@ {{#userdate}} {{startedat}}, {{#str}} strftimetime, core_langconfig{{/str}} {{/userdate}}. {{statusmessage}} - {{/statusrunning}} - {{^statusrunning}} -
{{statusmessage}}
- {{/statusrunning}} -
- {{#openingtime}} - - {{#str}}mod_form_field_openingtime, bigbluebuttonbn{{/str}}: - {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} - - {{/openingtime}} - {{#closingtime}} -
- {{#str}}mod_form_field_closingtime, bigbluebuttonbn{{/str}}: - {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} -
- {{/closingtime}} -
- {{#statusrunning}}
{{#moderatorplural}}{{#str}}view_message_moderators, mod_bigbluebuttonbn{{/str}}{{/moderatorplural}} @@ -74,6 +55,9 @@ {{participantcount}}
{{/statusrunning}} + {{^statusrunning}} +
{{statusmessage}}
+ {{/statusrunning}}
diff --git a/public/mod/bigbluebuttonbn/tests/behat/bigbluebuttonbn_activity_availability.feature b/public/mod/bigbluebuttonbn/tests/behat/bigbluebuttonbn_activity_availability.feature index 34ea0cdfe94..0e5666381d1 100644 --- a/public/mod/bigbluebuttonbn/tests/behat/bigbluebuttonbn_activity_availability.feature +++ b/public/mod/bigbluebuttonbn/tests/behat/bigbluebuttonbn_activity_availability.feature @@ -28,9 +28,9 @@ Feature: Manage BigBlueButton session timings | C1 | bigbluebuttonbn | BBB 1 | | | When I am on the "BBB 1" "bigbluebuttonbn activity" page logged in as student1 And "Join session" "link" exist - And I should see "Open:" + And I should see ":" And I should see "%A, %d %B %Y##" - And I should see "Close:" + And I should see ":" And I should see "%A, %d %B %Y##" And I am viewing calendar in "month" view And I see "BBB 1" @@ -39,7 +39,7 @@ Feature: Manage BigBlueButton session timings And I see "BBB 1" in the "Upcoming events" "block" Examples: - | openingtime | closingtime | calendarvisibility | buttonvisibility | upcomingeventvisibility | - | ##now +1 minute## | ##now +5 minutes## | should | should not | should | - | ##1 hour ago## | ##+2 hours## | should | should | should not | - | ##yesterday## | ##yesterday +3 hours## | should not | should not | should not | + | opens | closes | openingtime | closingtime | calendarvisibility | buttonvisibility | upcomingeventvisibility | + | Opens | Closes | ##now +1 minute## | ##now +5 minutes## | should | should not | should | + | Opened | Closes | ##1 hour ago## | ##+2 hours## | should | should | should not | + | Opened | Closed | ##yesterday## | ##yesterday +3 hours## | should not | should not | should not | diff --git a/public/mod/bigbluebuttonbn/tests/behat/end_meeting.feature b/public/mod/bigbluebuttonbn/tests/behat/end_meeting.feature index 7a6d91a5db3..7e390db3ba3 100644 --- a/public/mod/bigbluebuttonbn/tests/behat/end_meeting.feature +++ b/public/mod/bigbluebuttonbn/tests/behat/end_meeting.feature @@ -54,50 +54,3 @@ Feature: Test the ability to end a meeting | activity | Room recordings | When I am on the "Room recordings" Activity page logged in as admin Then "End session" "link" should exist - - Scenario Outline: Only a BigBlueButton moderator can end a session from the index page - Given the following course exists: - | name | Test course | - | shortname | C1 | - And the following "users" exist: - | username | firstname | lastname | email | - | traverst | Terry | Travers | t.travers@example.com | - And the following "course enrolments" exist: - | user | course | role | - | traverst | C1 | | - And the following "activity" exists: - | course | C1 | - | activity | bigbluebuttonbn | - | name | Room recordings | - | idnumber | Room recordings | - | moderators | | - And the following "mod_bigbluebuttonbn > meeting" exists: - | activity | Room recordings | - When I am on the C1 "mod_bigbluebuttonbn > Index" page logged in as traverst - Then "End session" "link" exist - - Examples: - # Note: If the teacher is not listed as a moderator in the activity roles, then will not have permission to end the - # session. - | moderators | role | existence | - | | editingteacher | should not | - | | teacher | should not | - | | student | should not | - | role:teacher | student | should not | - | role:teacher | teacher | should | - | role:student | student | should | - | user:traverst | student | should | - - Scenario: An administrator can always end a meeting on the index page - Given the following course exists: - | name | Test course | - | shortname | C1 | - And the following "activity" exists: - | course | C1 | - | activity | bigbluebuttonbn | - | name | Room recordings | - | idnumber | Room recordings | - And the following "mod_bigbluebuttonbn > meeting" exists: - | activity | Room recordings | - When I am on the C1 "mod_bigbluebuttonbn > Index" page logged in as admin - Then "End session" "link" should exist diff --git a/public/mod/bigbluebuttonbn/tests/behat/overview_report.feature b/public/mod/bigbluebuttonbn/tests/behat/overview_report.feature new file mode 100644 index 00000000000..b13c583663a --- /dev/null +++ b/public/mod/bigbluebuttonbn/tests/behat/overview_report.feature @@ -0,0 +1,84 @@ +@mod @mod_bigbluebuttonbn @javascript +Feature: Testing overview integration in mod_bigbluebuttonbn + In order to summarize the bigbluebuttonbn activities + As a user + I need to be able to see the bigbluebuttonbn overview + + Background: + Given a BigBlueButton mock server is configured + And I enable "bigbluebuttonbn" "mod" plugin + And the following "users" exist: + | username | firstname | lastname | + | student1 | Username | 1 | + | student2 | Username | 2 | + | student3 | Username | 3 | + | student4 | Username | 4 | + | student5 | Username | 5 | + | student6 | Username | 6 | + | student7 | Username | 7 | + | student8 | Username | 8 | + | teacher1 | Teacher | T | + | editingteacher1 | EditingTeacher | T | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | firstname | lastname | + | student1 | C1 | student | Username | 1 | + | student2 | C1 | student | Username | 2 | + | student3 | C1 | student | Username | 3 | + | teacher1 | C1 | teacher | Username | T | + | editingteacher1 | C1 | editingteacher | Username | ET | + And the following "activities" exist: + | activity | name | intro | course | idnumber | type | recordings_imported | openingtime | closingtime | grade | moderators | + | bigbluebuttonbn | RoomRecordings | Test Room Recording description | C1 | bigbluebuttonbn1 | 0 | 0 | 1 January 2024 | | 100 | role:editingteacher | + | bigbluebuttonbn | RoomOnly | Test Room Recording with visible groups | C1 | bigbluebuttonbn2 | 1 | 0 | | 1 January 2040 | 100 | role:editingteacher | + | bigbluebuttonbn | RecordingOnly | Test Room Recording with visible groups | C1 | bigbluebuttonbn3 | 2 | 0 | | | 0 | role:editingteacher | + | bigbluebuttonbn | RoomRecordingsNoUser | Test Room Recording with visible groups | C1 | bigbluebuttonbn4 | 0 | 0 | 1 January 2024 | 1 January 2040 | 0 | role:editingteacher | + | bigbluebuttonbn | RoomRecordingsNoModerator | Test Room Recording with visible groups | C1 | bigbluebuttonbn5 | 0 | 0 | 1 January 2024 | 1 January 2040 | 0 | | + And the following "mod_bigbluebuttonbn > meeting" exists: + | activity | RoomRecordings | + And the following "mod_bigbluebuttonbn > recordings" exist: + | bigbluebuttonbn | name | description | status | + | RoomRecordings | Recording 1 | Description 1 | 2 | + | RoomRecordings | Recording 2 | Description 2 | 2 | + | RoomRecordings | Recording 3 | Description 3 | 2 | + | RoomRecordings | Recording 4 | Description 4 | 0 | + And I am on the "Course 1" "grades > Grader report > View" page logged in as "editingteacher1" + And I turn editing mode on + And I give the grade "90.00" to the user "Username 1" for the grade item "RoomRecordings" + And I give the grade "100.00" to the user "Username 2" for the grade item "RoomOnly" + And I click on "Save changes" "button" + And I log out + + Scenario: The bigbluebuttonbn overview report should generate log events + Given I am on the "Course 1" "course > activities > bigbluebuttonbn" 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 'bigbluebuttonbn'" + + Scenario: Teachers can see relevant columns in the bigbluebuttonbn overview + Given I am on the "Course 1" "course > activities > bigbluebuttonbn" page logged in as "editingteacher1" + When I should not see "Grade" in the "bigbluebuttonbn_overview_collapsible" "region" + Then the following should exist in the "Table listing all BigBlueButton activities" table: + | Name | Opens | Closes | Instance type | Recordings | Actions | + | RoomRecordings | Monday, 1 January 2024, 12:00 AM | - | Room with recordings | 3 | View | + | RoomOnly | - | Sunday, 1 January 2040, 12:00 AM | Room only | - | View | + | RecordingOnly | - | - | Recordings only | 0 | View | + | RoomRecordingsNoUser | Monday, 1 January 2024, 12:00 AM | Sunday, 1 January 2040, 12:00 AM | Room with recordings | 0 | View | + | RoomRecordingsNoModerator | Monday, 1 January 2024, 12:00 AM | Sunday, 1 January 2040, 12:00 AM | | | | + + Scenario: Students can see relevant columns in the bigbluebuttonbn overview + Given I am on the "Course 1" "course > activities > bigbluebuttonbn" page logged in as "student1" + Then the following should exist in the "Table listing all BigBlueButton activities" table: + | Name | Opens | Closes | Grade | + | RoomRecordings | Monday, 1 January 2024, 12:00 AM | - | 90.00 | + | RoomOnly | - | Sunday, 1 January 2040, 12:00 AM | - | + | RecordingOnly | - | - | | + | RoomRecordingsNoUser | Monday, 1 January 2024, 12:00 AM | Sunday, 1 January 2040, 12:00 AM | | + | RoomRecordingsNoModerator | Monday, 1 January 2024, 12:00 AM | Sunday, 1 January 2040, 12:00 AM | | + And I should not see "Instance type" in the "bigbluebuttonbn_overview_collapsible" "region" + And I should not see "Actions" in the "bigbluebuttonbn_overview_collapsible" "region" diff --git a/public/mod/bigbluebuttonbn/tests/courseformat/overview_test.php b/public/mod/bigbluebuttonbn/tests/courseformat/overview_test.php new file mode 100644 index 00000000000..f4a2da07fdc --- /dev/null +++ b/public/mod/bigbluebuttonbn/tests/courseformat/overview_test.php @@ -0,0 +1,434 @@ +. + +namespace mod_bigbluebuttonbn\courseformat; + +use core_courseformat\local\overview\overviewfactory; +use mod_bigbluebuttonbn\instance; +use mod_bigbluebuttonbn\meeting; +use mod_bigbluebuttonbn\recording; +use mod_bigbluebuttonbn\test\testcase_helper_trait; + +/** + * Tests for bigbluebuttonbn activity overview + * + * @covers \mod_bigbluebuttonbn\courseformat\overview + * @package mod_bigbluebuttonbn + * @category test + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class overview_test extends \advanced_testcase { + use testcase_helper_trait; + + /** + * Test get_actions_overview. + */ + public function test_get_actions_overview(): void { + $this->resetAfterTest(); + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + // Students or non moderators have no action column. + foreach (['s1', 's2', 't1', 't2'] as $username) { + $this->setUser($users[$username]); + $this->assertNull(overviewfactory::create($cm)->get_actions_overview()); + } + // T3 is a moderator and should have an action column.. + $this->setUser($users['t3']); + $items = overviewfactory::create($cm)->get_actions_overview(); + $this->assertNotNull($items); + $this->assertEquals(get_string('actions'), $items->get_name()); + // Admin should have an action column too. + $this->setAdminUser(); + $this->assertNotNull(overviewfactory::create($cm)->get_actions_overview()); + $this->assertNotNull($items); + $this->assertEquals(get_string('actions'), $items->get_name()); + } + + /** + * Test get_due_date_overview method. + * + * Note here: we do not use the due date overview for bigbluebuttonbn activities as we have a opening date + * and a closing date instead. If we were to use the due date overview as a closing date, this column + * would not be displayed in the right order - next to closing date column (probably separated by the completion status column). + * So we decided to not use the due date overview at all and instead add two extra date columns: + * - opens: the opening date of the activity. + * - closes: the closing date of the activity. + * This test just checks that the due date overview is not used in the bigbluebuttonbn activity overview. + */ + public function test_get_due_date_overview(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $bigbluebuttonbntemplate = []; + $clock = $this->mock_clock_with_frozen(); + $timeincrement = DAYSECS; + $bigbluebuttonbntemplate['timeclose'] = $clock->time() + $timeincrement; + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(instancedata: $bigbluebuttonbntemplate, createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; + + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setUser($users['s1']); + $overview = overviewfactory::create($cm); + $this->assertNull($overview->get_due_date_overview()); + } + + /** + * Test get_extra_date_open method. + * + * @param int|null $timeincrement + * @dataProvider get_extra_date_data + */ + public function test_get_extra_date_open(?int $timeincrement): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $bigbluebuttonbntemplate = []; + $clock = $this->mock_clock_with_frozen(); + if (!is_null($timeincrement)) { + $bigbluebuttonbntemplate['openingtime'] = $clock->time() + $timeincrement; + } + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(instancedata: $bigbluebuttonbntemplate, createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; + + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setUser($users['s1']); + $overview = overviewfactory::create($cm); + $this->assertEquals( + is_null($timeincrement) ? null : $clock->time() + $timeincrement, + $overview->get_extra_date_open()->get_value(), + ); + } + /** + * Test get_extra_date_close method. + * + * @param int|null $timeincrement + * @dataProvider get_extra_date_data + */ + public function test_get_extra_date_close(?int $timeincrement): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $bigbluebuttonbntemplate = []; + $clock = $this->mock_clock_with_frozen(); + if (!is_null($timeincrement)) { + $bigbluebuttonbntemplate['closingtime'] = $clock->time() + $timeincrement; + } + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(instancedata: $bigbluebuttonbntemplate, createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; + + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setUser($users['s1']); + $overview = overviewfactory::create($cm); + $this->assertEquals( + is_null($timeincrement) ? null : $clock->time() + $timeincrement, + $overview->get_extra_date_close()->get_value(), + ); + } + /** + * Data provider for test_get_due_date_overview. + * + * @return array + */ + public static function get_extra_date_data(): array { + return [ + 'tomorrow' => [ + 'timeincrement' => DAYSECS, + ], + 'yesterday' => [ + 'timeincrement' => -1 * DAYSECS, + ], + 'today' => [ + 'timeincrement' => 0, + ], + 'No date' => [ + 'timeincrement' => null, + ], + ]; + } + + /** + * Test get_extra_date_close method. + * + * @param int $roomtype + * @param string $expectedtype + * + * @dataProvider get_extra_room_type_overview_data + */ + public function test_get_extra_room_type_overview(int $roomtype, string $expectedtype): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $bigbluebuttonbntemplate = []; + $bigbluebuttonbntemplate['type'] = $roomtype; + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(instancedata: $bigbluebuttonbntemplate, createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; + + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setUser($users['t3']); + $overview = overviewfactory::create($cm); + $overviewitem = $overview->get_extra_overview_items(); + $this->assertArrayHasKey('roomtype', $overviewitem); + $this->assertEquals( + $expectedtype, + $overviewitem['roomtype']->get_value(), + ); + } + /** + * Data provider for test_get_due_date_overview. + * + * @return array + */ + public static function get_extra_room_type_overview_data(): array { + return [ + 'All' => [ + 'roomtype' => instance::TYPE_ALL, + 'expectedtype' => get_string('instance_type_default', 'bigbluebuttonbn'), + ], + 'Room Only' => [ + 'roomtype' => instance::TYPE_ROOM_ONLY, + 'expectedtype' => get_string('instance_type_room_only', 'bigbluebuttonbn'), + ], + 'Recording Only' => [ + 'roomtype' => instance::TYPE_RECORDING_ONLY, + 'expectedtype' => get_string('instance_type_recording_only', 'bigbluebuttonbn'), + ], + ]; + } + + /** + * Test test_get_extra_recordings_overview. + * + * @param string $activityname + * @param int $recordingcount + * @throws \coding_exception + * @throws \moodle_exception + * @dataProvider get_extra_recordings_overview_data + */ + public function test_get_extra_recordings_overview(string $activityname, int $recordingcount): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $bigbluebuttonbntemplate = []; + ['users' => $users, 'course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(instancedata: $bigbluebuttonbntemplate); + [$activityname => $instance] = $instances; + + $cm = get_fast_modinfo($course)->get_cm($instance->cmid); + $this->setUser($users['t3']); + $overview = overviewfactory::create($cm); + $overviewitem = $overview->get_extra_overview_items(); + $this->assertArrayHasKey('recordings', $overviewitem); + $this->assertEquals( + $recordingcount, + $overviewitem['recordings']->get_value(), + ); + } + + /** + * Data provider for test_get_extra_studentsattempted_overview and test_get_extra_totalattempts_overview + * + * @return array + */ + public static function get_extra_recordings_overview_data(): array { + + return [ + 'with recordings' => [ + 'activityname' => 'withrecordings', + 'recordingcount' => 2, + ], + 'without recordings' => [ + 'activityname' => 'withoutrecordings', + 'recordingcount' => 0, + ], + ]; + } + + /** + * Test get_extra_overview_items when there are no users in the course + * + * @return void + */ + public function test_get_extra_overview_no_users(): void { + $this->resetAfterTest(); + ['course' => $course, 'instances' => $instances] = + $this->setup_users_and_activity(createusers: false, createrecordings: false); + ['withoutrecordings' => $instancewa] = $instances; // This has no user so no attempt too. + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setAdminUser(); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + $this->assertArrayHasKey('recordings', $items); + $this->assertEquals(0, $items['recordings']->get_value()); + $this->assertArrayHasKey('roomtype', $items); + } + + + /** + * Test check columns and content depeding on role (moderator, admin, other (viewer)). + * + * @return void + */ + public function test_all_get_extra_overview_items(): void { + $this->resetAfterTest(); + ['course' => $course, 'instances' => $instances, 'users' => $users] = + $this->setup_users_and_activity(createusers: true); + ['withoutrecordings' => $instancewa] = $instances; // This has no user so no attempt too. + $cm = get_fast_modinfo($course)->get_cm($instancewa->cmid); + $this->setAdminUser(); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + $this->assertNotEmpty($items['opens']); + $this->assertNotEmpty($items['closes']); + $this->assertNotEmpty($items['roomtype']); + $this->assertNotEmpty($items['recordings']); + // Normal viewers. + $viewers = ['s1', 's2', 't1', 't2']; + foreach ($viewers as $username) { + $this->setUser($users[$username]); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + $this->assertNotEmpty($items['opens']); + $this->assertNotEmpty($items['closes']); + $this->assertEmpty($items['roomtype']); + $this->assertEmpty($items['recordings']); + } + $moderators = ['t3']; // T3 is a moderator in the room. + foreach ($moderators as $username) { + $this->setUser($users[$username]); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + $this->assertNotEmpty($items['opens']); + $this->assertNotEmpty($items['closes']); + $this->assertNotEmpty($items['roomtype']); + $this->assertNotEmpty($items['recordings']); + } + } + + /** + * Setup users and activity for testing answers retrieval. + * + * @param int $groupmode the group mode to use for the course. + * @param bool $createrecordings whether to create an attempt for the student. + * @param array|null $instancedata additional data for the instance. + * @param array|null $grades the grade to set for the student. + * @param bool $createusers whether to enrol users in the course. + * @return array indexed array with 'users', 'course' and 'instance'. + */ + private function setup_users_and_activity( + int $groupmode = NOGROUPS, + bool $createrecordings = true, + ?array $instancedata = null, + ?array $grades = null, + bool $createusers = true, + ): array { + $users = []; + $generator = $this->getDataGenerator(); + $courseparams = []; + if ($groupmode !== NOGROUPS) { + // Set the group mode for the course. + $courseparams['groupmode'] = $groupmode; + $courseparams['groupmodeforce'] = 1; // Force the group mode. + } + $course = $generator->create_course($courseparams); + $groups = []; + if ($createusers) { + $data = [ + 's1' => ['role' => 'student', 'groups' => ['g1']], + 's2' => ['role' => 'student', 'groups' => ['g2']], + 't1' => ['role' => 'editingteacher', 'groups' => ['g1']], + 't2' => ['role' => 'editingteacher', 'groups' => []], + 't3' => ['role' => 'teacher', 'groups' => ['g1']], // T3 will be a moderator in the room. + ]; + // Enrol users in the course. + foreach ($data as $username => $userinfo) { + ['role' => $role, 'groups' => $groups] = $userinfo; + $users[$username] = $generator->create_and_enrol($course, $role, ['username' => $username]); + foreach ($groups as $group) { + if (!isset($groups[$group])) { + // Create the group if it does not exist. + $groups[$group] = $generator->create_group(['courseid' => $course->id, 'name' => $group]); + } + // Add the user to the group. + groups_add_member($groups[$group], $users[$username]->id); + } + } + } + $this->setAdminUser(); + $instancedata = $instancedata ?? []; + $instancedata = array_merge($instancedata, [ + 'course' => $course->id, + 'gradetype' => GRADE_TYPE_VALUE, // Use highest grade for grading. + ]); + if ($createusers) { + // Add the users to the instance data. + $instancedata['moderators'] = 'user:t3';// Set T3 as moderator in the room. + } + $instances = []; + $instances['withoutrecordings'] = + $generator->create_module('bigbluebuttonbn', $instancedata); // Create a second instance with no recordings. + $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn'); + $recordings = []; + if ($createrecordings) { + // We do that only when we want to create recordings. + $this->initialise_mock_server(); + $instances['withrecordings'] = $generator->create_module('bigbluebuttonbn', $instancedata); + $instance = instance::get_from_instanceid($instances['withrecordings']->id); + // We need to create a meeting for the instance in order to create recordings. + $bbbgenerator->create_meeting([ + 'instanceid' => $instance->get_instance_id(), + 'groupid' => $instance->get_group_id(), + ]); + $now = time(); + $recordingstatus = [ + recording::RECORDING_STATUS_AWAITING, + recording::RECORDING_STATUS_PROCESSED, + recording::RECORDING_STATUS_PROCESSED, + recording::RECORDING_STATUS_DISMISSED, + ]; + foreach ($recordingstatus as $status) { + $recordings[] = $bbbgenerator->create_recording( + array_merge([ + 'bigbluebuttonbnid' => $instance->get_instance_id(), + 'groupid' => $instance->get_group_id(), + 'starttime' => $now, + 'endtime' => $now + HOURSECS, + 'status' => $status, + ]) + ); + } + } + if ($grades) { + if ($grades) { + foreach ($instances as $instance) { + foreach ($grades as $grade) { + $instancedata = (object) [ + 'iteminstance' => $instance->id, + 'itemmodule' => 'bigbluebuttonbn', + 'itemtype' => 'mod', + 'courseid' => $course->id, + ]; + $instancedata->rawgrade = $grade; + bigbluebuttonbn_grade_item_update($instancedata); + } + } + } + } + return [ + 'users' => $users, + 'course' => $course, + 'instances' => $instances, + 'recordings' => $recordings, + ]; + } +} diff --git a/public/mod/bigbluebuttonbn/tests/dates_test.php b/public/mod/bigbluebuttonbn/tests/dates_test.php new file mode 100644 index 00000000000..bdc73b64dc7 --- /dev/null +++ b/public/mod/bigbluebuttonbn/tests/dates_test.php @@ -0,0 +1,217 @@ +. + +declare(strict_types=1); + +namespace mod_bigbluebuttonbn; + +use advanced_testcase; +use core\activity_dates; + +/** + * Class for unit testing mod_bigbluebutton\dates. + * + * @package mod_bigbluebuttonbn + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \mod_bigbluebuttonbn\dates + */ +final class dates_test extends advanced_testcase { + use \mod_bigbluebuttonbn\test\testcase_helper_trait; + + /** + * Data provider for get_dates_for_module(). + * + * @return array[] + */ + public static function get_dates_for_module_provider(): array { + $clock = \core\di::get(\core\clock::class); + $now = $clock->time(); + $open = $now - DAYSECS; + $close = $now + DAYSECS; + + return [ + 'Without any dates' => [ + null, null, [], + ], + 'Only with opening time' => [ + $open, + null, + [ + [ + 'label' => get_string('activitydate:opened', 'course'), + 'timestamp' => $open, + 'dataid' => 'timeopen', + ], + ], + ], + 'Only with closing time' => [ + null, + $close, + [ + [ + 'label' => get_string('activitydate:closes', 'course'), + 'timestamp' => $close, + 'dataid' => 'timeclose', + ], + ], + ], + 'With both times' => [ + $open, + $close, + [ + [ + 'label' => get_string('activitydate:opened', 'course'), + 'timestamp' => $open, + 'dataid' => 'timeopen', + ], + [ + 'label' => get_string('activitydate:closes', 'course'), + 'timestamp' => $close, + 'dataid' => 'timeclose', + ], + ], + ], + 'With both times in the future' => [ + $now + DAYSECS, + $now + (2 * DAYSECS), + [ + [ + 'label' => get_string('activitydate:opens', 'course'), + 'timestamp' => $now + DAYSECS, + 'dataid' => 'timeopen', + ], + [ + 'label' => get_string('activitydate:closes', 'course'), + 'timestamp' => $now + (2 * DAYSECS), + 'dataid' => 'timeclose', + ], + ], + ], + 'With both times in the past' => [ + $now - (2 * DAYSECS), + $now - DAYSECS, + [ + [ + 'label' => get_string('activitydate:opened', 'course'), + 'timestamp' => $now - (2 * DAYSECS), + 'dataid' => 'timeopen', + ], + [ + 'label' => get_string('activitydate:closed', 'course'), + 'timestamp' => $now - DAYSECS, + 'dataid' => 'timeclose', + ], + ], + ], + ]; + } + + /** + * Test for get_dates_for_module(). + * + * @param int|null $open Opening time in the BigBlueButton. + * @param int|null $close Closing time in the BigBlueButton. + * @param array $expected The expected value of calling get_dates_for_module() + * @covers ::get_dates_for_module + * @dataProvider get_dates_for_module_provider + */ + public function test_get_dates_for_module( + ?int $open, + ?int $close, + array $expected + ): void { + $this->resetAfterTest(); + ['user' => $user, 'cm' => $cm] = $this->setup_instance($open, $close); + $this->setUser($user); + $dates = activity_dates::get_dates_for_module($cm, (int) $user->id); + + $this->assertEquals($expected, $dates); + } + + + /** + * Test for get_open_date(). + * + * @param int|null $open Opening time in the BigBlueButton. + * @param int|null $close Closing time in the BigBlueButton. + * @covers ::get_open_date + * @dataProvider get_dates_for_module_provider + */ + public function test_get_open_date( + ?int $open, + ?int $close, + ): void { + + $this->resetAfterTest(); + ['user' => $user, 'cm' => $cm] = $this->setup_instance($open, $close); + $this->setUser($user); + $dates = new \mod_bigbluebuttonbn\dates($cm, (int) $user->id); + + $this->assertEquals($open, $dates->get_open_date()); + } + + /** + * Test for get_close_date(). + * + * @param int|null $open Opening time in the BigBlueButton. + * @param int|null $close Closing time in the BigBlueButton. + * @covers ::get_close_date + * @dataProvider get_dates_for_module_provider + */ + public function test_get_close_date( + ?int $open, + ?int $close, + ): void { + + $this->resetAfterTest(); + ['user' => $user, 'cm' => $cm] = $this->setup_instance($open, $close); + $dates = new \mod_bigbluebuttonbn\dates($cm, (int) $user->id); + + $this->assertEquals($close, $dates->get_close_date()); + } + + /** + * Setup a BigBlueButton activity instance. + * + * @param int|null $open Opening time in the BigBlueButton. + * @param int|null $close Closing time in the BigBlueButton. + * @return array with keys 'user' and 'cm'. + */ + private function setup_instance( + ?int $open, + ?int $close, + ): array { + $generator = $this->getDataGenerator(); + + $course = $generator->create_course(); + $user = $generator->create_user(); + $generator->enrol_user($user->id, $course->id); + $data = []; + if ($open !== null) { + $data['openingtime'] = $open; + } + if ($close !== null) { + $data['closingtime'] = $close; + } + $this->setAdminUser(); + [$bbactivitycontext, $bbactivitycm, $bbactivity] = $this->create_instance( + $course, + $data + ); + return ['user' => $user, 'cm' => $bbactivitycm]; + } +} diff --git a/public/mod/bigbluebuttonbn/tests/generator/lib.php b/public/mod/bigbluebuttonbn/tests/generator/lib.php index 07a6adb378d..1af9c572259 100644 --- a/public/mod/bigbluebuttonbn/tests/generator/lib.php +++ b/public/mod/bigbluebuttonbn/tests/generator/lib.php @@ -69,11 +69,27 @@ class mod_bigbluebuttonbn_generator extends \testing_module_generator { "recordings_preview" => 0, "grade" => 0, ]; - $record = (array) $record; $record['participants'] = json_encode($this->get_participants_from_record($record)); + if (!empty($record['openingtime'])) { + if (is_numeric($record['openingtime'])) { + $record['openingtime'] = intval($record['openingtime']); + } else { + // If it is a string, we assume it is a timestamp. + $record['openingtime'] = strtotime($record['openingtime']); + } + } + if (!empty($record['closingtime'])) { + if (is_numeric($record['closingtime'])) { + $record['closingtime'] = intval($record['closingtime']); + } else { + // If it is a string, we assume it is a timestamp. + $record['closingtime'] = strtotime($record['closingtime']); + } + } + foreach ($defaults as $key => $value) { if (!isset($record[$key])) { $record[$key] = $value; diff --git a/public/mod/bigbluebuttonbn/tests/generator_test.php b/public/mod/bigbluebuttonbn/tests/generator_test.php new file mode 100644 index 00000000000..199792f09a6 --- /dev/null +++ b/public/mod/bigbluebuttonbn/tests/generator_test.php @@ -0,0 +1,105 @@ +. + +namespace mod_bigbluebuttonbn; + +/** + * Genarator tests class for mod_bigbluebuttonbn. + * + * @package mod_bigbluebuttonbn + * @category test + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class generator_test extends \advanced_testcase { + /** + * Test the creation of a bigbluebuttonbn instance. + * @covers \mod_bigbluebuttonbn_generator::create_instance + */ + public function test_create_instance(): void { + $db = \core\di::get(\moodle_database::class); + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $bigbluebuttonbn = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course]); + $records = $db->get_records('bigbluebuttonbn', ['course' => $course->id], 'id'); + $this->assertEquals(1, count($records)); + $this->assertTrue(array_key_exists($bigbluebuttonbn->id, $records)); + + $params = ['course' => $course->id, 'name' => 'Another bigbluebuttonbn']; + $bigbluebuttonbn = $this->getDataGenerator()->create_module('bigbluebuttonbn', $params); + $records = $db->get_records('bigbluebuttonbn', ['course' => $course->id], 'id'); + $this->assertEquals(2, count($records)); + $this->assertEquals('Another bigbluebuttonbn', $records[$bigbluebuttonbn->id]->name); + } + + + /** + * Test the creation of a bigbluebuttonbn instance with a custom name. + * + * @param string|int $opening The opening time as a timestamp or human-readable date. + * @param string|int $closing The closing time as a timestamp or human-readable date + * @param int $expectedopening The expected opening time as a timestamp. + * @param int $expectedclosing The expected closing time as a timestamp. + * @covers \mod_bigbluebuttonbn_generator::create_instance + * @dataProvider provider_create_instance_with_name + */ + public function test_create_instance_with_dates( + string|int $opening, + string|int $closing, + int $expectedopening, + int $expectedclosing + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $params = ['course' => $course->id, 'openingtime' => $opening, 'closingtime' => $closing]; + $bigbluebuttonbn = $this->getDataGenerator()->create_module('bigbluebuttonbn', $params); + $instance = \mod_bigbluebuttonbn\instance::get_from_instanceid($bigbluebuttonbn->id); + $this->assertEquals($expectedopening, $instance->get_instance_var('openingtime')); + $this->assertEquals($expectedclosing, $instance->get_instance_var('closingtime')); + } + + /** + * Data provider for test_create_instance_with_dates. + * + * @return array[] + */ + public static function provider_create_instance_with_name(): array { + global $CFG; + require_once($CFG->libdir . '/testing/classes/frozen_clock.php'); + $clock = new \frozen_clock(); + \core\di::set(\core\clock::class, $clock); + $opening = $clock->time(); + $closing = $opening + DAYSECS; + return [ + 'Timestamp' => [ + $opening, + $closing, + $opening, + $closing, + ], + 'Human date' => [ + userdate($opening, get_string('strftimedatetimeaccurate', 'langconfig')), + userdate($closing, get_string('strftimedatetimeaccurate', 'langconfig')), + $opening, + $closing, + ], + ]; + } +}