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,87 @@
<?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 mod_data\backup;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php");
require_once($CFG->dirroot . '/rating/lib.php');
/**
* Restore date tests.
*
* @package mod_data
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_date_test extends \restore_date_testcase {
/**
* Test restore dates.
*/
public function test_restore_dates(): void {
global $DB, $USER;
$gg = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = ['assesstimefinish' => 100, 'assesstimestart' => 100, 'ratingtime' => 1, 'assessed' => 2, 'scale' => 1,
'timeavailablefrom' => 100, 'timeavailableto' => 100, 'timeviewfrom' => 100, 'timeviewto' => 100];
list($course, $data) = $this->create_course_and_module('data', $record);
// Data field/record.
$timestamp = 996699;
$diff = $this->get_diff();
$record = new \stdClass();
$record->name = 'field-1';
$record->type = 'text';
$field = $gg->create_field($record, $data);
$datarecordid = $gg->create_entry($data, [$field->field->id => 'NERDS NERDS EVERYWHERE, NO BRAIN TO THINK']);
$datarecord = $DB->get_record('data_records', ['id' => $datarecordid]);
// Ratings.
$ratingoptions = new \stdClass;
$ratingoptions->context = \context_module::instance($data->cmid);
$ratingoptions->ratingarea = 'entry';
$ratingoptions->component = 'mod_data';
$ratingoptions->itemid = $datarecord->id;
$ratingoptions->scaleid = 2;
$ratingoptions->userid = $USER->id;
$rating = new \rating($ratingoptions);
$rating->update_rating(2);
$rating = $DB->get_record('rating', ['itemid' => $datarecord->id]);
// Do backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newdata = $DB->get_record('data', ['course' => $newcourseid]);
$this->assertFieldsNotRolledForward($data, $newdata, ['timemodified']);
$props = ['assesstimefinish', 'assesstimestart', 'timeavailablefrom', 'timeavailableto', 'timeviewfrom', 'timeviewto'];
$this->assertFieldsRolledForward($data, $newdata, $props);
$newdatarecord = $DB->get_record('data_records', ['dataid' => $newdata->id]);
$newcm = $DB->get_record('course_modules', ['course' => $newcourseid, 'instance' => $newdata->id]);
// Data record time checks.
$this->assertEquals($datarecord->timecreated, $newdatarecord->timecreated);
$this->assertEquals($datarecord->timemodified, $newdatarecord->timemodified);
// Rating test.
$newrating = $DB->get_record('rating', ['contextid' => \context_module::instance($newcm->id)->id]);
$this->assertEquals($rating->timecreated, $newrating->timecreated);
$this->assertEquals($rating->timemodified, $newrating->timemodified);
}
}
@@ -0,0 +1,81 @@
<?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 mod_data\backup;
/**
* Restore type tests.
*
* @package mod_data
* @copyright 2024 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_type_test extends \advanced_testcase {
/**
* Data provider for test_duplicating_data_remove_unwanted_types.
*
* @return array[]
*/
public static function restore_format_test_provider(): array {
return [
'text' => [
'type' => 'text',
'expected' => 'text',
],
'picture' => [
'type' => 'picture',
'expected' => 'picture',
],
'wrong type' => [
'type' => '../wrongtype123',
'expected' => 'wrongtype',
],
];
}
/**
* Test that duplicating a database removes unwanted / invalid format.
*
* @param string $type The type of the field.
* @param string $expected The expected type of the field after duplication.
*
* @covers \restore_data_activity_structure_step
* @dataProvider restore_format_test_provider
*/
public function test_duplicating_data_remove_unwanted_types(string $type, string $expected): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Make a test course.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$data = $this->getDataGenerator()->create_module('data', ['course' => $course->id]);
$this->getDataGenerator()->get_plugin_generator('mod_data')->create_field(
(object) ['name' => 'field', 'type' => $type],
$data
);
// Duplicate the data module with the type.
$newdata = duplicate_module($course, get_fast_modinfo($course)->get_cm($data->cmid));
// Verify the settings of the duplicated activity.
$fields = $DB->get_records('data_fields', ['dataid' => $newdata->instance], 'id');
$newfield = reset($fields);
$this->assertEquals($expected, $newfield->type);
}
}
@@ -0,0 +1,351 @@
@mod @mod_data
Feature: Users can add the ##actionsmenu## replacement to the database templates
In order to display all the actions for entries in templates
As a teacher
I need to edit the templates and add the actionsmenu replacement
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
| data1 | text | field2 | Test field 2 description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | field1 | field2 |
| data1 | student1 | Student entry 1 | Some student content 1 |
| data1 | teacher1 | Teacher entry 1 | Some teacher content 1 |
And I am on the "Test database name" "data activity" page logged in as teacher1
And I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
And I set the field "Enable code editor" to "0"
And I set the following fields to these values:
| Header | <table> |
| Repeated entry | <tr><td>[[field1]]</td><td>##actionsmenu##</td><tr> |
| Footer | </table> |
And I click on "Save" "button" in the "sticky-footer" "region"
And I set the field "Templates tertiary navigation" to "Single view template"
And I set the following fields to these values:
| Single view template | <table><tr><td>[[field1]]</td><td>[[field2]]</td><td>##actionsmenu##</td><tr></table> |
And I click on "Save" "button" in the "sticky-footer" "region"
@javascript
Scenario: The ##actionsmenu## replacement displays the expected actions with default settings depending on the user permissions
Given I navigate to "Database" in current page administration
# Teachers should be able to edit/delete all the entries.
When I open the action menu in "Student entry 1" "table_row"
Then I should see "Show more"
And I should see "Edit"
And I should see "Delete"
But I should not see "Approve"
And I should not see "Undo approval"
And I should not see "Export to portfolio"
And I press the escape key
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Show more"
And I should see "Edit"
And I should see "Delete"
# Single view (for teacher).
And I choose "Show more" in the open action menu
And I should see "Teacher entry 1"
And I should see "Some teacher content 1"
And I should not see "Student entry 1"
And I open the action menu in "Teacher entry 1" "table_row"
And I should not see "Show more"
And I should see "Edit"
And I should see "Delete"
And I should not see "Approve"
And I should not see "Undo approval"
And I should not see "Export to portfolio"
And I press the escape key
And I follow "Previous page"
And I should see "Student entry 1"
And I should see "Some student content 1"
And I should not see "Teacher entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should not see "Show more"
And I should see "Edit"
And I should see "Delete"
And I log out
# Students only should edit/delete their entries.
But I am on the "Test database name" "data activity" page logged in as student1
And I open the action menu in "Student entry 1" "table_row"
And I should see "Show more"
And I should see "Edit"
And I should see "Delete"
And I should not see "Approve"
And I should not see "Undo approval"
And I should not see "Export to portfolio"
And I press the escape key
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Show more"
And I should not see "Edit"
And I should not see "Delete"
# Single view (for student).
And I choose "Show more" in the open action menu
And I should see "Teacher entry 1"
And I should see "Some teacher content 1"
And I should not see "Student entry 1"
And I should not see "Actions" in the "Teacher entry 1" "table_row"
And I follow "Previous page"
And I should see "Student entry 1"
And I should see "Some student content 1"
And I should not see "Teacher entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should not see "Show more"
And I should see "Edit"
And I should see "Delete"
And I should not see "Approve"
And I should not see "Undo approval"
And I should not see "Export to portfolio"
@javascript
Scenario: The ##actionsmenu## replacement displays the Approval/Undo approval options
Given I navigate to "Settings" in current page administration
And I follow "Entries"
And I set the field "Approval required" to "Yes"
And I press "Save and display"
When I navigate to "Database" in current page administration
# Teachers should be able to approve/unapprove all the entries from list view.
And I open the action menu in "Student entry 1" "table_row"
Then I should see "Approve"
And I should not see "Undo approval"
And I choose "Approve" in the open action menu
And I should see "Entry approved"
And I press "Dismiss this notification"
And I open the action menu in "Student entry 1" "table_row"
And I should see "Undo approval"
And I should not see "Approve" in the ".menu-action-text" "css_element"
And I press the escape key
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Undo approval"
And I should not see "Approve" in the ".menu-action-text" "css_element"
# Single view (for teacher).
And I choose "Show more" in the open action menu
And I should see "Teacher entry 1"
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Undo approval"
And I should not see "Approve"
And I press the escape key
And I follow "Previous page"
And I should see "Student entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should not see "Approve"
And I should see "Undo approval"
# Check entries can be approved/unapproved from single view too.
And I choose "Undo approval" in the open action menu
And I should see "Entry unapproved"
And I press "Dismiss this notification"
And I open the action menu in "Student entry 1" "table_row"
And I should see "Approve"
And I should not see "Undo approval"
And I log out
# Students should not see the Approve/Undo approval options.
But I am on the "Test database name" "data activity" page logged in as student1
And I open the action menu in "Teacher entry 1" "table_row"
And I should not see "Approve"
And I should not see "Undo approval"
And I press the escape key
And I open the action menu in "Student entry 1" "table_row"
And I should not see "Approve"
And I should not see "Undo approval"
# Single view (for student).
And I choose "Show more" in the open action menu
And I should see "Student entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should not see "Approve"
And I should not see "Undo approval"
And I follow "Next page"
And I should see "Teacher entry 1"
And I should not see "Actions" in the "Teacher entry 1" "table_row"
@javascript
Scenario: The ##actionsmenu## replacement displays the Export to portfolio options
Given I log in as "admin"
And the following config values are set as admin:
| enableportfolios | 1 |
And I navigate to "Plugins > Portfolios > Manage portfolios" in site administration
And I set portfolio instance "File download" to "Enabled and visible"
And I click on "Save" "button"
And I log out
And I am on the "Test database name" "data activity" page logged in as teacher1
# Teachers should be able to export to portfolio all the entries from list view.
When I open the action menu in "Student entry 1" "table_row"
Then I should see "Export to portfolio"
And I choose "Export to portfolio" in the open action menu
And I should see "Configure exported data"
And I press "Cancel"
And I click on "Yes" "button" in the "Confirm" "dialogue"
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Export to portfolio"
# Single view (for teacher).
And I choose "Show more" in the open action menu
And I should see "Teacher entry 1"
And I open the action menu in "Teacher entry 1" "table_row"
And I should see "Export to portfolio"
And I press the escape key
And I follow "Previous page"
And I should see "Student entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should see "Export to portfolio"
# Check entries can be exported from single view too.
And I choose "Export to portfolio" in the open action menu
And I should see "Configure exported data"
And I log out
# Students should only export their entries.
But I am on the "Test database name" "data activity" page logged in as student1
And I open the action menu in "Teacher entry 1" "table_row"
And I should not see "Export to portfolio"
And I press the escape key
And I open the action menu in "Student entry 1" "table_row"
And I should see "Export to portfolio"
And I choose "Export to portfolio" in the open action menu
And I should see "Configure exported data"
And I press "Cancel"
And I click on "Yes" "button" in the "Confirm" "dialogue"
And I open the action menu in "Teacher entry 1" "table_row"
# Single view (for student).
And I choose "Show more" in the open action menu
And I should see "Teacher entry 1"
And I should not see "Actions" in the "Teacher entry 1" "table_row"
And I follow "Previous page"
And I should see "Student entry 1"
And I open the action menu in "Student entry 1" "table_row"
And I should see "Export to portfolio"
And I choose "Export to portfolio" in the open action menu
And I should see "Configure exported data"
@javascript
Scenario: The ##actionsmenu## replacement does not display the Export to portfolio option when there are no portfolios enabled
Given I log in as "admin"
And the following config values are set as admin:
| enableportfolios | 1 |
And I log out
And I am on the "Test database name" "data activity" page logged in as teacher1
When I open the action menu in "Student entry 1" "table_row"
Then I should not see "Export to portfolio"
And I log out
# If we enable, at least, one portfolio, the Export to portfolio option should be displayed.
But I log in as "admin"
And I navigate to "Plugins > Portfolios > Manage portfolios" in site administration
And I set portfolio instance "File download" to "Enabled and visible"
And I click on "Save" "button"
And I log out
And I am on the "Test database name" "data activity" page logged in as teacher1
And I open the action menu in "Student entry 1" "table_row"
And I should see "Export to portfolio"
@javascript
Scenario: The Edit option in the ##actionsmenu## replacement is working
Given I navigate to "Database" in current page administration
# Teachers should be able to edit any entry.
And I open the action menu in "Student entry 1" "table_row"
When I choose "Edit" in the open action menu
And I set the field "field2" to "Some MODIFIED BY THE TEACHER student content 1"
And I click on "Save" "button"
# Single view (for teacher).
Then I should see "Some MODIFIED BY THE TEACHER student content 1"
And I should not see "Some student content 1"
And I open the action menu in "Student entry 1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "field2" to "Some MORE TEACHER MODIFICATIONS FOR student content 1"
And I click on "Save" "button"
And I should see "Some MORE TEACHER MODIFICATIONS FOR student content 1"
And I should not see "Some MODIFIED BY THE TEACHER student content 1"
And I log out
# Students only should edit their entries.
But I am on the "Test database name" "data activity" page logged in as student1
And I open the action menu in "Student entry 1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "field2" to "Some MODIFIED student content 1"
And I click on "Save" "button"
# Single view (for student).
And I should see "Some MODIFIED student content 1"
And I should not see "Some MORE TEACHER MODIFICATIONS FOR student content 1"
And I open the action menu in "Student entry 1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "field2" to "Some MORE MODIFICATIONS FOR student content 1"
And I click on "Save" "button"
And I should see "Some MORE MODIFICATIONS FOR student content 1"
And I should not see "Some MODIFIED student content 1"
@javascript
Scenario: The Delete option in the ##actionsmenu## replacement is working
Given the following "mod_data > entries" exist:
| database | user | field1 | field2 |
| data1 | student1 | Student entry 2 | Some student content 2 |
| data1 | teacher1 | Teacher entry 2 | Some teacher content 2 |
And I navigate to "Database" in current page administration
# Teachers should be able to delete any entry.
And I open the action menu in "Student entry 1" "table_row"
When I choose "Delete" in the open action menu
Then I should see "Delete entry"
# Cancel doesn't delete the entry.
And I click on "Cancel" "button" in the "Confirm" "dialogue"
And I open the action menu in "Teacher entry 1" "table_row"
# But Delete removes the entry.
And I choose "Delete" in the open action menu
And I should see "Delete entry"
And I click on "Delete" "button" in the "Confirm" "dialogue"
And I should see "Entry deleted"
And I should not see "Teacher entry 1"
And I should see "Teacher entry 2"
And I should see "Student entry 1"
And I should see "Student entry 2"
# Single view (for teacher).
And I open the action menu in "Teacher entry 2" "table_row"
And I choose "Delete" in the open action menu
And I should see "Delete entry"
And I click on "Delete" "button" in the "Confirm" "dialogue"
And I should see "Entry deleted"
And I should not see "Teacher entry 1"
And I should not see "Teacher entry 2"
And I should see "Student entry 1"
And I should see "Student entry 2"
And I log out
# Students only should edit their entries.
But I am on the "Test database name" "data activity" page logged in as student1
And I open the action menu in "Student entry 1" "table_row"
When I choose "Delete" in the open action menu
Then I should see "Delete entry"
# Cancel doesn't delete the entry.
And I click on "Cancel" "button" in the "Confirm" "dialogue"
And I open the action menu in "Student entry 1" "table_row"
# But Delete removes the entry.
And I choose "Delete" in the open action menu
And I should see "Delete entry"
And I click on "Delete" "button" in the "Confirm" "dialogue"
And I should see "Entry deleted"
And I should not see "Teacher entry 1"
And I should not see "Teacher entry 2"
And I should not see "Student entry 1"
And I should see "Student entry 2"
# Single view (for student).
And I open the action menu in "Student entry 2" "table_row"
And I choose "Delete" in the open action menu
And I should see "Delete entry"
And I click on "Delete" "button" in the "Confirm" "dialogue"
And I should see "Entry deleted"
And I should not see "Teacher entry 1"
And I should not see "Teacher entry 2"
And I should not see "Student entry 1"
And I should not see "Student entry 2"
+155
View File
@@ -0,0 +1,155 @@
@mod @mod_data
Feature: Users can add entries to database activities
In order to populate databases
As a user
I need to add entries to databases
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | n | C1 | data1 |
@javascript
Scenario: Students can add entries to a database
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
| data1 | text | Test field 2 name | Test field 2 description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name |
| data1 | student1 | Student original entry | Student original entry 2 |
And I am on the "data1" Activity page logged in as student1
And I open the action menu in "#data-listview-content" "css_element"
And I choose "Edit" in the open action menu
And I set the following fields to these values:
| Test field name | Student original entry |
| Test field 2 name | |
And I press "Save"
Then I should not see "Student original entry 2"
And I open the action menu in "#data-singleview-content" "css_element"
And I choose "Edit" in the open action menu
And I set the following fields to these values:
| Test field name | Student edited entry |
And I press "Save"
And I should see "Student edited entry"
And the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name |
| data1 | student1 | Student second entry | |
| data1 | student1 | Student third entry | |
And I am on the "data1" Activity page logged in as student1
And I should see "Student edited entry"
And I should see "Student second entry"
And I should see "Student third entry"
# Will delete the first one.
And I open the action menu in ".defaulttemplate-listentry" "css_element"
And I choose "Delete" in the open action menu
And I press "Delete"
And I should not see "Student edited entry"
And I should see "Student second entry"
And I should see "Student third entry"
@javascript
Scenario: If a new text area entry is added, the filepicker is displayed in the H5P editor dialogue
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | textarea | Textarea field name |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And I am on the "Course 1" course page logged in as teacher1
When I click on "Test database name" "link"
And I click on "Add entry" "button"
And I click on "Configure H5P content" "button"
Then I should see "Browse repositories..." in the "Insert H5P content" "dialogue"
@javascript
Scenario: If maximum number of entries is set other than None then add entries should be seen only if number of entries is less than it
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | Test1 |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | Test1 |
| data1 | student1 | foo |
| data1 | student1 | bar |
And I am on the "Test database name" "data activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Maximum number of entries | 2 |
And I press "Save and display"
And I log out
When I am on the "Test database name" "data activity" page logged in as student1
Then I should not see "Add entry"
And I log out
And I am on the "Test database name" "data activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Maximum number of entries | 3 |
And I press "Save and display"
And I log out
And I am on the "Test database name" "data activity" page logged in as student1
And I should see "Add entry"
@javascript
Scenario: Guest user cannot add entries to a database
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | Textarea field name |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And I am on the "Course 1" "enrolment methods" page logged in as teacher1
And I click on "Enable" "link" in the "Guest access" "table_row"
And I log out
When I am on the "Test database name" "data activity" page logged in as "guest"
Then I should not see "Add entry"
@javascript
Scenario Outline: Users see the Add entry button in the view page when some field has been created only.
Given I am on the "Test database name" "data activity" page logged in as <user>
And I should not see "Add entry"
And I log out
When the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
Then I am on the "Test database name" "data activity" page logged in as <user>
And I should see "Add entry"
Examples:
| user |
| teacher1 |
| student1 |
@@ -0,0 +1,82 @@
@mod @mod_data
Feature: Database entries can be searched using an advanced search form.
In order to find an entry
As a user
I need to have an advanced search form
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | n | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | My Field | Field 1 description |
| data1 | text | Their field | Field 2 description |
And the following "mod_data > entries" exist:
| database | user | My Field | Their field |
| data1 | teacher1 | First content | Owned content |
| data1 | teacher1 | Second content | Authored content |
And I am on the "Test database name" "data activity" page logged in as teacher1
And I should see "First content"
And I should see "Second content"
@javascript
Scenario: Content can be searched using advanced search
Given I click on "Advanced search" "checkbox"
And I should see "My Field" in the "data_adv_form" "region"
And I should see "Their field" in the "data_adv_form" "region"
When I set the field "My Field" to "First"
And I click on "Save settings" "button" in the "data_adv_form" "region"
Then I should see "First content"
And I should not see "Second content"
@javascript
Scenario: Advanced search template can use field information tags
Given I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "Advanced search template"
And I set the following fields to these values:
| Advanced search template | The test is on [[My Field#name]], [[My Field#description]], and the input [[My Field]] |
And I click on "Save" "button" in the "sticky-footer" "region"
And I navigate to "Database" in current page administration
And I should see "First content"
And I should see "Second content"
And I click on "Advanced search" "checkbox"
And I should see "The test is on My Field, Field 1 description, and the input" in the "data_adv_form" "region"
And I should not see "Their field" in the "data_adv_form" "region"
When I set the field "My Field" to "First"
And I click on "Save settings" "button" in the "data_adv_form" "region"
Then I should see "First content"
And I should not see "Second content"
@javascript
Scenario: Advanced search can use otherfields tag
Given I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "Advanced search template"
And I set the following fields to these values:
| Advanced search template | Main search [[My Field]], Other fields ##otherfields## |
And I click on "Save" "button" in the "sticky-footer" "region"
And I navigate to "Database" in current page administration
And I should see "First content"
And I should see "Second content"
And I click on "Advanced search" "checkbox"
And I should see "Main search" in the "data_adv_form" "region"
And I should see "Other fields" in the "data_adv_form" "region"
And I should see "Their field" in the "data_adv_form" "region"
When I set the field "My Field" to "First"
And I click on "Save settings" "button" in the "data_adv_form" "region"
Then I should see "First content"
And I should not see "Second content"
And I set the field "My Field" to ""
And I set the field "Their field" to "Authored content"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And I should not see "First content"
And I should see "Second content"
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
/**
* Steps definitions related with the database activity.
*
* @package mod_data
* @category test
* @copyright 2014 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\Gherkin\Node\TableNode as TableNode;
/**
* Database-related steps definitions.
*
* @package mod_data
* @category test
* @copyright 2014 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_data extends behat_base {
/**
* Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
*
* Recognised page names are:
* | pagetype | name meaning | description |
* | Add entry | Database name | Add an entry page (view.php) |
*
* @param string $type identifies which type of page this is, e.g. 'Add entry'.
* @param string $identifier identifies the particular page, e.g. 'My database name'.
* @return moodle_url the corresponding URL.
* @throws Exception with a meaningful error message if the specified page cannot be found.
*/
protected function resolve_page_instance_url(string $type, string $identifier): moodle_url {
global $DB;
switch (strtolower($type)) {
case 'add entry':
return new moodle_url('/mod/data/edit.php', [
'd' => $this->get_cm_by_activity_name('data', $identifier)->instance,
]);
default:
throw new Exception("Unrecognised page type '{$type}'");
}
}
}
@@ -0,0 +1,99 @@
<?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/>.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
use Behat\Gherkin\Node\TableNode as TableNode;
require_once(__DIR__ . '/../../../../lib/behat/behat_deprecated_base.php');
/**
* Steps definitions that are now deprecated and will be removed in the next releases.
*
* This file only contains the steps that previously were in the behat_*.php files in the SAME DIRECTORY.
* When deprecating steps from other components or plugins, create a behat_COMPONENT_deprecated.php
* file in the same directory where the steps were defined.
*
* @package mod_data
* @category test
* @copyright 2024 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_data_deprecated extends behat_deprecated_base
{
/**
* Adds a new field to a database
*
* @Given /^I add a "(?P<fieldtype_string>(?:[^"]|\\")*)" field to "(?P<activityname_string>(?:[^"]|\\")*)" database and I fill the form with:$/
*
* @param string $fieldtype
* @param string $activityname
* @param TableNode $fielddata
* @todo MDL-79721 This will be deleted in Moodle 4.8.
*
* @deprecated since 4.4
*/
public function i_add_a_field_to_database_and_i_fill_the_form_with($fieldtype, $activityname, TableNode $fielddata)
{
$this->deprecated_message([
'behat_mod_data::i_add_a_field_to_database_and_i_fill_the_form_with is deprecated',
'mod_data > fields generator',
]);
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($activityname), 'data activity']);
$fieldsstr = get_string('fields', 'mod_data');
$this->execute("behat_navigation::i_navigate_to_in_current_page_administration", $fieldsstr);
$this->execute('behat_general::i_click_on', [get_string('newfield', 'mod_data'), "button"]);
$this->execute('behat_general::i_click_on_in_the',
[$this->escape($fieldtype), "link", "#action_bar", "css_element"]
);
if (!$this->running_javascript()) {
$this->execute('behat_general::i_click_on_in_the',
array(get_string('go'), "button", ".fieldadd", "css_element")
);
}
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $fielddata);
$this->execute('behat_forms::press_button', get_string('save'));
}
/**
* Adds an entry to a database.
*
* @Given /^I add an entry to "(?P<activityname_string>(?:[^"]|\\")*)" database with:$/
*
* @param string $activityname
* @param TableNode $entrydata
* @deprecated since 4.4
* @todo MDL-79721 This will be deleted in Moodle 4.8.
*
*/
public function i_add_an_entry_to_database_with($activityname, TableNode $entrydata)
{
$this->deprecated_message([
'behat_mod_data::i_add_an_entry_to_database_with is deprecated',
'mod_data > entries generator',
]);
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($activityname), 'mod_data > add entry']);
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $entrydata);
}
}
@@ -0,0 +1,44 @@
@mod @mod_data
Feature: Teachers can enable comments only if comments are enabled at site level
In order to enable comments on entries
As an admin
I need to enable comments at site level
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I turn editing mode on
@javascript
Scenario: Teacher can enable comments if they are enabled at site level
Given I press "Add an activity or resource"
And I click on "Add a new Database" "link" in the "Add an activity or resource" "dialogue"
When I expand all fieldsets
And "Allow comments on entries" "field" should exist
And I set the field "Name" to "Test Database name"
And I set the field "Allow comments on entries" to "Yes"
And I press "Save and return to course"
And I should see "Test Database name"
@javascript
Scenario: Teacher cannot enable comments if they are disabled at site level
# Disable comments in site config.
Given the following config values are set as admin:
| usecomments | 0 |
And I press "Add an activity or resource"
And I click on "Add a new Database" "link" in the "Add an activity or resource" "dialogue"
When I expand all fieldsets
And I set the field "Name" to "Test Database name 2"
And "Allow comments on entries" "field" should not exist
Then I should see "No" in the "//*[@id=\"fitem_id_comments\"]/*[@data-fieldtype=\"selectyesno\"]" "xpath_element"
And I press "Save and return to course"
And I should see "Test Database name 2"
@@ -0,0 +1,42 @@
@mod @mod_data
Feature: Users can view the list of data activities and their formatted descriptions
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Bob | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database 1 | This is an intro without an image | C1 | data1 |
| data | Test database 2 | This is an intro with an image: <img src="@@PLUGINFILE@@/some_image.jpg"> | C1 | data2 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| activity_modules | Course | C1 | course-view-* | side-pre |
Scenario: Teachers can view the list of data activities and their formatted descriptions
Given I am on the "Course 1" course page logged in as teacher1
When I follow "Databases"
Then I should see "Test database 1"
And I should see "Test database 2"
And I should see "This is an intro without an image"
And I should see "This is an intro with an image: "
And "//img[contains(@src, 'some_image.jpg')]" "xpath_element" should exist
And "//img[contains(@src, '@@PLUGINFILE@@/some_image.jpg')]" "xpath_element" should not exist
Scenario: Students can view the list of data activities and their formatted descriptions
Given I am on the "Course 1" course page logged in as student1
When I follow "Databases"
Then I should see "Test database 1"
And I should see "Test database 2"
And I should see "This is an intro without an image"
And I should see "This is an intro with an image: "
And "//img[contains(@src, 'some_image.jpg')]" "xpath_element" should exist
And "//img[contains(@src, '@@PLUGINFILE@@/some_image.jpg')]" "xpath_element" should not exist
@@ -0,0 +1,112 @@
@mod @mod_data @core_completion @javascript
Feature: View activity completion in the database activity
In order to have visibility of database completion requirements
As a student
I need to be able to view my database completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| teacher1 | Darrell | Teacher1 | 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 |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activity" exists:
| activity | data |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
| completionentriesenabled | 1 |
| completionentries | 2 |
And the following "mod_data > fields" exist:
| database | type | name |
| mh1 | text | Instrument types |
And the following "mod_data > templates" exist:
| database | name |
| mh1 | singletemplate |
| mh1 | listtemplate |
| mh1 | addtemplate |
| mh1 | asearchtemplate |
| mh1 | rsstemplate |
Given I am on the "Music history" "data activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the following fields to these values:
| Aggregate type | Average of ratings |
| scale[modgrade_type] | Point |
| scale[modgrade_point] | 100 |
| Add requirements | 1 |
| View the activity | 1 |
| Receive a grade | 1 |
| Any grade | 1 |
And I press "Save and display"
And I log out
Scenario: View automatic completion items as a teacher
# We add an entry to let the user change to a different view.
Given the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | teacher1 | Drums |
When I am on the "Music history" "data activity" page logged in as teacher1
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And I select "Single view" from the "jump" singleselect
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
Scenario: View automatic completion items as a student
Given I am on the "Music history" "data activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Drums |
And I am on "Course 1" course homepage
# One entry is not enough to mark as complete.
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Hurdygurdy |
And I am on "Course 1" course homepage
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "data activity" page logged in as teacher1
And I select "Single view" from the "jump" singleselect
And I set the field "rating" to "3"
And I log out
When I am on the "Music history" "data activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And I log out
When I am on the "Course 1" course page logged in as teacher1
And "Vinnie Student1" user has completed "Music history" activity
@javascript
Scenario: Use manual completion
Given I am on the "Music history" "data activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the field "Students must manually mark the activity as done" to "1"
And I press "Save and display"
# Teacher view.
And the manual completion button for "Music history" should be disabled
And I log out
# Student view.
When I am on the "Music history" "data activity" page logged in as student1
Then the manual completion button of "Music history" is displayed as "Mark as done"
And I toggle the manual completion state of "Music history"
And the manual completion button of "Music history" is displayed as "Done"
@@ -0,0 +1,141 @@
@mod @mod_data @core_completion
Feature: Completion pass grade
View activity completion in the database activity
In order to have visibility of database completion requirements
As a student
I need to be able to view my database completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activity" exists:
| activity | data |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
And the following "mod_data > fields" exist:
| database | type | name |
| mh1 | text | Instrument types |
And I am on the "Music history" "data activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Aggregate type | Average of ratings |
| scale[modgrade_type] | Point |
| scale[modgrade_point] | 100 |
| gradepass | 50 |
| Add requirements | 1 |
| View the activity | 1 |
| Receive a grade | 1 |
| Passing grade | 1 |
| completionentriesenabled | 1 |
| completionentries | 2 |
And I press "Save and display"
And I log out
@javascript
Scenario: View automatic completion items as a teacher
# We add an entry to let the user change to a different view.
Given the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | teacher1 | Drums |
When I am on the "Music history" "data activity" page logged in as teacher1
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I select "Single view" from the "jump" singleselect
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
@javascript
Scenario: View automatic completion items as a failing student
Given I am on the "Music history" "data activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Drums |
And I am on "Course 1" course homepage
# One entry is not enough to mark as complete.
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Hurdygurdy |
And I am on "Course 1" course homepage
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "data activity" page logged in as teacher1
And I select "Single view" from the "jump" singleselect
And I set the field "rating" to "3"
And I log out
When I am on the "Music history" "data activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
@javascript
Scenario: View automatic completion items as a passing student
Given I am on the "Music history" "data activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Drums |
And I am on "Course 1" course homepage
# One entry is not enough to mark as complete.
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the following "mod_data > entries" exist:
| database | user | Instrument types |
| mh1 | student1 | Hurdygurdy |
And I am on "Course 1" course homepage
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "data activity" page logged in as teacher1
And I select "Single view" from the "jump" singleselect
And I set the field "rating" to "60"
And I log out
When I am on the "Music history" "data activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
@@ -0,0 +1,53 @@
@mod @mod_data
Feature: Enable activity rating according to chosen grading scale
In order to have ratings appear in the course gradebook
As a teacher
I need to enable activity rating according to chosen grading scale
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
| student2 | Student | Two | student2@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activities" exist:
| activity | course | name | idnumber |
| data | C1 | DB activity 1 | db1 |
And the following "mod_data > fields" exist:
| database | type | name |
| db1 | text | DB field |
@javascript
Scenario: View ratings in the course gradebook
Given I am on the "DB activity 1" "data activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the following fields to these values:
| Aggregate type | Count of ratings |
| scale[modgrade_type] | Point |
| scale[modgrade_point] | 10 |
And I press "Save and display"
And the following "mod_data > entries" exist:
| database | user | DB field |
| db1 | student1 | S1 entry 1 |
| db1 | student1 | S1 entry 2 |
| db1 | student2 | S2 entry 1 |
And I am on the "DB activity 1" "data activity" page
And I select "Single view" from the "jump" singleselect
And I set the field "rating" to "5"
And I follow "Next page"
And I set the field "rating" to "7"
And I follow "Next page"
And I set the field "rating" to "10"
When I am on the "Course 1" "grades > Grader report > View" page
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student One | student1@example.com | 2.00 |
| Student Two | student2@example.com | 1.00 |
@@ -0,0 +1,33 @@
@mod @mod_data
Feature: Control database activity entry based on read-only dates
In order to restrict or allow student entries based on specific dates
As a teacher
I need to be able to set read-only dates for the database activity
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
Scenario Outline: Student can add entries only when the current date falls outside the read-only date range
Given the following "activities" exist:
| activity | course | name | idnumber | timeviewfrom | timeviewto |
| data | C1 | Data Activity 1 | DB1 | <viewfrom> | <viewto> |
And the following "mod_data > fields" exist:
| database | type | name |
| DB1 | text | DB Field 1 |
When I am on the "Data Activity 1" "data activity" page logged in as student1
# The "Add entry" button is visible only when the current date falls outside the read-only date range.
Then "Add entry" "button" <btnvisibility> exist
Examples:
| viewfrom | viewto | btnvisibility |
| ##yesterday## | ##tomorrow## | should not |
| ##tomorrow## | ##tomorrow +1day## | should |
| ##1 week ago## | ##yesterday## | should |
@@ -0,0 +1,42 @@
@mod @mod_data
Feature: Students can view upcoming data activities in the timeline block
In order for student to see upcoming data activities in timeline block
As a teacher
I should be able to set the availability dates of data activities
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | One | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
@javascript
Scenario Outline: Student can view upcoming data activities in the timeline block
Given the following "activities" exist:
| activity | course | name | id_timeavailablefrom_enabled | timeavailablefrom | id_timeavailableto_enabled | timeavailableto |
| data | C1 | DB Past | 1 | <pastfrom> | 1 | <pastto> |
| data | C1 | DB Future | 1 | <futurefrom> | 1 | <futureto> |
| data | C1 | DB No date | 0 | | 0 | |
# Confirm that student can see future but not past db activity in the timeline block
When I log in as "student 1"
Then I should not see "DB Past" in the "Timeline" "block"
# Also confirm that student can't see db activity where availability is disabled
And I should not see "DB No Date" in the "Timeline" "block"
And I should see "DB Future" in the "Timeline" "block"
# Confirm link works and redirects to db activity
And I click on "DB Future" "link" in the "Timeline" "block"
And the activity date in "DB Future" should contain "Opens:"
And the activity date in "DB Future" should contain "<futurefrom>%A, %d %B %Y, %I:%M##"
And the activity date in "DB Future" should contain "Closes:"
And the activity date in "DB Future" should contain "<futureto>%A, %d %B %Y, %I:%M##"
Examples:
| pastfrom | pastto | futurefrom | futureto |
| ##1 month ago## | ##yesterday## | ##tomorrow## | ##tomorrow +1day## |
| ##yesterday## | ##yesterday +3hours## | ##tomorrow noon## | ##tomorrow noon +3hours## |
| ##6 months ago## | ##1 week ago## | ##tomorrow +5days## | ##tomorrow +6days## |
@@ -0,0 +1,61 @@
@mod @mod_data
Feature: Database with no calendar capabilites
In order to allow work effectively
As a teacher
I need to be able to create databases even when I cannot edit calendar events
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 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 I log in as "admin"
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 |
And I log out
Scenario: Editing a database
Given I log in as "admin"
And the following "activities" exist:
| activity | name | intro | course | section | idnumber |
| data | Test database name | Test database description | C1 | 1 | data1 |
And I am on "Course 1" course homepage
And I follow "Test database name"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| id_timeavailablefrom_enabled | 1 |
| id_timeavailablefrom_day | 1 |
| id_timeavailablefrom_month | 1 |
| id_timeavailablefrom_year | 2017 |
| id_timeavailableto_enabled | 1 |
| id_timeavailableto_day | 1 |
| id_timeavailableto_month | 4 |
| id_timeavailableto_year | 2017 |
| id_timeviewfrom_enabled | 1 |
| id_timeviewfrom_day | 1 |
| id_timeviewfrom_month | 3 |
| id_timeviewfrom_year | 2017 |
| id_timeviewto_enabled | 1 |
| id_timeviewto_day | 1 |
| id_timeviewto_month | 4 |
| id_timeviewto_year | 2017 |
And I press "Save and return to course"
And I log out
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test database name"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| id_timeavailablefrom_year | 2018 |
| id_timeavailableto_year | 2018 |
| id_timeviewfrom_year | 2018 |
| id_timeviewto_year | 2018 |
And I press "Save and return to course"
Then I should see "Test database name"
+353
View File
@@ -0,0 +1,353 @@
@mod @mod_data
Feature: Users can view and manage data presets
In order to use presets
As a user
I need to view, manage and use presets
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Mountain landscapes | n | C1 | data1 |
And the following "mod_data > presets" exist:
| database | name | description | user |
| data1 | Saved preset 1 | The preset1 has description | admin |
| data1 | Saved preset 2 | | admin |
| data1 | Saved preset by teacher1 | This preset has also a description | teacher1 |
@javascript
Scenario: Admins can delete saved presets
Given I am on the "Mountain landscapes" "data activity" page logged in as admin
When I follow "Presets"
Then I should see "Choose a preset to use as a starting point."
And I should see "Image gallery"
And I should see "Saved preset 1"
And I should see "Saved preset 2"
And I should see "Saved preset by teacher1"
# Plugin presets can't be removed.
And I should not see "Actions" in the "Image gallery" "table_row"
# The admin should be able to delete saved presets.
But I open the action menu in "Saved preset 1" "table_row"
And I should see "Delete"
And I press the escape key
And I open the action menu in "Saved preset 2" "table_row"
And I should see "Delete"
And I press the escape key
And I open the action menu in "Saved preset by teacher1" "table_row"
And I should see "Delete"
@javascript
Scenario: Teachers can see and use presets
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
Then I should see "Choose a preset to use as a starting point."
And I should see "Image gallery"
And I should see "Use this preset to collect images." in the "Image gallery" "table_row"
And I should see "Saved preset 1"
And I should see "The preset1 has description" in the "Saved preset 1" "table_row"
And I should see "Saved preset 2"
And I should see "Saved preset by teacher1"
And I should see "This preset has also a description" in the "Saved preset by teacher1" "table_row"
# Plugin presets can't be removed.
And I should not see "Actions" in the "Image gallery" "table_row"
# Teachers should be able to delete their saved presets.
And I open the action menu in "Saved preset by teacher1" "table_row"
And I should see "Delete"
# Teachers can't delete the presets they haven't created.
And I should not see "Actions" in the "Saved preset 1" "table_row"
# The "Use this preset" button should be enabled only when a preset is selected.
And the "Use this preset" "button" should be disabled
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And the "Use this preset" "button" should be enabled
@javascript
Scenario: Only users with the viewalluserpresets capability can see presets created by other users
Given the following "permission override" exists:
| role | editingteacher |
| capability | mod/data:viewalluserpresets |
| permission | Prohibit |
| contextlevel | System |
| reference | |
When I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
Then I should see "Image gallery"
And I should not see "Saved preset 1"
And I should not see "Saved preset 2"
But I should see "Saved preset by teacher1"
@javascript
Scenario: Teachers can save presets
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Templates"
When I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
Then I should see "Name" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "Description" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And "Replace existing preset with this name and overwrite its contents" "checkbox" should not be visible
# Teacher should be able to save preset.
And I set the field "Name" to "New saved preset"
And I set the field "Description" to "My funny description goes here."
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "Preset saved."
And I follow "Presets"
And I should see "New saved preset"
And I should see "My funny description goes here." in the "New saved preset" "table_row"
# Teacher can't overwrite an existing preset that they haven't created.
And I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "Saved preset 1"
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "A preset with this name already exists. Choose a different name."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should not be visible
# Teacher can overwrite existing presets created by them, but they are not overwritten if the checkbox is not marked.
And I set the field "Name" to "New saved preset"
And I set the field "Description" to "This is a new description that shouldn't be saved."
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "A preset with this name already exists."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should be visible
# Confirm the checkbox is still displayed and nothing happens if it's not checked and no change is done in the name.
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "A preset with this name already exists."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should be visible
And I click on "Cancel" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I follow "Presets"
And I should see "New saved preset"
And I should see "My funny description goes here." in the "New saved preset" "table_row"
And I should not see "This is a new description that shouldn't be saved."
# But teacher can overwrite existing presets created by them.
But I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "New saved preset"
And I set the field "Description" to "This is a new description that will be overwritten."
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "A preset with this name already exists."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should be visible
And I click on "Replace existing preset with this name and overwrite its contents" "checkbox" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "Preset saved."
And I follow "Presets"
And I should see "New saved preset"
And I should see "This is a new description that will be overwritten." in the "New saved preset" "table_row"
And I should not see "My funny description goes here." in the "New saved preset" "table_row"
@javascript
Scenario: Teachers can edit presets
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
# Plugin presets can't be edited.
Then I should not see "Actions" in the "Image gallery" "table_row"
# Teachers can't edit the presets they haven't created.
And I should not see "Actions" in the "Saved preset 1" "table_row"
# Teachers should be able to edit their saved presets.
And I open the action menu in "Saved preset by teacher1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Name" to "RENAMED preset by teacher1"
And I set the field "Description" to "My funny description goes here."
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see "Preset saved."
And I should see "RENAMED preset by teacher1"
And I should see "My funny description goes here." in the "RENAMED preset by teacher1" "table_row"
And I should not see "Saved preset by teacher1"
And I should not see "This preset has also a description"
@javascript
Scenario: Nothing happens when teachers edit a preset and do not change anything
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
And I open the action menu in "Saved preset by teacher1" "table_row"
And I choose "Edit" in the open action menu
And I click on "Save" "button" in the "Edit preset" "dialogue"
Then I should not see "Preset saved."
And I should see "Saved preset by teacher1"
@javascript
Scenario: Teachers can edit presets and overwrite them if they are the authors
Given the following "mod_data > preset" exists:
| database | data1 |
| name | Another preset created by teacher1 |
| description | This description will be overwritten |
| user | teacher1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
And I open the action menu in "Saved preset by teacher1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Name" to "Another preset created by teacher1"
And I click on "Save" "button" in the "Edit preset" "dialogue"
Then I should see "A preset with this name already exists."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should be visible
# If the checkbox is not selected, the preset shoudn't be saved.
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see "A preset with this name already exists."
# But when I select the overwrite checkbox, the preset should be overwritten.
But I click on "Replace existing preset with this name and overwrite its contents" "checkbox" in the "Edit preset" "dialogue"
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see "Preset saved."
And I should see "Another preset created by teacher1"
And I should see "This preset has also a description" in the "Another preset created by teacher1" "table_row"
And I should not see "Saved preset by teacher1"
And I should not see "This description will be overwritten"
@javascript
Scenario: Teachers cannot overwrite presets if they are not the authors
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
And I open the action menu in "Saved preset by teacher1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Name" to "Saved preset 1"
And I click on "Save" "button" in the "Edit preset" "dialogue"
Then I should see " A preset with this name already exists. Choose a different name."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should not be visible
# If the teacher clicks again the Save button, the preset shoudn't be saved.
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see " A preset with this name already exists. Choose a different name."
# But if they set a different name (which doesn't exist), the preset should be saved.
And I set the field "Name" to "New saved preset"
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see "Preset saved."
And I should see "New saved preset"
And I should see "This preset has also a description" in the "New saved preset" "table_row"
And I should not see "Saved preset by teacher1"
@javascript
Scenario: Admins can overwrite presets even if they are not the authors
Given I am on the "Mountain landscapes" "data activity" page logged in as admin
When I follow "Presets"
And I open the action menu in "Saved preset by teacher1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Name" to "Saved preset 1"
And I click on "Save" "button" in the "Edit preset" "dialogue"
Then I should see " A preset with this name already exists."
And "Replace existing preset with this name and overwrite its contents" "checkbox" should be visible
# But when admin selects the overwrite checkbox, the preset should be overwritten.
But I click on "Replace existing preset with this name and overwrite its contents" "checkbox" in the "Edit preset" "dialogue"
And I click on "Save" "button" in the "Edit preset" "dialogue"
And I should see "Preset saved."
And I should see "Saved preset 1"
And I should see "This preset has also a description" in the "Saved preset 1" "table_row"
And I should not see "Saved preset by teacher1"
And I should not see "The preset1 has description"
@javascript
Scenario: Teachers can delete their own presets
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And the following "mod_data > presets" exist:
| database | name | description | user |
| data1 | Saved preset by teacher1 | My funny description goes here. | teacher1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
And I should see "Image gallery"
And I should see "Saved preset 1"
And I should see "Saved preset by teacher1"
# Plugin presets can't be removed.
And I should not see "Actions" in the "Image gallery" "table_row"
# The teacher should not be able to delete presets saved by others.
And I should not see "Actions" in the "Saved preset 1" "table_row"
# The teacher should be able to delete their own preset.
And I open the action menu in "Saved preset by teacher" "table_row"
And I follow "Delete"
And I click on "Delete" "button" in the "Delete preset Saved preset by teacher1?" "dialogue"
And I should see "Preset deleted"
And I should not see "Saved preset by teacher1"
@javascript
Scenario: Teachers can preview a saved preset from the notification
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "New saved preset"
And I set the field "Description" to "My funny description goes here."
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
And I should see "Preset saved"
When I click on "Preview preset" "link"
Then I should see "Preview"
And I should see "New saved preset"
And I should see "Test field name"
And I should see "This is a short text"
Then "Use this preset" "button" should exist
Scenario: Teachers can export any saved preset
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
When I follow "Presets"
# Plugin presets can't be exported.
And I should not see "Actions" in the "Image gallery" "table_row"
# The teacher should be able to export any saved preset.
And I open the action menu in "Saved preset by teacher1" "table_row"
Then I should see "Export"
And following "Export" in the "Saved preset by teacher1" "table_row" should download a file that:
| Contains file in zip | preset.xml |
And I open the action menu in "Saved preset 1" "table_row"
And I should see "Export"
And following "Export" in the "Saved preset 1" "table_row" should download a file that:
| Contains file in zip | preset.xml |
@javascript @_file_upload
Scenario Outline: Admins and Teachers can load a preset from a file
Given I am on the "Mountain landscapes" "data activity" page logged in as <user>
When I follow "Presets"
Then I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
Then I click on "Import preset and apply" "button" in the ".modal-dialog" "css_element"
Then I should see "Preset applied"
# I am on the field page.
And I should see "Manage fields"
Then I should see "Preset applied"
Examples:
| user |
| admin |
| teacher1 |
@javascript
Scenario Outline: Teachers can use "Use this preset" actions menu next to each preset.
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I open the action menu in "<Preset Name>" "table_row"
When I click on "Use this preset" "link" in the "<Preset Name>" "table_row"
Then I should see "Preset applied"
Examples:
| Preset Name |
| Image gallery |
| Saved preset 1 (Admin User) |
| Saved preset by teacher1 (Teacher 1) |
@javascript
Scenario Outline: Teachers can use "Preview" actions menu next to each preset.
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I open the action menu in "<Preset Name>" "table_row"
When I click on "Preview" "link" in the "<Preset Name>" "table_row"
Then I should see "Preview of <Preset preview name>"
Examples:
| Preset Name | Preset preview name |
| Image gallery | Image gallery |
| Saved preset 1 (Admin User) | Saved preset 1 |
| Saved preset by teacher1 (Teacher 1) | Saved preset by teacher1 |
@@ -0,0 +1,162 @@
@mod @mod_data
Feature: Users can use mod_data without editing the templates
In order to use the database module
As a teacher
I need to manage fields and entries using always the default templates.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
| data1 | text | field2 | Test field 2 description |
And the following "mod_data > entries" exist:
| database | field1 | field2 |
| data1 | Student entry 1 | Some content 1 |
| data1 | Student entry 2 | Some content 2 |
And I am on the "Test database name" "data activity" page logged in as teacher1
@javascript
Scenario: The default view templates should be updated when a field is added.
Given I navigate to "Database" in current page administration
And I should see "field1"
And I should see "field2"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should see "Student entry 2"
And I should see "Some content 2"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "field1"
And I should see "field2"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should not see "Student entry 2"
And I should not see "Some content 2"
When the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field3 | Test field 3 description |
Then I navigate to "Database" in current page administration
And I should see "field1"
And I should see "field2"
And I should see "field3"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should see "Student entry 2"
And I should see "Some content 2"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "field1"
And I should see "field2"
And I should see "field3"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should not see "Student entry 2"
And I should not see "Some content 2"
Scenario: The default add templates should be updated when a field is added.
Given I navigate to "Database" in current page administration
And I click on "Add entry" "button"
And I should see "field1"
And I should see "field2"
When the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field3 | Test field 3 description |
Then I navigate to "Database" in current page administration
And I click on "Add entry" "button"
And I should see "field1"
And I should see "field2"
And I should see "field3"
@javascript
Scenario: The default view templates should be updated when a field is deleted.
Given I navigate to "Database" in current page administration
And I should see "field1"
And I should see "field2"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should see "Student entry 2"
And I should see "Some content 2"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "field1"
And I should see "field2"
And I should see "Student entry 1"
And I should see "Some content 1"
And I should not see "Student entry 2"
And I should not see "Some content 2"
When I navigate to "Fields" in current page administration
And I click on "Actions" "button" in the "field2" "table_row"
And I choose "Delete" in the open action menu
And I click on "Continue" "button"
Then I navigate to "Database" in current page administration
And I should see "field1"
And I should not see "field2"
And I should see "Student entry 1"
And I should not see "Some content 1"
And I should see "Student entry 2"
And I should not see "Some content 2"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "field1"
And I should not see "field2"
And I should see "Student entry 1"
And I should not see "Some content 1"
And I should not see "Student entry 2"
And I should not see "Some content 2"
Scenario: The default add templates should be updated when a field is deleted.
Given I navigate to "Database" in current page administration
And I click on "Add entry" "button"
And I should see "field1"
And I should see "field2"
When I navigate to "Fields" in current page administration
And I click on "Delete" "link" in the "field2" "table_row"
And I click on "Continue" "button"
Then I navigate to "Database" in current page administration
And I click on "Add entry" "button"
And I should see "field1"
And I should not see "field2"
@javascript
Scenario: The dynamic default templates can be reset after a manual edition.
Given I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
And I set the following fields to these values:
| Header | New header! |
| Repeated entry | This is the template content |
| Footer | New footer! |
And I click on "Save" "button" in the "sticky-footer" "region"
And I navigate to "Database" in current page administration
And I should see "New header!"
And I should see "This is the template content"
And I should see "New footer!"
And I should not see "Student entry 1"
And I should not see "Some content 1"
When I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
And I click on "Actions" "button"
And I choose "Reset current template" in the open action menu
And I click on "Reset" "button" in the "Reset template?" "dialogue"
And I should see "Template reset"
And I navigate to "Database" in current page administration
And I should not see "New header!"
And I should not see "This is the template content"
And I should not see "New footer!"
And I should see "Student entry 1"
And I should see "Some content 1"
Then the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field3 | Test field 3 description |
And I navigate to "Database" in current page administration
And I click on "Add entry" "button"
And I should see "field1"
And I should see "field2"
And I should see "field3"
+215
View File
@@ -0,0 +1,215 @@
@mod @mod_data
Feature: Users can edit the database templates
In order to use custom templates for entries
As a teacher
I need to edit the templates html
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
| data1 | text | field2 | Test field 2 description |
And the following "mod_data > entries" exist:
| database | field1 | field2 |
| data1 | Student entry 1 | Some content 1 |
And I am on the "Test database name" "data activity" page logged in as teacher1
And I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
@javascript
Scenario: Edit list template
Given I set the following fields to these values:
| Header | New header! |
| Repeated entry | [[field1]] and [[field2]]! |
| Footer | New footer! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
Then I should see "New header!"
And I should see "Student entry 1 and Some content 1!"
And I should see "New footer!"
@javascript
Scenario: Edit single template
Given I set the field "Templates tertiary navigation" to "Single view template"
And I set the following fields to these values:
| Single view template | [[field1]] and [[field2]] details! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
And I set the field "View mode tertiary navigation" to "Single view"
Then I should see "Student entry 1 and Some content 1 details!"
@javascript
Scenario: Edit add entry template
Given I set the field "Templates tertiary navigation" to "Add entry template"
And I set the following fields to these values:
| Add entry template | [[field1]] [[field2]] Form extra! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
And I click on "Add entry" "button"
Then I should see "Form extra!"
@javascript
Scenario: Edit advanced search template
Given I set the field "Templates tertiary navigation" to "Advanced search template"
And I set the following fields to these values:
| Advanced search template | New advanced search template! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
And I click on "Advanced search" "checkbox"
Then I should see "New advanced search template!"
@javascript
Scenario: Edit without the wysiwyg editor
Given I click on "Enable code editor" "checkbox"
And I set the following fields to these values:
| Repeated entry | <span class="d-none">Nope</span>Yep! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
Then I should not see "Nope"
And I should see "Yep!"
@javascript
Scenario: Edit CSS teamplate
Given I click on "Enable code editor" "checkbox"
And I set the following fields to these values:
| Repeated entry | <span class="hideme">Nope</span>Yep! |
And I click on "Save" "button" in the "sticky-footer" "region"
And I set the field "Templates tertiary navigation" to "Custom CSS"
And I set the following fields to these values:
| Custom CSS | .hideme {display: none;} |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
Then I should not see "Nope"
And I should see "Yep!"
@javascript
Scenario: Edit Custom JavaScript
Given I click on "Enable code editor" "checkbox"
And I set the following fields to these values:
| Repeated entry | <span id="hideme">Nope</span>Yep! |
And I click on "Save" "button" in the "sticky-footer" "region"
And I set the field "Templates tertiary navigation" to "Custom JavaScript"
And I set the following fields to these values:
| Custom JavaScript | window.onload = () => document.querySelector('#hideme').style.display = 'none'; |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
Then I should not see "Nope"
And I should see "Yep!"
@javascript
Scenario: Reset database activity template
Given I set the following fields to these values:
| Header | New header! |
| Repeated entry | This is the template content |
| Footer | New footer! |
And I click on "Save" "button" in the "sticky-footer" "region"
And I navigate to "Database" in current page administration
And I should see "New header!"
And I should see "This is the template content"
And I should see "New footer!"
And I should not see "Student entry 1"
And I should not see "Some content 1"
When I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
And I click on "Actions" "button"
And I choose "Reset current template" in the open action menu
And I should see "This will permanently remove the List view template for your current preset."
And I click on "Reset" "button" in the "Reset template?" "dialogue"
Then I should see "Template reset"
And I navigate to "Database" in current page administration
And I should not see "New header!"
And I should not see "This is the template content"
And I should not see "New footer!"
And I should see "Student entry 1"
And I should see "Some content 1"
@javascript
Scenario: Reset all database templates using the action menu
Given the following "mod_data > templates" exist:
| database | name | content |
| data1 | singletemplate | Initial single |
| data1 | listtemplate | Initial list |
| data1 | addtemplate | Initial add |
| data1 | asearchtemplate | Initial search |
And I navigate to "Database" in current page administration
And I should see "Initial list"
And I should not see "Student entry 1"
And I should not see "Some content 1"
And I click on "Advanced search" "checkbox"
And I should see "Initial search"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "Initial single"
And I should not see "Student entry 1"
And I should not see "Some content 1"
And I click on "Add entry" "button"
And I should see "Initial add"
When I navigate to "Templates" in current page administration
And I click on "Actions" "button"
And I choose "Reset all templates" in the open action menu
And I should see "You're about to remove all templates for your current preset."
And I click on "Reset" "button" in the "Reset all templates?" "dialogue"
Then I should see "All templates reset"
And I navigate to "Database" in current page administration
And I should not see "Initial list"
And I should see "Student entry 1"
And I should see "Some content 1"
And I click on "Advanced search" "checkbox"
And I should not see "Initial search"
And I set the field "View mode tertiary navigation" to "Single view"
And I should not see "Initial single"
And I should see "Student entry 1"
And I should see "Some content 1"
And I click on "Add entry" "button"
And I should not see "Initial add"
@javascript
Scenario: Reset all database templates using the reset template button
Given the following "mod_data > templates" exist:
| database | name | content |
| data1 | singletemplate | Initial single |
| data1 | listtemplate | Initial list |
| data1 | addtemplate | Initial add |
| data1 | asearchtemplate | Initial search |
And I navigate to "Database" in current page administration
And I should see "Initial list"
And I should not see "Student entry 1"
And I should not see "Some content 1"
And I click on "Advanced search" "checkbox"
And I should see "Initial search"
And I set the field "View mode tertiary navigation" to "Single view"
And I should see "Initial single"
And I should not see "Student entry 1"
And I should not see "Some content 1"
And I click on "Add entry" "button"
And I should see "Initial add"
When I navigate to "Templates" in current page administration
And I click on "Actions" "button"
And I choose "Reset current template" in the open action menu
And I should see "This will permanently remove the Add entry template for your current preset."
And I click on "Reset all templates" "checkbox"
And I click on "Reset" "button" in the "Reset template?" "dialogue"
Then I should see "All templates reset"
And I navigate to "Database" in current page administration
And I should not see "Initial list"
And I should see "Student entry 1"
And I should see "Some content 1"
And I click on "Advanced search" "checkbox"
And I should not see "Initial search"
And I set the field "View mode tertiary navigation" to "Single view"
And I should not see "Initial single"
And I should see "Student entry 1"
And I should see "Some content 1"
And I click on "Add entry" "button"
And I should not see "Initial add"
@@ -0,0 +1,50 @@
@mod @mod_data
Feature: User can see the entry approval status on the single view and list view
in the default template.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber | approval |
| data | Test database name | Database intro | C1 | data1 | 1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
| data1 | text | field2 | Test field 2 description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | field1 | field2 |
| data1 | student1 | Student entry 1 | Some student content 1 |
| data1 | teacher1 | Teacher entry 1 | Some teacher content 1 |
@javascript
Scenario Outline: The approval status is displayed in the single view and list view next to the action menu
# List view.
Given I am on the "Test database name" "data activity" page logged in as <user>
Then I should see "Pending approval" in the "region-main" "region"
# Single view.
And I select "Single view" from the "jump" singleselect
And I should see "Pending approval" in the "region-main" "region"
And I click on "2" "link" in the ".pagination" "css_element"
And I should not see "Pending approval" in the "region-main" "region"
And I should not see "Approved" in the "region-main" "region"
Examples:
| user |
| student1 |
| teacher1 |
+110
View File
@@ -0,0 +1,110 @@
@mod @mod_data
Feature: Group data activity
In order to create a database with my group
As a student
I need to add and view entries for the groups I am a member of
Background:
And the following "courses" exist:
| fullname | shortname |
| Test Course 1 | C1 |
And the following "groups" exist:
| name | course | idnumber | participation |
| Group 1 | C1 | G1 | 1 |
| Group 2 | C1 | G2 | 1 |
| Group 3 | C1 | G3 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | TeacherG1 | 1 | teacher1@example.com |
| user1 | User1G1 | 1 | user1@example.com |
| user2 | User2G2 | 2 | user2@example.com |
| user3 | User3None | 3 | user3@example.com |
| user4 | User4NPgroup | 4 | user4@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| user1 | C1 | student |
| user2 | C1 | student |
| user3 | C1 | student |
| user4 | C1 | student |
And the following "group members" exist:
| user | group |
| teacher1 | G1 |
| user1 | G1 |
| user2 | G2 |
| user4 | G3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | groupmode |
| data | Separate data | Data with separate groups | C1 | data1 | 1 |
| data | Visible data | Data with visible groups | C1 | data2 | 2 |
And the following "mod_data > fields" exist:
| database | type | name |
| data1 | shorttext | separatetext |
| data2 | shorttext | visibletext |
And the following "mod_data > entries" exist:
| database | user | group | separatetext |
| data1 | user1 | G1 | I am user 1 |
| data1 | user2 | G2 | I am user 2 |
And the following "mod_data > entries" exist:
| database | user | separatetext |
| data1 | user3 | I am user 3 |
And the following "mod_data > entries" exist:
| database | user | group | visibletext |
| data2 | user1 | G1 | I am user 1 |
| data2 | user2 | G2 | I am user 2 |
And the following "mod_data > entries" exist:
| database | user | visibletext |
| data2 | user3 | I am user 3 |
| data2 | user4 | I am user 4 |
Scenario Outline: Users should see their own participation groups in "separate groups" mode, and all
participation groups in "visible groups" mode.
Given I am on the "<data>" "data activity" page logged in as "<user>"
Then I <all> "All participants"
And I <G1> "Group 1"
And I <user1> "I am user 1"
And I <G2> "Group 2"
And I <user2> "I am user 2"
# All users should see entries with no group.
And I should see "I am user 3"
# No-one should see non-participation groups.
And I should not see "Group 3"
Examples:
| data | user | all | G1 | G2 | user1 | user2 |
| data1 | teacher1 | should see | should see | should see | should see | should see |
| data1 | user1 | should not see | should see | should not see | should see | should not see |
| data1 | user2 | should not see | should not see | should see | should not see | should see |
| data1 | user3 | should see | should not see | should not see | should not see | should not see |
| data1 | user4 | should see | should not see | should not see | should not see | should not see |
| data2 | teacher1 | should see | should see | should see | should see | should see |
| data2 | user1 | should see | should see | should see | should see | should not see |
| data2 | user2 | should see | should see | should see | should not see | should see |
| data2 | user3 | should see | should see | should see | should see | should not see |
| data2 | user4 | should see | should see | should see | should see | should not see |
Scenario Outline: When viewing a database in visible groups mode,
a user should only have the "Add entry" button for their own participation groups
Given I am on the "data2" "data activity" page logged in as "<user>"
And I select "<mygroup>" from the "group" singleselect
And I should see "Add entry"
When I select "<othergroup>" from the "group" singleselect
Then I should not see "Add entry"
Examples:
| user | mygroup | othergroup |
| user1 | Group 1 | Group 2 |
| user2 | Group 2 | Group 1 |
| user1 | All participants | Group 2 |
| user2 | All participants | Group 1 |
Scenario Outline: Users in no groups or non-participation groups should not be able to add entries
Given I am on the "<data>" "data activity" page logged in as "<user>"
Then I should not see "Add entry"
Examples:
| data | user |
| data1 | user3 |
| data1 | user4 |
| data2 | user3 |
| data2 | user4 |
+180
View File
@@ -0,0 +1,180 @@
@mod @mod_data @javascript @_file_upload
Feature: Users can import presets
In order to use presets
As a user
I need to import and apply presets from zip files
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Mountain landscapes | n | C1 | data1 |
Scenario: Teacher can import from preset page on an empty database
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
And I should see "Image" in the "image" "table_row"
Scenario: Teacher can import from preset page on a database with fields
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
Scenario: Teacher can import from preset page on a database with entries
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | field1 |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
And I should see "image"
And I should see "Create a new field" in the "image" "table_row"
Scenario: Teacher can import from field page on a database with entries
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | field1 |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should see "Field mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to field1"
And I click on "Continue" "button"
And I follow "Database"
And I should see "Student entry"
Scenario: Teacher can import from zero state page on an empty database
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I click on "Import a preset" "button"
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
And I should see "Image" in the "image" "table_row"
Scenario: Importing a preset could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Importing a preset could create map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
# Let's map a field that is not mapped by default
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Importing same preset twice doesn't show mapping dialogue
# Importing a preset on an empty database doesn't show the mapping dialogue, so we add a field for the database
# not to be empty.
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I should see "Field mappings"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I choose the "Import preset" item in the "Action" action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
And I click on "Import preset and apply" "button"
Then I should not see "Field mappings"
And I should see "Preset applied"
Scenario: Teacher can import from field page on a non-empty database and previous fields will be removed
Given the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I click on "Continue" "button"
Then I should see "Preset applied."
And I follow "Fields"
And I should see "image"
And I should see "title"
And I should not see "Test field name"
@@ -0,0 +1,83 @@
@mod @mod_data
Feature: Users can edit approved entries in database activities
In order to control whether approved database entries can be changed
As a teacher
I need to be able to enable or disable management of approved entries
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario: Students can manage their approved entries to a database
Given the following "activity" exists:
| activity | data |
| course | C1 |
| idnumber | Test database name |
| name | Test database name |
| approval | 1 |
| manageapproved | 1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| Test database name | text | Test field name | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| Test database name | singletemplate |
| Test database name | listtemplate |
| Test database name | addtemplate |
| Test database name | asearchtemplate |
| Test database name | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | Test field name |
| Test database name | student1 | Student entry |
# Approve the student's entry as a teacher.
And I am on the "Test database name" "data activity" page logged in as teacher1
And I open the action menu in ".defaulttemplate-listentry" "css_element"
And I choose "Approve" in the open action menu
And I log out
# Make sure the student can still edit their entry after it's approved.
When I am on the "Test database name" "data activity" page logged in as student1
Then I should see "Student entry"
And "Edit" "link" should exist
@javascript
Scenario: Students can not manage their approved entries to a database
# Create database activity and don't allow editing of approved entries.
Given the following "activity" exists:
| activity | data |
| course | C1 |
| idnumber | Test database name |
| name | Test database name |
| approval | 1 |
| manageapproved | 0 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| Test database name | text | Test field name | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| Test database name | singletemplate |
| Test database name | listtemplate |
| Test database name | addtemplate |
| Test database name | asearchtemplate |
| Test database name | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | Test field name |
| Test database name | student1 | Student entry |
# Approve the student's entry as a teacher.
And I am on the "Test database name" "data activity" page logged in as teacher1
And I open the action menu in ".defaulttemplate-listentry" "css_element"
And I choose "Approve" in the open action menu
And I log out
# Make sure the student isn't able to edit their entry after it's approved.
When I am on the "Test database name" "data activity" page logged in as student1
Then "Edit" "link" should not exist
And I should see "Student entry"
+165
View File
@@ -0,0 +1,165 @@
@mod @mod_data
Feature: Users can preview presets
In order to find the preset I am looking for
As a teacher
I need to preview the database activity presets
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "tags" exist:
| name | isstandard |
| Tag1 | 1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And I am on the "Test database name" "data activity" page logged in as teacher1
@javascript @_file_upload
Scenario: Preview a user preset as list view template in database
Given I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "Saved preset by teacher1"
And I set the field "Description" to "Behat test preset"
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
When I follow "Presets"
And I click on "Saved preset by teacher1" "link"
# Check list template preview fields.
Then I should see "Saved preset by teacher1"
And I should see "List header" in the "template-preview" "region"
And I should see "List footer" in the "template-preview" "region"
And I should see "List template content" in the "template-preview" "region"
And I should see "My text field" in the "template-preview" "region"
And I should see "This is a short text" in the "template-preview" "region"
And I should see "My multiple selection" in the "template-preview" "region"
And I should see "Multi 1" in the "template-preview" "region"
And I should see "My date field" in the "template-preview" "region"
And I should see "My checkbox field" in the "template-preview" "region"
And I should see "Check 2" in the "template-preview" "region"
And I should see "My geo field" in the "template-preview" "region"
And I should see "41.3912°N 2.1639°E" in the "template-preview" "region"
And I should see "My menu field" in the "template-preview" "region"
And I should see "Menu 2" in the "template-preview" "region"
And I should see "My number field" in the "template-preview" "region"
And I should see "1234" in the "template-preview" "region"
And I should see "My radio field" in the "template-preview" "region"
And I should see "Radio 2" in the "template-preview" "region"
And I should see "My text area field" in the "template-preview" "region"
And I should see "This is a text area" in the "template-preview" "region"
And I should see "My URL field" in the "template-preview" "region"
And I should see "https://example.com" in the "template-preview" "region"
And I should see "My file field" in the "template-preview" "region"
And "Comma-separated values" "icon" should exist in the "template-preview" "region"
And I should see "samplefile.csv" in the "template-preview" "region"
And I should see "My picture field" in the "template-preview" "region"
# Test CSS and JS templates.
And I should not see "This content should not be displayed" in the "template-preview" "region"
And I should not see "This text should change" in the "template-preview" "region"
And I should see "New value" in the "template-preview" "region"
@javascript @_file_upload
Scenario: Preview a user preset as single view template in database
Given I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "Saved preset by teacher1"
And I set the field "Description" to "Behat test preset"
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
When I follow "Presets"
And I click on "Saved preset by teacher1" "link"
And I set the field "Templates tertiary navigation" to "Single view template"
# Check single view template preview fields.
Then I should see "Saved preset by teacher1"
And I should see "Single template content" in the "template-preview" "region"
And I should see "My text field" in the "template-preview" "region"
And I should see "This is a short text" in the "template-preview" "region"
And I should see "My multiple selection" in the "template-preview" "region"
And I should see "Multi 2" in the "template-preview" "region"
And I should see "My date field" in the "template-preview" "region"
And I should see "My checkbox field" in the "template-preview" "region"
And I should see "Check 2" in the "template-preview" "region"
And I should see "My geo field" in the "template-preview" "region"
And I should see "41.3912°N 2.1639°E" in the "template-preview" "region"
And I should see "My menu field" in the "template-preview" "region"
And I should see "Menu 2" in the "template-preview" "region"
And I should see "My number field" in the "template-preview" "region"
And I should see "1234" in the "template-preview" "region"
And I should see "My radio field" in the "template-preview" "region"
And I should see "Radio 2" in the "template-preview" "region"
And I should see "My text area field" in the "template-preview" "region"
And I should see "This is a text area" in the "template-preview" "region"
And I should see "My URL field" in the "template-preview" "region"
And I should see "https://example.com" in the "template-preview" "region"
And I should see "My file field" in the "template-preview" "region"
And "Comma-separated values" "icon" should exist in the "template-preview" "region"
And I should see "samplefile.csv" in the "template-preview" "region"
And I should see "My picture field" in the "template-preview" "region"
# Test CSS and JS templates.
And I should not see "This content should not be displayed" in the "template-preview" "region"
And I should not see "This text should change" in the "template-preview" "region"
And I should see "New value" in the "template-preview" "region"
@javascript
Scenario: Preview a plugin preset in database
Given I follow "Presets"
When I click on "Journal" "link"
Then I should see "Journal"
And I should see "This is a short text"
And I should see "This is a text area"
And I select "Single view template" from the "Templates tertiary navigation" singleselect
And I should see "This is a short text"
And I should see "This is a text area"
And I should see "This is a short text" in the "template-preview" "region"
@javascript
Scenario: Use back button to return to the presets page in database
Given I follow "Presets"
And I click on "Image gallery" "link"
And I should see "Image gallery"
When I click on "Back" "button"
Then I should see "Choose a preset to use as a starting point."
@javascript
Scenario: Apply plugin preset from preview in database
Given I follow "Presets"
And I click on "Image gallery" "link"
When I click on "Use this preset" "button"
Then I should see "image"
And I should see "title"
@javascript @_file_upload
Scenario: Apply user preset from preview in database
Given I follow "Presets"
And I click on "Actions" "button"
And I choose "Import preset" in the open action menu
And I upload "mod/data/tests/fixtures/behat_preset.zip" file to "Preset file" filemanager
When I click on "Import preset and apply" "button"
And I follow "Templates"
And I click on "Actions" "button"
And I choose "Publish preset on this site" in the open action menu
And I set the field "Name" to "Saved preset by teacher1"
And I set the field "Description" to "Behat test preset"
And I click on "Save" "button" in the "Save all fields and templates and publish as preset on this site" "dialogue"
When I follow "Presets"
And I click on "Saved preset by teacher1" "link"
And I click on "Use this preset" "button"
Then I should see "Preset applied"
And I should see "My URL field"
@@ -0,0 +1,169 @@
@mod @mod_data
Feature: Users can be required to specify certain fields when adding entries to database activities
In order to constrain user input
As a teacher
I need to specify certain fields as required when I add entries to databases
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | n | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | required | description | param1 |
| data1 | text | Base Text input | 1 | Base Text input | |
| data1 | checkbox | Required Checkbox | 1 | Base Text input | Required Checkbox Option 1 |
| data1 | checkbox | Required Two-Option Checkbox | 1 | Required Two-Option Checkbox | RTOC Option 1\nRTOC Option 2 |
| data1 | latlong | Required Coordinates | 1 | Required Coordinates | |
| data1 | menu | Required Menu | 1 | Required Menu | Option 1 |
| data1 | number | Required Number | 1 | Required Number | |
| data1 | radiobutton | Required Radio | 1 | Required Radio | Required Radio Option 1 |
| data1 | text | Required Text input | 1 | Required Text input | |
| data1 | textarea | Required Text area | 1 | Required Text area | |
| data1 | url | Required URL | 1 | Required URL | |
| data1 | multimenu | Required Multimenu | 1 | Required Multimenu | Option 1 |
| data1 | multimenu | Required Two-Option Multimenu | 1 | Required Two-Option Multimenu | Option 1\nOption 2 |
| data1 | checkbox | Not required Checkbox | 0 | Not required Checkbox | Not required Checkbox Option 1 |
| data1 | latlong | Not required Coordinates | 0 | Not required Coordinates | |
| data1 | menu | Not required Menu | 0 | Not required Menu | Option 1 |
| data1 | number | Not required Number | 0 | Not required Number | |
| data1 | radiobutton | Not required Radio | 0 | Not required Radio | Not required Radio Option 1 |
| data1 | text | Not required Text input | 0 | Not required Text input | |
| data1 | textarea | Not required Text area | 0 | Not required Text area | |
| data1 | url | Not required URL | 0 | Not required URL | |
| data1 | multimenu | Not required Multimenu | 0 | Not required Multimenu | Option 1 |
Scenario: Students receive errors for empty required fields but not for optional fields
Given I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
And I set the field "Base Text input" to "Some input to allow us to submit the otherwise empty form"
When I press "Save"
# Then ".alert" "css_element" should exist in the "//div[contains(@id,'defaulttemplate-addentry']//div[position()=1]]" "xpath_element"
Then ".alert" "css_element" should appear after "Checkbox" "text"
And ".alert" "css_element" should appear before "Required Checkbox" "text"
And ".alert" "css_element" should appear after "Two-Option Checkbox" "text"
And ".alert" "css_element" should appear before "Required Two-Option Checkbox" "text"
And ".alert" "css_element" should appear after "Coordinates" "text"
And ".alert" "css_element" should appear before "Required Coordinates" "text"
And ".alert" "css_element" should appear after "Menu" "text"
And ".alert" "css_element" should appear before "Required Menu" "text"
And ".alert" "css_element" should appear after "Radio" "text"
And ".alert" "css_element" should appear before "Required Radio" "text"
And ".alert" "css_element" should appear after "Text input" "text"
And ".alert" "css_element" should appear before "Required Text input" "text"
And ".alert" "css_element" should appear after "Text area" "text"
And ".alert" "css_element" should appear before "Required Text area" "text"
And ".alert" "css_element" should appear after "URL" "text"
And ".alert" "css_element" should appear before "Required URL" "text"
And ".alert" "css_element" should appear after "Multimenu" "text"
And ".alert" "css_element" should appear before "Required Multimenu" "text"
And ".alert" "css_element" should appear after "Two-Option Multimenu" "text"
And ".alert" "css_element" should appear before "Required Two-Option Multimenu" "text"
And I am on "Course 1" course homepage
And I follow "Test database name"
And I should see "No entries yet"
Scenario: Students receive no error for filled in required fields
Given I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
And I set the following fields to these values:
| Base Text input | Some input to allow us to submit the otherwise empty form |
| Required Checkbox Option 1 | 1 |
| RTOC Option 1 | 1 |
| Latitude | 0 |
| Longitude | 0 |
| Required Menu | 1 |
| Required Number | 1 |
| Required Radio Option 1 | 1 |
| Required Text input | New entry text |
| Required Text area | More text |
| Required URL | http://example.com/ |
| Required Multimenu | 1 |
| Required Two-Option Multimenu | 1 |
When I press "Save"
And I select "List view" from the "jump" singleselect
Then I should not see "No entries in database"
And I should see "New entry text"
Scenario: Fields refill with data after having an error
Given I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
And I set the following fields to these values:
| RTOC Option 1 | 1 |
| Latitude | 0 |
| Longitude | 0 |
| Required Menu | 1 |
| Required Number | 1 |
| Required Radio Option 1 | 1 |
| Required Text input | New entry text |
| Required Text area | More text |
| Required URL | http://example.com/ |
| Required Multimenu | 1 |
| Required Two-Option Multimenu | 1 |
When I press "Save"
Then the following fields match these values:
| Base Text input | |
| Latitude | 0 |
| Longitude | 0 |
| Required Menu | Option 1 |
| Required Number | 1 |
| Required Radio Option 1 | 1 |
| Required Text input | New entry text |
| Required Text area | More text |
| Required URL | http://example.com/ |
| Required Multimenu | Option 1 |
| Required Two-Option Multimenu | Option 1 |
@javascript
Scenario: A student fills in Latitude but not Longitude will see an error
Given I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
And I set the following fields to these values:
| Base Text input | Some input to allow us to submit the otherwise empty form |
| Required Checkbox Option 1 | 1 |
| RTOC Option 1 | 1 |
| Latitude | 24 |
| Required Menu | 1 |
| Required Number | 1 |
| Required Radio Option 1 | 1 |
| Required Text input | New entry text |
| Required Text area | More text |
| Required URL | http://example.com/ |
| Required Multimenu | 1 |
| Required Two-Option Multimenu | 1 |
| Latitude | 20 |
When I press "Save"
Then I should see "Both latitude and longitude are required."
Scenario: A student filling in number and text fields with zero will not see an error.
Scenario: A student fills in Latitude but not Longitude will see an error
Given I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
And I set the following fields to these values:
| Base Text input | Some input to allow us to submit the otherwise empty form |
| Required Checkbox Option 1 | 1 |
| RTOC Option 1 | 1 |
| Latitude | 0 |
| Longitude | 0 |
| Required Menu | 1 |
| Required Number | 0 |
| Required Radio Option 1 | 1 |
| Required Text input | 0 |
| Required Text area | 0 |
| Required URL | http://example.com/ |
| Required Multimenu | 1 |
| Required Two-Option Multimenu | 1 |
When I press "Save"
And I select "List view" from the "jump" singleselect
Then I should not see "No entries in database"
And I should see "Some input to allow us to submit the otherwise empty form"
@@ -0,0 +1,129 @@
@mod @mod_data
Feature: Users can navigate through the database activity using the tertiary navigation
In order to use the database module
As a user
I need to navigate using the tertiary navigation.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
| data | Database without fields | Database intro | C1 | data2 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | field1 | Test field description |
| data1 | text | field2 | Test field 2 description |
And the following "mod_data > entries" exist:
| database | user | field1 | field2 |
| data1 | teacher1 | Teacher entry 1 | Some content 1 |
| data1 | teacher1 | Teacher entry 2 | Some content 2 |
And the following "mod_data > presets" exist:
| database | name | description | user |
| data1 | Saved preset by teacher1 | This preset has also a description | teacher1 |
And I log in as "admin"
And the following config values are set as admin:
| enableportfolios | 1 |
And I navigate to "Plugins > Portfolios > Manage portfolios" in site administration
And I set portfolio instance "File download" to "Enabled and visible"
And I click on "Save" "button"
And I log out
And I am on the "Test database name" "data activity" page logged in as teacher1
@javascript
Scenario: The tertiary navigation in the Database page.
Given I navigate to "Database" in current page administration
# Teacher: List view.
And I should not see "List view" in the "data-listview-content" "region"
When I click on "Actions" "button"
Then I should see "Import entries" in the ".entriesactions" "css_element"
And I should see "Export entries" in the ".entriesactions" "css_element"
And I should see "Export to portfolio" in the ".entriesactions" "css_element"
And I press the escape key
# Teacher: Single view.
And I set the field "View mode tertiary navigation" to "Single view"
And I should not see "Single view" in the "data-singleview-content" "region"
And I click on "Actions" "button"
And I should see "Import entries" in the ".entriesactions" "css_element"
And I should see "Export entries" in the ".entriesactions" "css_element"
And I should not see "Export to portfolio" in the ".entriesactions" "css_element"
# Teacher: Database without fields.
And I am on the "Database without fields" "data activity" page
And I should not see "Actions"
# Student without entries: List view.
And I am on the "Test database name" "data activity" page logged in as student1
And I should not see "Actions"
# Student without entries: Single view.
And I set the field "View mode tertiary navigation" to "Single view"
And I should not see "Actions"
# Student with entries: Single view.
But the following "mod_data > entries" exist:
| database | user | field1 | field2 |
| data1 | student1 | Student entry 3 | Some content 3 |
And I should not see "Actions"
# Student with entries: List view.
And I set the field "View mode tertiary navigation" to "List view"
And I click on "Actions" "button"
And I should not see "Import entries" in the ".entriesactions" "css_element"
And I should not see "Export entries" in the ".entriesactions" "css_element"
And I should see "Export to portfolio" in the ".entriesactions" "css_element"
@javascript
Scenario: The tertiary navigation in the Presets page.
Given I navigate to "Presets" in current page administration
When I click on "Actions" "button"
Then I should see "Import preset" in the ".presetsactions" "css_element"
And I should see "Export preset" in the ".presetsactions" "css_element"
And I should see "Publish preset on this site" in the ".presetsactions" "css_element"
And I press the escape key
# Database without fields.
But I am on the "Database without fields" "data activity" page
And I navigate to "Presets" in current page administration
And I click on "Actions" "button"
And I should see "Import preset" in the ".presetsactions" "css_element"
And I should not see "Export preset" in the ".presetsactions" "css_element"
And I should not see "Publish preset on this site" in the ".presetsactions" "css_element"
Scenario: The tertiary navigation in the Presets preview page.
Given I navigate to "Presets" in current page administration
When I follow "Saved preset by teacher1"
Then I should see "Preview of Saved preset by teacher1"
And "Use this preset" "button" should exist
# Single view
And I set the field "Templates tertiary navigation" to "Single view template"
And I should see "Preview of Saved preset by teacher1"
And "Use this preset" "button" should exist
@javascript
Scenario: The tertiary navigation in the Fields page.
Given I navigate to "Fields" in current page administration
When I open the action menu in "field1" "table_row"
Then I should see "Edit"
And I should see "Delete"
And I press the escape key
And I should not see "Actions"
@javascript
Scenario: The tertiary navigation in the Templates page.
Given I navigate to "Templates" in current page administration
When I click on "Actions" "button"
Then I should see "Export preset" in the ".presetsactions" "css_element"
And I should see "Publish preset on this site" in the ".presetsactions" "css_element"
And I press the escape key
And I should see "Add entry template"
# List template.
And I set the field "Templates tertiary navigation" to "List view template"
And I should not see "Add entry template"
And I should see "Header"
And I should see "Repeated entry"
And I should see "Footer"
+254
View File
@@ -0,0 +1,254 @@
@mod @mod_data @javascript
Feature: Users can use predefined presets
In order to use presets
As a user
I need to select an existing preset
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Mountain landscapes | introduction... | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
Scenario: Using a preset on a non empty database could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
And I should see "Field mappings"
When I click on "Continue" "button"
And I should see "Preset applied"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Using a preset on a non-empty database could show the option to map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
# Let's map a field that is not mapped by default
And I should see "Apply preset Image gallery"
And I should see "Fields to be created: image, title, description"
And I should see "Existing fields to be deleted: Test field name, oldtitle"
When I click on "Map fields" "button"
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Teacher can use a preset from presets page on a database with existing entries
# Creating an entry to test use a preset feature with databases with entries.
Given the following "mod_data > entries" exist:
| database | Test field name |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And the "Use this preset" "button" should be enabled
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
Then I should see "Field mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Fields"
And I should see "title"
And I follow "Database"
And I should see "Student entry 1"
Scenario: Using same preset twice doesn't show mapping dialogue and applies the preset directly
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
When I click on "Use this preset" "button"
And I should see "Apply preset"
And I click on "Map fields" "button"
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
Then I should not see "Apply preset Image gallery"
And I should see "Preset applied"
Scenario: Using a preset from preset preview page on a non empty database could create new fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | title |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "title"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
When I click on "Apply preset" "button"
And I should see "Preset applied"
And I follow "Fields"
Then I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Using a preset from preset preview page on a non-empty database could show the option to map fields
Given the following "mod_data > fields" exist:
| database | type | name |
| data1 | text | oldtitle |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "oldtitle"
And I should not see "Description"
And I should not see "image"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
# Let's map a field that is not mapped by default
And I should see "Apply preset Image gallery"
And I should see "Fields to be created: image, title, description"
And I should see "Existing fields to be deleted: Test field name, oldtitle"
When I click on "Map fields" "button"
And I should see "Create a new field" in the "oldtitle" "table_row"
And I set the field "id_title" to "Map to oldtitle"
And I click on "Continue" "button"
And I should see "Preset applied"
Then I should not see "oldtitle"
And I should see "title"
And I should see "description" in the "description" "table_row"
And I should see "image" in the "image" "table_row"
Scenario: Teacher can use a preset from preset preview page on a database with existing entries
# Creating an entry to test use a preset feature with databases with entries.
Given the following "mod_data > entries" exist:
| database | Test field name |
| data1 | Student entry 1 |
And I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
And the "Use this preset" "button" should be enabled
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
Then I should see "Field mappings"
And I should see "title"
And I should see "Create a new field" in the "title" "table_row"
# We map existing field to keep the entry data
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Fields"
And I should see "title"
And I follow "Database"
And I should see "Student entry 1"
Scenario: Using same preset twice from preset preview page doesn't show mapping dialogue and applies the preset
directly
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
And I click on "Map fields" "button"
And I should see "Field mappings"
And I set the field "id_title" to "Map to Test field name"
And I click on "Continue" "button"
And I should see "Preset applied"
And I follow "Presets"
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
Then I should not see "Field mappings"
And I should see "Preset applied"
Scenario: Apply preset dialogue should show helpful information to the user
Given the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Sea landscapes | introduction... | C1 | data2 |
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | text | title |
And I am on the "Sea landscapes" "data activity" page logged in as teacher1
And I follow "Presets"
And I click on "Image gallery" "link"
When I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be created only.
Then I should see "Fields to be created: image, description"
And I should not see "If fields to be deleted are of the same type as fields to be created"
And I should not see "If fields to be deleted are of the same type as new fields in the preset"
And I click on "Cancel" "button" in the "Apply preset Image gallery?" "dialogue"
And I follow "Presets"
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | number | number |
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be created and fields to be deleted.
And I should see "Fields to be created: image, description"
And I should see "Existing fields to be deleted: number"
And I should see "If fields to be deleted are of the same type as fields to be created"
And I should not see "If fields to be deleted are of the same type as new fields in the preset"
And I click on "Cancel" "button" in the "Apply preset Image gallery?" "dialogue"
And I follow "Presets"
And the following "mod_data > fields" exist:
| database | type | name |
| data2 | textarea | description |
| data2 | picture | image |
And I click on "Image gallery" "link"
And I click on "Use this preset" "button"
And I should see "Apply preset Image gallery"
# Fields to be deleted only.
And I should see "Existing fields to be deleted: number"
And I should not see "If fields to be deleted are of the same type as fields to be created"
And I should see "If fields to be deleted are of the same type as new fields in the preset"
Scenario: Teacher can use a preset on a non-empty database and previous fields will be removed
Given I am on the "Mountain landscapes" "data activity" page logged in as teacher1
And I follow "Fields"
And I should see "Test field name"
And I follow "Presets"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And I click on "Use this preset" "button"
And I should see "Existing fields to be deleted: Test field name"
When I click on "Apply preset" "button"
Then I should see "Preset applied."
And I should see "image"
And I should see "title"
And I should not see "Test field name"
+200
View File
@@ -0,0 +1,200 @@
@mod @mod_data
Feature: Users can view and search database entries
In order to find the database entries that I am looking for
As a user
I need to list and search the database entries
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Bob | 1 | student1@example.com |
| student2 | Alice | 2 | student2@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "tags" exist:
| name | isstandard |
| Tag1 | 1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
| data1 | text | Test field 2 name | Test field 2 description |
| data1 | url | Test field 3 name | Test field 3 description |
@javascript
Scenario: Students can view, list and search entries
Given the following "mod_data > entries" exist:
| database | Test field name | Test field 2 name | Test field 3 name |
| data1 | Student entry 1 | | https://moodledev.io |
| data1 | Student entry 2 | | |
| data1 | Student entry 3 | | |
When I log in as "student1"
And I am on the "Test database name" "data activity" page
Then I should see "Student entry 1"
# Confirm that the URL field is displayed as a link.
And "https://moodledev.io" "link" should exist
And I should see "Student entry 2"
And I should see "Student entry 3"
And I select "Single view" from the "jump" singleselect
And I should see "Student entry 1"
# Confirm that the URL field is displayed as a link.
And "https://moodledev.io" "link" should exist
And I should not see "Student entry 2"
And "2" "link" should exist
And "3" "link" should exist
And I follow "Next"
And I should see "Student entry 2"
And I should not see "Student entry 1"
And I click on "3" "link" in the "region-main" "region"
And I should see "Student entry 3"
And I should not see "Student entry 2"
And I follow "Previous"
And I should see "Student entry 2"
And I should not see "Student entry 1"
And I should not see "Student entry 3"
And I select "List view" from the "jump" singleselect
And I click on "Advanced search" "checkbox"
And I set the field "Test field name" to "Student entry 1"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And I should see "Student entry 1"
And I should not see "Student entry 2"
And I should not see "Student entry 3"
And I set the field "Test field name" to "Student entry"
And I set the field "Order" to "Descending"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And "Student entry 3" "text" should appear before "Student entry 2" "text"
And "Student entry 2" "text" should appear before "Student entry 1" "text"
@javascript
Scenario: Check that searching by tags works as expected
Given the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name |
| data1 | student1 | Student original entry untagged | Student original entry untagged 2 | |
And I am on the "Test database name" "data activity" page logged in as student1
And I click on "Add entry" "button"
# This is required for now to prevent the tag suggestion menu from overlapping over the Save & view button.
And I change window size to "large"
And I set the following fields to these values:
| Test field name | Student original entry tagged |
| Test field 2 name | Student original entry tagged 2 |
And I set the field with xpath "//div[@class='datatagcontrol']//input[@type='text']" to "Tag1"
And I press "Save"
And I should see "Student original entry"
And I should see "Tag1" in the "div.tag_list" "css_element"
And I open the action menu in "#defaulttemplate-single" "css_element"
And I choose "Edit" in the open action menu
And I should see "Tag1" in the ".form-autocomplete-selection" "css_element"
And I follow "Cancel"
And I select "List view" from the "jump" singleselect
And I should see "Tag1" in the "div.tag_list" "css_element"
And I click on "Advanced search" "checkbox"
And I set the field with xpath "//div[@class='datatagcontrol']//input[@type='text']" to "Tag1"
And I click on "#page-content" "css_element"
When I click on "Save settings" "button" in the "data_adv_form" "region"
Then I should see "Student original entry tagged"
And I should see "Student original entry tagged 2"
And I should not see "Student original entry untagged"
And I should not see "Student original entry untagged 2"
@javascript
Scenario: Check that searching by first and last name works as expected
Given the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name |
| data1 | student1 | Student entry 1 | | |
| data1 | student2 | Student entry 2 | | |
When I am on the "Test database name" "data activity" page logged in as teacher1
And I click on "Advanced search" "checkbox"
And I set the field "First name" to "Bob"
And I click on "Save settings" "button" in the "data_adv_form" "region"
Then I should see "Found 1 out of 2 records."
And I should see "Student entry 1"
And I should not see "Student entry 2"
And I set the field "First name" to ""
And I set the field "Last name" to "2"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And I should see "Found 1 out of 2 records."
And I should not see "Student entry 1"
And I should see "Student entry 2"
# Search: no records found.
But I set the field "Last name" to ""
And I set the field "Test field name" to "Student entry 0"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And I should see "No records found."
And I should not see "Student entry 1"
And I should not see "Student entry 2"
# Search all the entries.
And I set the field "Test field name" to "Student entry"
And I click on "Save settings" "button" in the "data_adv_form" "region"
And I should not see "Found 2 out of 2 records."
And I should see "Student entry 1"
And I should see "Student entry 2"
@javascript
Scenario: Database entries can be deleted in batch if delcheck is present
Given the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name |
| data1 | student1 | Student entry 1 | Some student content 1 | http://moodle.com |
| data1 | teacher1 | Teacher entry 2 | Some teacher content 2 | http://moodle.com |
And I am on the "Test database name" "data activity" page logged in as teacher1
And I navigate to "Templates" in current page administration
And I set the field "Templates tertiary navigation" to "List view template"
And I set the following fields to these values:
| Repeated entry | ##delcheck##[[Test field name]]! |
And I click on "Save" "button" in the "sticky-footer" "region"
When I navigate to "Database" in current page administration
When I click on "Select all" "button"
And I click on "Delete selected" "button"
And I press "Delete"
And I should see "No entries yet"
@javascript
Scenario: Database entries cannot be deleted in batch if delcheck is not present
Given the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name |
| data1 | student1 | Student entry 1 | Some student content 1 | http://moodle.com |
| data1 | teacher1 | Teacher entry 2 | Some teacher content 2 | http://moodle.com |
And I am on the "Test database name" "data activity" page logged in as teacher1
Then I should not see "Select all"
And I should not see "Delete selected"
Scenario Outline: Entries are linked based on autolink and open in new window settings
# Param1 refers to `Autolink`, param3 refers to `Open in new window`.
Given the following "mod_data > fields" exist:
| database | type | name | param1 | param3 |
| data1 | url | URL field name | <param1> | <param3> |
And the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name | URL field name |
| data1 | teacher1 | Test field entry | Test field 2 entry | http://example.com/ | www.moodle.org |
When I am on the "Test database name" "data activity" page logged in as teacher1
Then "www.moodle.org" "link" <autolink> exist
# Verify that the URL field is rendered as a link with the correct href attribute and target set to _blank.
And "a[target='_blank'][href='http://www.moodle.org']" "css_element" <autolink> exist
Examples:
| param1 | param3 | autolink |
| 0 | 0 | should not |
| 1 | 1 | should |
@javascript @accessibility
Scenario: Check the accessibility of the database entries page (zero state)
When I am on the "Test database name" "data activity" page logged in as "teacher1"
Then I should see "No entries yet"
And the page should meet accessibility standards
@javascript @accessibility
Scenario: Check the accessibility of the database entries page
Given the following "mod_data > entries" exist:
| database | user | Test field name | Test field 2 name | Test field 3 name |
| data1 | student1 | Student entry 1 | Some student content 1 | http://moodle.com |
| data1 | teacher1 | Teacher entry 2 | Some teacher content 2 | http://moodle.com |
When I am on the "Test database name" "data activity" page logged in as teacher1
Then the page should meet accessibility standards
+73
View File
@@ -0,0 +1,73 @@
@mod @mod_data
Feature: Zero state page (no fields created)
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | n | C1 | data1 |
Scenario: Teachers see buttons to manage database when there is no field created on view page
Given I am on the "Test database name" "data activity" page logged in as "teacher1"
And "Import a preset" "button" should exist
And I am on the "Test database name" "data activity" page
And "Create a field" "button" should exist
And I click on "Create a field" "button"
And I click on "Short text" "link"
And I should see "Create a field"
And I am on the "Test database name" "data activity" page
And "Use a preset" "button" should exist
And I click on "Use a preset" "button"
And I should see "Presets"
@javascript
Scenario: Teachers see buttons to manage database when there is no field created on templates page
Given I am on the "Test database name" "data activity" page logged in as "teacher1"
And "Import a preset" "button" should exist
When I click on "Import a preset" "button"
Then I should see "Preset file"
And I am on the "Test database name" "data activity" page
And I click on "Templates" "link"
And "Create a field" "button" should exist
And I click on "Create a field" "button"
And I click on "Short text" "link"
And I should see "Create a field"
And I am on the "Test database name" "data activity" page
And I click on "Templates" "link"
And "Use a preset" "button" should exist
And I click on "Use a preset" "button"
And I should see "Presets"
@javascript @_file_upload
Scenario: Teachers can import preset from the zero state page
Given I am on the "Test database name" "data activity" page logged in as "teacher1"
And "Import a preset" "button" should exist
When I click on "Import a preset" "button"
And I upload "mod/data/tests/fixtures/image_gallery_preset.zip" file to "Preset file" filemanager
Then I click on "Import preset and apply" "button" in the ".modal-dialog" "css_element"
And I should see "Manage fields"
Then I should see "Preset applied"
@javascript
Scenario: Teacher can use a preset from zero state page on an empty database
Given I am on the "Test database name" "data activity" page logged in as "teacher1"
And I click on "Use a preset" "button"
And I click on "fullname" "radio" in the "Image gallery" "table_row"
And the "Use this preset" "button" should be enabled
Then I click on "Use this preset" "button"
And I should not see "Field mappings"
And I should see "Image" in the "image" "table_row"
@javascript @accessibility
Scenario: Check the accessibility of the database activity zero state
When I am on the "Test database name" "data activity" page logged in as "teacher1"
Then I should see "Start building your activity"
And the page should meet accessibility standards
+216
View File
@@ -0,0 +1,216 @@
<?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/activity_custom_completion.
*
* @package mod_data
* @copyright Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_data;
use advanced_testcase;
use cm_info;
use coding_exception;
use mod_data\completion\custom_completion;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
/**
* Class for unit testing mod_data/activity_custom_completion.
*
* @package mod_data
* @copyright Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_completion_test extends advanced_testcase {
/**
* Data provider for get_state().
*
* @return array[]
*/
public function get_state_provider(): array {
return [
'Undefined rule' => [
'somenonexistentrule', COMPLETION_DISABLED, 0, null, coding_exception::class
],
'Rule not available' => [
'completionentries', COMPLETION_DISABLED, 0, null, moodle_exception::class
],
'Rule available, user has not created entries' => [
'completionentries', COMPLETION_ENABLED, 0, COMPLETION_INCOMPLETE, null
],
'Rule available, user has created entries' => [
'completionentries', COMPLETION_ENABLED, 2, COMPLETION_COMPLETE, null
],
];
}
/**
* Test for get_state().
*
* @dataProvider get_state_provider
* @param string $rule The custom completion rule.
* @param int $available Whether this rule is available.
* @param int $entries The number of entries.
* @param int|null $status Expected status.
* @param string|null $exception Expected exception.
*/
public function test_get_state(string $rule, int $available, int $entries, ?int $status, ?string $exception): void {
global $DB;
if (!is_null($exception)) {
$this->expectException($exception);
}
// Custom completion rule data for cm_info::customdata.
$customdataval = [
'customcompletionrules' => [
$rule => $available
]
];
// 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([
['customdata', $customdataval],
['instance', 1],
]));
// Mock the DB calls.
$DB = $this->createMock(get_class($DB));
$DB->expects($this->atMost(1))
->method('count_records')
->willReturn($entries);
$customcompletion = new custom_completion($mockcminfo, 2);
$this->assertEquals($status, $customcompletion->get_state($rule));
}
/**
* Test for get_defined_custom_rules().
*/
public function test_get_defined_custom_rules(): void {
$rules = custom_completion::get_defined_custom_rules();
$this->assertCount(1, $rules);
$this->assertEquals('completionentries', reset($rules));
}
/**
* Test for get_defined_custom_rule_descriptions().
*/
public function test_get_custom_rule_descriptions(): void {
// Get defined custom rules.
$rules = custom_completion::get_defined_custom_rules();
// Build a mock cm_info instance.
$mockcminfo = $this->getMockBuilder(cm_info::class)
->disableOriginalConstructor()
->onlyMethods(['__get'])
->getMock();
// Instantiate a custom_completion object using the mocked cm_info.
$customcompletion = new custom_completion($mockcminfo, 1);
// Get custom rule descriptions.
$ruledescriptions = $customcompletion->get_custom_rule_descriptions();
// Confirm that defined rules and rule descriptions are consistent with each other.
$this->assertEquals(count($rules), count($ruledescriptions));
foreach ($rules as $rule) {
$this->assertArrayHasKey($rule, $ruledescriptions);
}
}
/**
* Test for is_defined().
*/
public function test_is_defined(): void {
// Build a mock cm_info instance.
$mockcminfo = $this->getMockBuilder(cm_info::class)
->disableOriginalConstructor()
->getMock();
$customcompletion = new custom_completion($mockcminfo, 1);
// Rule is defined.
$this->assertTrue($customcompletion->is_defined('completionentries'));
// Undefined rule.
$this->assertFalse($customcompletion->is_defined('somerandomrule'));
}
/**
* Data provider for test_get_available_custom_rules().
*
* @return array[]
*/
public function get_available_custom_rules_provider(): array {
return [
'Completion entries available' => [
COMPLETION_ENABLED, ['completionentries']
],
'Completion entries not available' => [
COMPLETION_DISABLED, []
],
];
}
/**
* Test for get_available_custom_rules().
*
* @dataProvider get_available_custom_rules_provider
* @param int $status
* @param array $expected
*/
public function test_get_available_custom_rules(int $status, array $expected): void {
$customdataval = [
'customcompletionrules' => [
'completionentries' => $status
]
];
// Build a mock cm_info instance.
$mockcminfo = $this->getMockBuilder(cm_info::class)
->disableOriginalConstructor()
->onlyMethods(['__get'])
->getMock();
// Mock the return of magic getter for the customdata attribute.
$mockcminfo->expects($this->any())
->method('__get')
->with('customdata')
->willReturn($customdataval);
$customcompletion = new custom_completion($mockcminfo, 1);
$this->assertEquals($expected, $customcompletion->get_available_custom_rules());
}
}
+122
View File
@@ -0,0 +1,122 @@
<?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 mod_data\dates.
*
* @package mod_data
* @category test
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_data;
use advanced_testcase;
use cm_info;
use core\activity_dates;
/**
* Class for unit testing mod_data\dates.
*
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dates_test extends advanced_testcase {
/**
* Data provider for get_dates_for_module().
* @return array[]
*/
public function get_dates_for_module_provider(): array {
$now = time();
$before = $now - DAYSECS;
$earlier = $before - DAYSECS;
$after = $now + DAYSECS;
$later = $after + DAYSECS;
return [
'without any dates' => [
null, null, []
],
'only with opening time' => [
$after, null, [
['label' => 'Opens:', 'timestamp' => $after, 'dataid' => 'timeavailablefrom'],
]
],
'only with closing time' => [
null, $after, [
['label' => 'Closes:', 'timestamp' => $after, 'dataid' => 'timeavailableto'],
]
],
'with both times' => [
$after, $later, [
['label' => 'Opens:', 'timestamp' => $after, 'dataid' => 'timeavailablefrom'],
['label' => 'Closes:', 'timestamp' => $later, 'dataid' => 'timeavailableto'],
]
],
'between the dates' => [
$before, $after, [
['label' => 'Opened:', 'timestamp' => $before, 'dataid' => 'timeavailablefrom'],
['label' => 'Closes:', 'timestamp' => $after, 'dataid' => 'timeavailableto'],
]
],
'dates are past' => [
$earlier, $before, [
['label' => 'Opened:', 'timestamp' => $earlier, 'dataid' => 'timeavailablefrom'],
['label' => 'Closed:', 'timestamp' => $before, 'dataid' => 'timeavailableto'],
]
],
];
}
/**
* Test for get_dates_for_module().
*
* @dataProvider get_dates_for_module_provider
* @param int|null $availablefrom The "available from" time in the database activity.
* @param int|null $availableto The "available to" time in the database activity.
* @param array $expected The expected value of calling get_dates_for_module()
*/
public function test_get_dates_for_module(?int $availablefrom, ?int $availableto, array $expected): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$data = ['course' => $course->id];
if ($availablefrom) {
$data['timeavailablefrom'] = $availablefrom;
}
if ($availableto) {
$data['timeavailableto'] = $availableto;
}
$moddata = $this->getDataGenerator()->create_module('data', $data);
$this->setUser($user);
$cm = get_coursemodule_from_instance('data', $moddata->id);
// Make sure we're using a cm_info object.
$cm = cm_info::create($cm);
$dates = activity_dates::get_dates_for_module($cm, (int) $user->id);
$this->assertEquals($expected, $dates);
}
}
+260
View File
@@ -0,0 +1,260 @@
<?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 mod_data;
use context_module;
use mod_data\local\exporter\csv_entries_exporter;
use mod_data\local\exporter\ods_entries_exporter;
use mod_data\local\exporter\utils;
/**
* Unit tests for exporting entries.
*
* @package mod_data
* @copyright 2023 ISB Bayern
* @author Philipp Memmel
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class entries_export_test extends \advanced_testcase {
/**
* Get the test data.
*
* In this instance we are setting up database records to be used in the unit tests.
*
* @return array of test instances
*/
protected function get_test_data(): array {
$this->resetAfterTest(true);
/** @var \mod_data_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($teacher);
$student = $this->getDataGenerator()->create_and_enrol($course, 'student', ['username' => 'student']);
$data = $generator->create_instance(['course' => $course->id]);
$cm = get_coursemodule_from_instance('data', $data->id);
// Add fields.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'numberfield'; // Identifier of the records for testing.
$fieldrecord->type = 'number';
$numberfield = $generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'textfield';
$fieldrecord->type = 'text';
$textfield = $generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'filefield1';
$fieldrecord->type = 'file';
$filefield1 = $generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'filefield2';
$fieldrecord->type = 'file';
$filefield2 = $generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'picturefield';
$fieldrecord->type = 'picture';
$picturefield = $generator->create_field($fieldrecord, $data);
$contents[$numberfield->field->id] = '3';
$contents[$textfield->field->id] = 'a simple text';
$contents[$filefield1->field->id] = 'samplefile.png';
$contents[$filefield2->field->id] = 'samplefile.png';
$contents[$picturefield->field->id] = ['picturefile.png', 'this picture shows something'];
$generator->create_entry($data, $contents);
return [
'teacher' => $teacher,
'student' => $student,
'data' => $data,
'cm' => $cm,
];
}
/**
* Tests the exporting of the content of a mod_data instance by using the csv_entries_exporter.
*
* It also includes more general testing of the functionality of the entries_exporter the csv_entries_exporter
* is inheriting from.
*
* @covers \mod_data\local\exporter\entries_exporter
* @covers \mod_data\local\exporter\entries_exporter::get_records_count()
* @covers \mod_data\local\exporter\entries_exporter::send_file()
* @covers \mod_data\local\exporter\csv_entries_exporter
* @covers \mod_data\local\exporter\utils::data_exportdata
*/
public function test_export_csv(): void {
global $DB;
[
'data' => $data,
'cm' => $cm,
] = $this->get_test_data();
$exporter = new csv_entries_exporter();
$exporter->set_export_file_name('testexportfile');
$fieldrecords = $DB->get_records('data_fields', ['dataid' => $data->id], 'id');
$fields = [];
foreach ($fieldrecords as $fieldrecord) {
$fields[] = data_get_field($fieldrecord, $data);
}
// We select all fields.
$selectedfields = array_map(fn($field) => $field->field->id, $fields);
$currentgroup = groups_get_activity_group($cm);
$context = context_module::instance($cm->id);
$exportuser = false;
$exporttime = false;
$exportapproval = false;
$tags = false;
// We first test the export without exporting files.
// This means file and picture fields will be exported, but only as text (which is the filename),
// so we will receive a csv export file.
$includefiles = false;
utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
$exportuser, $exporttime, $exportapproval, $tags, $includefiles);
$this->assertEquals(file_get_contents(__DIR__ . '/fixtures/test_data_export_without_files.csv'),
$exporter->send_file(false));
$this->assertEquals(1, $exporter->get_records_count());
// We now test the export including files. This will generate a zip archive.
$includefiles = true;
$exporter = new csv_entries_exporter();
$exporter->set_export_file_name('testexportfile');
utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
$exportuser, $exporttime, $exportapproval, $tags, $includefiles);
// We now write the zip archive temporary to disc to be able to parse it and assert it has the correct structure.
$tmpdir = make_request_directory();
file_put_contents($tmpdir . '/testexportarchive.zip', $exporter->send_file(false));
$ziparchive = new \zip_archive();
$ziparchive->open($tmpdir . '/testexportarchive.zip');
$expectedfilecontents = [
// The test generator for mod_data uses a copy of field/picture/pix/sample.png as sample file content for the
// file stored in a file and picture field.
// So we expect that this file has to have the same content as sample.png.
// Also, the default value for the subdirectory in the zip archive containing the files is 'files/'.
'files/samplefile.png' => 'mod/data/field/picture/pix/sample.png',
'files/samplefile_1.png' => 'mod/data/field/picture/pix/sample.png',
'files/picturefile.png' => 'mod/data/field/picture/pix/sample.png',
// By checking that the content of the exported csv is identical to the fixture file it is verified
// that the filenames in the csv file correspond to the names of the exported file.
// It also verifies that files with identical file names in different fields (or records) will be numbered
// automatically (samplefile.png, samplefile_1.png, ...).
'testexportfile.csv' => __DIR__ . '/fixtures/test_data_export_with_files.csv'
];
for ($i = 0; $i < $ziparchive->count(); $i++) {
// We here iterate over all files in the zip archive and check if their content is identical to the files
// in the $expectedfilecontents array.
$filestream = $ziparchive->get_stream($i);
$fileinfo = $ziparchive->get_info($i);
$filecontent = fread($filestream, $fileinfo->size);
$this->assertEquals(file_get_contents($expectedfilecontents[$fileinfo->pathname]), $filecontent);
fclose($filestream);
}
$ziparchive->close();
unlink($tmpdir . '/testexportarchive.zip');
}
/**
* Tests specific ODS exporting functionality.
*
* @covers \mod_data\local\exporter\ods_entries_exporter
* @covers \mod_data\local\exporter\utils::data_exportdata
*/
public function test_export_ods(): void {
global $DB;
[
'data' => $data,
'cm' => $cm,
] = $this->get_test_data();
$exporter = new ods_entries_exporter();
$exporter->set_export_file_name('testexportfile');
$fieldrecords = $DB->get_records('data_fields', ['dataid' => $data->id], 'id');
$fields = [];
foreach ($fieldrecords as $fieldrecord) {
$fields[] = data_get_field($fieldrecord, $data);
}
// We select all fields.
$selectedfields = array_map(fn($field) => $field->field->id, $fields);
$currentgroup = groups_get_activity_group($cm);
$context = context_module::instance($cm->id);
$exportuser = false;
$exporttime = false;
$exportapproval = false;
$tags = false;
// We first test the export without exporting files.
// This means file and picture fields will be exported, but only as text (which is the filename),
// so we will receive an ods export file.
$includefiles = false;
utils::data_exportdata($data->id, $fields, $selectedfields, $exporter, $currentgroup, $context,
$exportuser, $exporttime, $exportapproval, $tags, $includefiles);
$odsrows = $this->get_ods_rows_content($exporter->send_file(false));
// Check, if the headings match with the first row of the ods file.
$i = 0;
foreach ($fields as $field) {
$this->assertEquals($field->field->name, $odsrows[0][$i]);
$i++;
}
// Check, if the values match with the field values.
$this->assertEquals('3', $odsrows[1][0]);
$this->assertEquals('a simple text', $odsrows[1][1]);
$this->assertEquals('samplefile.png', $odsrows[1][2]);
$this->assertEquals('samplefile.png', $odsrows[1][3]);
$this->assertEquals('picturefile.png', $odsrows[1][4]);
// As the logic of renaming the files and building a zip archive is implemented in entries_exporter class, we do
// not need to test this for the ods_entries_exporter, because entries_export_test::test_export_csv already does this.
}
/**
* Helper function to extract the text data as row arrays from an ODS document.
*
* @param string $content the file content
* @return array two-dimensional row/column array with the text content of the first spreadsheet
*/
private function get_ods_rows_content(string $content): array {
$file = tempnam(make_request_directory(), 'ods_');
$filestream = fopen($file, "w");
fwrite($filestream, $content);
$reader = new \OpenSpout\Reader\ODS\Reader();
$reader->open($file);
/** @var \OpenSpout\Reader\ODS\Sheet[] $sheets */
$sheets = $reader->getSheetIterator();
$rowscellsvalues = [];
foreach ($sheets as $sheet) {
/** @var \OpenSpout\Common\Entity\Row[] $rows */
$rows = $sheet->getRowIterator();
foreach ($rows as $row) {
$cellvalues = [];
foreach ($row->getCells() as $cell) {
$cellvalues[] = $cell->getValue();
}
$rowscellsvalues[] = $cellvalues;
}
}
return $rowscellsvalues;
}
}
+222
View File
@@ -0,0 +1,222 @@
<?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 mod_data;
use context_module;
use mod_data\local\exporter\csv_entries_exporter;
use mod_data\local\exporter\ods_entries_exporter;
use mod_data\local\exporter\utils;
/**
* Unit tests for entries_exporter and csv_entries_exporter classes.
*
* Also {@see entries_export_test} class which provides module tests for exporting entries.
*
* @package mod_data
* @covers \mod_data\local\exporter\entries_exporter
* @covers \mod_data\local\exporter\csv_entries_exporter
* @copyright 2023 ISB Bayern
* @author Philipp Memmel
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class entries_exporter_test extends \advanced_testcase {
/**
* Tests get_records_count method.
*
* @covers \mod_data\local\exporter\entries_exporter::get_records_count
* @dataProvider get_records_count_provider
* @param array $rows the rows from the data provider to be tested by the exporter
* @param int $expectedcount the expected count of records to be exported
*/
public function test_get_records_count(array $rows, int $expectedcount): void {
$exporter = new csv_entries_exporter();
foreach ($rows as $row) {
$exporter->add_row($row);
}
$this->assertEquals($expectedcount, $exporter->get_records_count());
}
/**
* Data provider method for self::test_get_records_count.
*
* @return array data for testing
*/
public function get_records_count_provider(): array {
return [
'onlyheader' => [
'rows' => [
['numberfield', 'textfield', 'filefield1', 'filefield2', 'picturefield']
],
'expectedcount' => 0 // Only header present, so we expect record count 0.
],
'onerecord' => [
'rows' => [
['numberfield', 'textfield', 'filefield1', 'filefield2', 'picturefield'],
['3', 'a simple text', 'samplefile.png', 'samplefile_1.png', 'picturefile.png']
],
'expectedcount' => 1
],
'tworecords' => [
'rows' => [
['numberfield', 'textfield', 'filefield1', 'filefield2', 'picturefield'],
['3', 'a simple text', 'samplefile.png', 'samplefile_1.png', 'picturefile.png'],
['5', 'a supersimple text', 'anotherfile.png', 'someotherfile.png', 'andapicture.png']
],
'expectedcount' => 2
]
];
}
/**
* Tests adding of files to the exporter to be included in the exported zip archive.
*
* @dataProvider add_file_from_string_provider
* @covers \mod_data\local\exporter\entries_exporter::add_file_from_string
* @covers \mod_data\local\exporter\entries_exporter::file_exists
* @param array $files array of filename and filecontent to be tested for exporting
* @param bool $success if the exporting of files should be successful
*/
public function test_add_file_from_string(array $files, bool $success): void {
$exporter = new csv_entries_exporter();
foreach ($files as $file) {
if (empty($file['subdir'])) {
$exporter->add_file_from_string($file['filename'], $file['filecontent']);
$this->assertEquals($exporter->file_exists($file['filename']), $success);
} else {
$exporter->add_file_from_string($file['filename'], $file['filecontent'], $file['subdir']);
$this->assertEquals($exporter->file_exists($file['filename'], $file['subdir']), $success);
}
}
}
/**
* Data provider method for self::test_add_file_from_string.
*
* @return array data for testing
*/
public function add_file_from_string_provider(): array {
return [
'one file' => [
'files' => [
[
'filename' => 'testfile.txt',
'filecontent' => 'somecontent'
],
],
'success' => true
],
'more files, also with subdirs' => [
'files' => [
[
'filename' => 'testfile.txt',
'filecontent' => 'somecontent'
],
[
'filename' => 'testfile2.txt',
'filecontent' => 'someothercontent',
'subdir' => 'testsubdir'
],
[
'filename' => 'testfile3.txt',
'filecontent' => 'someverydifferentcontent',
'subdir' => 'files/foo/bar'
],
[
'filename' => 'testfile4.txt',
'filecontent' => 'someverydifferentcontent',
'subdir' => 'files/foo/bar/'
],
[
'filename' => 'testfile5.txt',
'filecontent' => 'someverydifferentcontent',
'subdir' => '/files/foo/bar/'
],
],
'success' => true
],
'nocontent' => [
'files' => [
[
'filename' => '',
'filecontent' => ''
]
],
'success' => false
]
];
}
/**
* Tests if unique filenames are being created correctly.
*
* @covers \mod_data\local\exporter\entries_exporter::create_unique_filename
* @dataProvider create_unique_filename_provider
* @param string $inputfilename the name of the file which should be converted into a unique filename
* @param string $resultfilename the maybe changed $inputfilename, so that it is unique in the exporter
*/
public function test_create_unique_filename(string $inputfilename, string $resultfilename): void {
$exporter = new csv_entries_exporter();
$exporter->add_file_from_string('test.txt', 'somecontent');
$exporter->add_file_from_string('foo.txt', 'somecontent');
$exporter->add_file_from_string('foo_1.txt', 'somecontent');
$exporter->add_file_from_string('foo_2.txt', 'somecontent');
$exporter->add_file_from_string('foo', 'somecontent');
$exporter->add_file_from_string('foo_1', 'somecontent');
$exporter->add_file_from_string('sample_5.txt', 'somecontent');
$exporter->add_file_from_string('bar_1.txt', 'somecontent');
$this->assertEquals($resultfilename, $exporter->create_unique_filename($inputfilename));
}
/**
* Data provider method for self::test_create_unique_filename.
*
* @return array data for testing
*/
public function create_unique_filename_provider(): array {
return [
'does not exist yet' => [
'inputfilename' => 'someuniquename.txt',
'resultfilename' => 'someuniquename.txt'
],
'already exists' => [
'inputfilename' => 'test.txt',
'resultfilename' => 'test_1.txt'
],
'already exists, other numbers as well' => [
'inputfilename' => 'foo.txt',
'resultfilename' => 'foo_3.txt'
],
'file with _5 suffix already exists' => [
'inputfilename' => 'sample_5.txt',
'resultfilename' => 'sample_5_1.txt'
],
'file with _1 suffix already exists' => [
'inputfilename' => 'bar_1.txt',
'resultfilename' => 'bar_1_1.txt'
],
'file without extension unique' => [
'inputfilename' => 'test',
'resultfilename' => 'test'
],
'file without extension not unique' => [
'inputfilename' => 'foo',
'resultfilename' => 'foo_2'
]
];
}
}
+485
View File
@@ -0,0 +1,485 @@
<?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 mod_data;
use coding_exception;
use dml_exception;
use mod_data\local\importer\csv_entries_importer;
use moodle_exception;
use zip_archive;
/**
* Unit tests for import.php.
*
* @package mod_data
* @category test
* @covers \mod_data\local\importer\entries_importer
* @covers \mod_data\local\importer\csv_entries_importer
* @copyright 2019 Tobias Reischmann
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class entries_import_test extends \advanced_testcase {
/**
* Set up function.
*/
protected function setUp(): void {
parent::setUp();
global $CFG;
require_once($CFG->dirroot . '/mod/data/lib.php');
require_once($CFG->dirroot . '/lib/datalib.php');
require_once($CFG->dirroot . '/lib/csvlib.class.php');
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
require_once($CFG->dirroot . '/mod/data/tests/generator/lib.php');
}
/**
* Get the test data.
* In this instance we are setting up database records to be used in the unit tests.
*
* @return array
*/
protected function get_test_data(): array {
$this->resetAfterTest(true);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($teacher);
$student = $this->getDataGenerator()->create_and_enrol($course, 'student', array('username' => 'student'));
$data = $generator->create_instance(array('course' => $course->id));
$cm = get_coursemodule_from_instance('data', $data->id);
// Add fields.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'ID'; // Identifier of the records for testing.
$fieldrecord->type = 'number';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'Param2';
$fieldrecord->type = 'text';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'filefield';
$fieldrecord->type = 'file';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'picturefield';
$fieldrecord->type = 'picture';
$generator->create_field($fieldrecord, $data);
return [
'teacher' => $teacher,
'student' => $student,
'data' => $data,
'cm' => $cm,
];
}
/**
* Test uploading entries for a data instance without userdata.
*
* @throws dml_exception
*/
public function test_import(): void {
[
'data' => $data,
'cm' => $cm,
'teacher' => $teacher,
] = $this->get_test_data();
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import.csv',
'test_data_import.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
// No userdata is present in the file: Fallback is to assign the uploading user as author.
$expecteduserids = array();
$expecteduserids[1] = $teacher->id;
$expecteduserids[2] = $teacher->id;
$records = $this->get_data_records($data->id);
$this->assertCount(2, $records);
foreach ($records as $record) {
$identifier = $record->items['ID']->content;
$this->assertEquals($expecteduserids[$identifier], $record->userid);
}
}
/**
* Test uploading entries for a data instance with userdata.
*
* At least one entry has an identifiable user, which is assigned as author.
*
* @throws dml_exception
*/
public function test_import_with_userdata(): void {
[
'data' => $data,
'cm' => $cm,
'teacher' => $teacher,
'student' => $student,
] = $this->get_test_data();
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_userdata.csv',
'test_data_import_with_userdata.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$expecteduserids = array();
$expecteduserids[1] = $student->id; // User student exists and is assigned as author.
$expecteduserids[2] = $teacher->id; // User student2 does not exist. Fallback is the uploading user.
$records = $this->get_data_records($data->id);
$this->assertCount(2, $records);
foreach ($records as $record) {
$identifier = $record->items['ID']->content;
$this->assertEquals($expecteduserids[$identifier], $record->userid);
}
}
/**
* Test uploading entries for a data instance with userdata and a defined field 'Username'.
*
* This should test the corner case, in which a user has defined a data fields, which has the same name
* as the current lang string for username. In that case, the first Username entry is used for the field.
* The second one is used to identify the author.
*
* @throws coding_exception
* @throws dml_exception
*/
public function test_import_with_field_username(): void {
[
'data' => $data,
'cm' => $cm,
'teacher' => $teacher,
'student' => $student,
] = $this->get_test_data();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Add username field.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'Username';
$fieldrecord->type = 'text';
$generator->create_field($fieldrecord, $data);
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_field_username.csv',
'test_data_import_with_field_username.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$expecteduserids = array();
$expecteduserids[1] = $student->id; // User student exists and is assigned as author.
$expecteduserids[2] = $teacher->id; // User student2 does not exist. Fallback is the uploading user.
$expecteduserids[3] = $student->id; // User student exists and is assigned as author.
$expectedcontent = array();
$expectedcontent[1] = array(
'Username' => 'otherusername1',
'Param2' => 'My first entry',
);
$expectedcontent[2] = array(
'Username' => 'otherusername2',
'Param2' => 'My second entry',
);
$expectedcontent[3] = array(
'Username' => 'otherusername3',
'Param2' => 'My third entry',
);
$records = $this->get_data_records($data->id);
$this->assertCount(3, $records);
foreach ($records as $record) {
$identifier = $record->items['ID']->content;
$this->assertEquals($expecteduserids[$identifier], $record->userid);
foreach ($expectedcontent[$identifier] as $field => $value) {
$this->assertEquals($value, $record->items[$field]->content,
"The value of field \"$field\" for the record at position \"$identifier\" " .
"which is \"{$record->items[$field]->content}\" does not match the expected value \"$value\".");
}
}
}
/**
* Test uploading entries for a data instance with a field 'Username' but only one occurrence in the csv file.
*
* This should test the corner case, in which a user has defined a data fields, which has the same name
* as the current lang string for username. In that case, the only Username entry is used for the field.
* The author should not be set.
*
* @throws coding_exception
* @throws dml_exception
*/
public function test_import_with_field_username_without_userdata(): void {
[
'data' => $data,
'cm' => $cm,
'teacher' => $teacher,
'student' => $student,
] = $this->get_test_data();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Add username field.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'Username';
$fieldrecord->type = 'text';
$generator->create_field($fieldrecord, $data);
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_userdata.csv',
'test_data_import_with_userdata.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
// No userdata is present in the file: Fallback is to assign the uploading user as author.
$expecteduserids = array();
$expecteduserids[1] = $teacher->id;
$expecteduserids[2] = $teacher->id;
$expectedcontent = array();
$expectedcontent[1] = array(
'Username' => 'student',
'Param2' => 'My first entry',
);
$expectedcontent[2] = array(
'Username' => 'student2',
'Param2' => 'My second entry',
);
$records = $this->get_data_records($data->id);
$this->assertCount(2, $records);
foreach ($records as $record) {
$identifier = $record->items['ID']->content;
$this->assertEquals($expecteduserids[$identifier], $record->userid);
foreach ($expectedcontent[$identifier] as $field => $value) {
$this->assertEquals($value, $record->items[$field]->content,
"The value of field \"$field\" for the record at position \"$identifier\" " .
"which is \"{$record->items[$field]->content}\" does not match the expected value \"$value\".");
}
}
}
/**
* Data provider for {@see test_import_without_approved}
*
* @return array[]
*/
public static function import_without_approved_provider(): array {
return [
'Teacher can approve entries' => ['teacher', [1, 1]],
'Student cannot approve entries' => ['student', [0, 0]],
];
}
/**
* Test importing file without approved status column
*
* @param string $user
* @param int[] $expected
*
* @dataProvider import_without_approved_provider
*/
public function test_import_without_approved(string $user, array $expected): void {
$testdata = $this->get_test_data();
['data' => $data, 'cm' => $cm] = $testdata;
$this->setUser($testdata[$user]);
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import.csv', 'test_data_import.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$records = $this->get_data_records($data->id);
$this->assertEquals($expected, array_column($records, 'approved'));
}
/**
* Data provider for {@see test_import_with_approved}
*
* @return array[]
*/
public static function import_with_approved_provider(): array {
return [
'Teacher can approve entries' => ['teacher', [1, 0]],
'Student cannot approve entries' => ['student', [0, 0]],
];
}
/**
* Test importing file with approved status column
*
* @param string $user
* @param int[] $expected
*
* @dataProvider import_with_approved_provider
*/
public function test_import_with_approved(string $user, array $expected): void {
$testdata = $this->get_test_data();
['data' => $data, 'cm' => $cm] = $testdata;
$this->setUser($testdata[$user]);
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_approved.csv',
'test_data_import_with_approved.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$records = $this->get_data_records($data->id);
$this->assertEquals($expected, array_column($records, 'approved'));
}
/**
* Tests the import including files from a zip archive.
*/
public function test_import_with_files(): void {
[
'data' => $data,
'cm' => $cm,
] = $this->get_test_data();
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_files.zip',
'test_data_import_with_files.zip');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$records = $this->get_data_records($data->id);
$ziparchive = new zip_archive();
$ziparchive->open(__DIR__ . '/fixtures/test_data_import_with_files.zip');
$importedcontent = array_values($records)[0]->items;
$this->assertEquals(17, $importedcontent['ID']->content);
$this->assertEquals('samplefile.png', $importedcontent['filefield']->content);
$this->assertEquals('samplepicture.png', $importedcontent['picturefield']->content);
// We now check if content of imported file from zip content is identical to the content of the file
// stored in the mod_data record in the field 'filefield'.
$fileindex = array_values(array_map(fn($file) => $file->index,
array_filter($ziparchive->list_files(), fn($file) => $file->pathname === 'files/samplefile.png')))[0];
$filestream = $ziparchive->get_stream($fileindex);
$filefield = data_get_field_from_name('filefield', $data);
$filefieldfilecontent = fread($filestream, $ziparchive->get_info($fileindex)->size);
$this->assertEquals($filefield->get_file(array_keys($records)[0])->get_content(),
$filefieldfilecontent);
fclose($filestream);
// We now check if content of imported picture from zip content is identical to the content of the picture file
// stored in the mod_data record in the field 'picturefield'.
$fileindex = array_values(array_map(fn($file) => $file->index,
array_filter($ziparchive->list_files(), fn($file) => $file->pathname === 'files/samplepicture.png')))[0];
$filestream = $ziparchive->get_stream($fileindex);
$filefield = data_get_field_from_name('picturefield', $data);
$filefieldfilecontent = fread($filestream, $ziparchive->get_info($fileindex)->size);
$this->assertEquals($filefield->get_file(array_keys($records)[0])->get_content(),
$filefieldfilecontent);
fclose($filestream);
$this->assertCount(1, $importer->get_added_records_messages());
$ziparchive->close();
}
/**
* Tests the import including files from a zip archive.
*/
public function test_import_with_files_missing_file(): void {
[
'data' => $data,
'cm' => $cm,
] = $this->get_test_data();
$importer = new csv_entries_importer(__DIR__ . '/fixtures/test_data_import_with_files_missing_file.zip',
'test_data_import_with_files_missing_file.zip');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$records = $this->get_data_records($data->id);
$ziparchive = new zip_archive();
$ziparchive->open(__DIR__ . '/fixtures/test_data_import_with_files_missing_file.zip');
$importedcontent = array_values($records)[0]->items;
$this->assertEquals(17, $importedcontent['ID']->content);
$this->assertFalse(isset($importedcontent['filefield']));
$this->assertEquals('samplepicture.png', $importedcontent['picturefield']->content);
$this->assertCount(1, $importer->get_added_records_messages());
$ziparchive->close();
}
/**
* Returns the records of the data instance.
*
* Each records has an item entry, which contains all fields associated with this item.
* Each fields has the parameters name, type and content.
*
* @param int $dataid Id of the data instance.
* @return array The records of the data instance.
* @throws dml_exception
*/
private function get_data_records(int $dataid): array {
global $DB;
$records = $DB->get_records('data_records', ['dataid' => $dataid]);
foreach ($records as $record) {
$sql = 'SELECT f.name, f.type, con.content FROM
{data_content} con JOIN {data_fields} f ON con.fieldid = f.id
WHERE con.recordid = :recordid';
$items = $DB->get_records_sql($sql, array('recordid' => $record->id));
$record->items = $items;
}
return $records;
}
/**
* Tests if the amount of imported records is counted properly.
*
* @dataProvider get_added_record_messages_provider
* @param string $datafilecontent the content of the datafile to test as string
* @param int $expectedcount the expected count of messages depending on the datafile content
*/
public function test_get_added_record_messages(string $datafilecontent, int $expectedcount): void {
[
'data' => $data,
'cm' => $cm,
] = $this->get_test_data();
// First we need to create the zip file from the provided data.
$tmpdir = make_request_directory();
$datafile = $tmpdir . '/entries_import_test_datafile_tmp_' . time() . '.csv';
file_put_contents($datafile, $datafilecontent);
$importer = new csv_entries_importer($datafile, 'testdatafile.csv');
$importer->import_csv($cm, $data, 'UTF-8', 'comma');
$this->assertEquals($expectedcount, count($importer->get_added_records_messages()));
}
/**
* Data provider method for self::test_get_added_record_messages.
*
* @return array data for testing
*/
public function get_added_record_messages_provider(): array {
return [
'only header' => [
'datafilecontent' => 'ID,Param2,filefield,picturefield' . PHP_EOL,
'expectedcount' => 0 // One line is being assumed to be the header.
],
'one record' => [
'datafilecontent' => 'ID,Param2,filefield,picturefield' . PHP_EOL
. '5,"some short text",testfilename.pdf,testpicture.png',
'expectedcount' => 1
],
'two records' => [
'datafilecontent' => 'ID,Param2,filefield,picturefield' . PHP_EOL
. '5,"some short text",testfilename.pdf,testpicture.png' . PHP_EOL
. '3,"other text",testfilename2.pdf,testpicture2.png',
'expectedcount' => 2
],
];
}
}
+205
View File
@@ -0,0 +1,205 @@
<?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 mod_data;
use context_module;
use mod_data\local\exporter\csv_entries_exporter;
use mod_data\local\exporter\ods_entries_exporter;
use mod_data\local\exporter\utils;
use mod_data\local\importer\csv_entries_importer;
use zip_archive;
/**
* Unit tests for entries_importer and csv_entries_importer class.
*
* Also {@see entries_import_test} class which provides module tests for importing entries.
*
* @package mod_data
* @covers \mod_data\local\importer\entries_importer
* @covers \mod_data\local\importer\csv_entries_importer
* @copyright 2023 ISB Bayern
* @author Philipp Memmel
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class entries_importer_test extends \advanced_testcase {
/**
* Set up function.
*/
protected function setUp(): void {
parent::setUp();
global $CFG;
require_once($CFG->dirroot . '/mod/data/lib.php');
require_once($CFG->dirroot . '/lib/datalib.php');
require_once($CFG->dirroot . '/lib/csvlib.class.php');
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
require_once($CFG->dirroot . '/mod/data/tests/generator/lib.php');
}
/**
* Get the test data.
* In this instance we are setting up database records to be used in the unit tests.
*
* @return array
*/
protected function get_test_data(): array {
$this->resetAfterTest(true);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($teacher);
$student = $this->getDataGenerator()->create_and_enrol($course, 'student', array('username' => 'student'));
$data = $generator->create_instance(array('course' => $course->id));
$cm = get_coursemodule_from_instance('data', $data->id);
// Add fields.
$fieldrecord = new \stdClass();
$fieldrecord->name = 'ID'; // Identifier of the records for testing.
$fieldrecord->type = 'number';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'Param2';
$fieldrecord->type = 'text';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'filefield';
$fieldrecord->type = 'file';
$generator->create_field($fieldrecord, $data);
$fieldrecord->name = 'picturefield';
$fieldrecord->type = 'picture';
$generator->create_field($fieldrecord, $data);
return [
'teacher' => $teacher,
'student' => $student,
'data' => $data,
'cm' => $cm,
];
}
/**
* Test importing files from zip archive.
*
* @covers \mod_data\local\importer\entries_importer::get_file_content_from_zip
* @covers \mod_data\local\importer\entries_importer::get_data_file_content
* @dataProvider get_file_content_from_zip_provider
* @param array $files array of filenames and filecontents to test
* @param mixed $datafilecontent the expected result returned by the method which is being tested here
*/
public function test_get_file_content_from_zip(array $files, mixed $datafilecontent): void {
// First we need to create the zip file from the provided data.
$tmpdir = make_request_directory();
$zipfilepath = $tmpdir . '/entries_importer_test_tmp_' . time() . '.zip';
$ziparchive = new zip_archive();
$ziparchive->open($zipfilepath);
foreach ($files as $file) {
$localname = empty($file['subdir']) ? $file['filename'] : $file['subdir'] . '/' . $file['filename'];
$ziparchive->add_file_from_string($localname, $file['filecontent']);
}
$ziparchive->close();
// We now created a zip archive according to the data provider's data. We now can test the importer.
$importer = new csv_entries_importer($zipfilepath, 'testzip.zip');
foreach ($files as $file) {
$subdir = empty($file['subdir']) ? '' : $file['subdir'];
$this->assertEquals($file['filecontent'], $importer->get_file_content_from_zip($file['filename'], $subdir));
}
// Test the method to retrieve the datafile content.
$this->assertEquals($datafilecontent, $importer->get_data_file_content());
unlink($zipfilepath);
}
/**
* Data provider method for self::test_get_file_content_from_zip.
*
* @return array data for testing
*/
public function get_file_content_from_zip_provider(): array {
return [
'some files in the zip archive' => [
'files' => [
[
'filename' => 'datafile.csv',
'filecontent' => 'some,csv,data'
],
[
'filename' => 'testfile.txt',
'filecontent' => 'somecontent',
'subdir' => 'files'
],
[
'filename' => 'testfile2.txt',
'filecontent' => 'someothercontent',
'subdir' => 'testsubdir'
]
],
// Should be identical with filecontent of 'datafile.csv' above.
'datafilecontent' => 'some,csv,data'
],
'wrongly placed data file' => [
'files' => [
[
'filename' => 'datafile.csv',
'filecontent' => 'some,csv,data',
'subdir' => 'wrongsubdir'
],
[
'filename' => 'testfile.txt',
'filecontent' => 'somecontent',
'subdir' => 'files'
],
[
'filename' => 'testfile2.txt',
'filecontent' => 'someothercontent',
'subdir' => 'testsubdir'
]
],
// Data file is not in the root directory, though no content should be retrieved.
'datafilecontent' => false
],
'two data files where only one is allowed' => [
'files' => [
[
'filename' => 'datafile.csv',
'filecontent' => 'some,csv,data',
],
[
'filename' => 'anothercsvfile.csv',
'filecontent' => 'some,other,csv,data',
],
[
'filename' => 'testfile.txt',
'filecontent' => 'somecontent',
'subdir' => 'files'
],
[
'filename' => 'testfile2.txt',
'filecontent' => 'someothercontent',
'subdir' => 'testsubdir'
]
],
// There are two data files in the zip root, so the data cannot be imported.
'datafilecontent' => false
],
];
}
}
+461
View File
@@ -0,0 +1,461 @@
<?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/>.
/**
* Events tests.
*
* @package mod_data
* @category test
* @copyright 2014 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_data\event;
use mod_data\local\importer\preset_existing_importer;
use mod_data\manager;
use mod_data\preset;
class events_test extends \advanced_testcase {
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Test the field created event.
*/
public function test_field_created(): void {
$this->setAdminUser();
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Now we want to create a field.
$field = data_get_field_new('text', $data);
$fielddata = new \stdClass();
$fielddata->name = 'Test';
$fielddata->description = 'Test description';
$field->define_field($fielddata);
// Trigger and capture the event for creating a field.
$sink = $this->redirectEvents();
$field->insert_field();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\field_created', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/field.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the field updated event.
*/
public function test_field_updated(): void {
$this->setAdminUser();
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Now we want to create a field.
$field = data_get_field_new('text', $data);
$fielddata = new \stdClass();
$fielddata->name = 'Test';
$fielddata->description = 'Test description';
$field->define_field($fielddata);
$field->insert_field();
// Trigger and capture the event for updating the field.
$sink = $this->redirectEvents();
$field->update_field();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\field_updated', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/field.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the field deleted event.
*/
public function test_field_deleted(): void {
$this->setAdminUser();
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Now we want to create a field.
$field = data_get_field_new('text', $data);
$fielddata = new \stdClass();
$fielddata->name = 'Test';
$fielddata->description = 'Test description';
$field->define_field($fielddata);
$field->insert_field();
// Trigger and capture the event for deleting the field.
$sink = $this->redirectEvents();
$field->delete_field();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\field_deleted', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/field.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the record created event.
*/
public function test_record_created(): void {
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Trigger and capture the event for creating the record.
$sink = $this->redirectEvents();
$recordid = data_add_record($data);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\record_created', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/view.php', array('d' => $data->id, 'rid' => $recordid));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the record updated event.
*
* There is no external API for updating a record, so the unit test will simply create
* and trigger the event and ensure the legacy log data is returned as expected.
*/
public function test_record_updated(): void {
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Trigger an event for updating this record.
$event = \mod_data\event\record_updated::create(array(
'objectid' => 1,
'context' => \context_module::instance($data->cmid),
'courseid' => $course->id,
'other' => array(
'dataid' => $data->id
)
));
// Trigger and capture the event for updating the data record.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\record_updated', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/view.php', array('d' => $data->id, 'rid' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the record deleted event.
*/
public function test_record_deleted(): void {
global $DB;
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Now we want to create a field.
$field = data_get_field_new('text', $data);
$fielddata = new \stdClass();
$fielddata->name = 'Test';
$fielddata->description = 'Test description';
$field->define_field($fielddata);
$field->insert_field();
// Create data record.
$datarecords = new \stdClass();
$datarecords->userid = '2';
$datarecords->dataid = $data->id;
$datarecords->id = $DB->insert_record('data_records', $datarecords);
// Create data content.
$datacontent = new \stdClass();
$datacontent->fieldid = $field->field->id;
$datacontent->recordid = $datarecords->id;
$datacontent->id = $DB->insert_record('data_content', $datacontent);
// Trigger and capture the event for deleting the data record.
$sink = $this->redirectEvents();
data_delete_record($datarecords->id, $data, $course->id, $data->cmid);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\record_deleted', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/view.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the template viewed event.
*
* There is no external API for viewing templates, so the unit test will simply create
* and trigger the event and ensure the legacy log data is returned as expected.
*/
public function test_template_viewed(): void {
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Trigger an event for updating this record.
$event = \mod_data\event\template_viewed::create(array(
'context' => \context_module::instance($data->cmid),
'courseid' => $course->id,
'other' => array(
'dataid' => $data->id
)
));
// Trigger and capture the event for updating the data record.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\template_viewed', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/templates.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Test the template updated event.
*
* There is no external API for updating a template, so the unit test will simply create
* and trigger the event and ensure the legacy log data is returned as expected.
*/
public function test_template_updated(): void {
// Create a course we are going to add a data module to.
$course = $this->getDataGenerator()->create_course();
// The generator used to create a data module.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a data module.
$data = $generator->create_instance(array('course' => $course->id));
// Trigger an event for updating this record.
$event = \mod_data\event\template_updated::create(array(
'context' => \context_module::instance($data->cmid),
'courseid' => $course->id,
'other' => array(
'dataid' => $data->id,
)
));
// Trigger and capture the event for updating the data record.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\template_updated', $event);
$this->assertEquals(\context_module::instance($data->cmid), $event->get_context());
$url = new \moodle_url('/mod/data/templates.php', array('d' => $data->id));
$this->assertEquals($url, $event->get_url());
}
/**
* Data provider for build providers for test_needs_mapping and test_set_affected_fields.
*
* @return array[]
*/
public function preset_importer_provider(): array {
// Image gallery preset is: ['title' => 'text', 'description' => 'textarea', 'image' => 'picture'];
$titlefield = new \stdClass();
$titlefield->name = 'title';
$titlefield->type = 'text';
$descfield = new \stdClass();
$descfield->name = 'description';
$descfield->type = 'textarea';
$imagefield = new \stdClass();
$imagefield->name = 'image';
$imagefield->type = 'picture';
$difffield = new \stdClass();
$difffield->name = 'title';
$difffield->type = 'textarea';
$newfield = new \stdClass();
$newfield->name = 'number';
$newfield->type = 'number';
return [
'Empty database / Importer with fields' => [
'currentfields' => [],
'newfields' => [$titlefield, $descfield, $imagefield],
'expected' => ['field_created' => 3],
],
'Database with fields / Empty importer' => [
'currentfields' => [$titlefield, $descfield, $imagefield],
'newfields' => [],
'expected' => ['field_deleted' => 3],
],
'Fields to create' => [
'currentfields' => [$titlefield, $descfield],
'newfields' => [$titlefield, $descfield, $imagefield],
'expected' => ['field_updated' => 2, 'field_created' => 1],
],
'Fields to remove' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $imagefield],
'expected' => ['field_updated' => 2, 'field_deleted' => 1],
],
'Fields to update' => [
'currentfields' => [$difffield, $descfield, $imagefield],
'newfields' => [$titlefield, $descfield, $imagefield],
'expected' => ['field_updated' => 1, 'field_created' => 1, 'field_deleted' => 1],
],
'Fields to create, remove and update' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $newfield],
'expected' => ['field_updated' => 2, 'field_created' => 1, 'field_deleted' => 2],
],
];
}
/**
* Test for needs_mapping method.
*
* @dataProvider preset_importer_provider
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param array $expected Expected events.
*/
public function test_importing_events(
array $currentfields,
array $newfields,
array $expected
): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
// Trigger and capture the event for deleting the field.
$sink = $this->redirectEvents();
$savedimporter->import(false);
$events = $sink->get_events();
foreach ($expected as $triggeredevent => $count) {
for ($i = 0; $i < $count; $i++) {
$event = array_shift($events);
// Check that the event data is valid.
$this->assertInstanceOf('\mod_data\event\\'.$triggeredevent, $event);
$this->assertEquals(\context_module::instance($activity->cmid), $event->get_context());
$this->assertEventContextNotUsed($event);
$url = new \moodle_url('/mod/data/field.php', ['d' => $activity->id]);
$this->assertEquals($url, $event->get_url());
}
}
}
}
+135
View File
@@ -0,0 +1,135 @@
<?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 mod_data\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use externallib_advanced_testcase;
use core_external\external_api;
use mod_data\manager;
/**
* External function test for delete_saved_preset.
*
* @package mod_data
* @category external
* @since Moodle 4.1
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\external\delete_saved_preset
*/
class delete_saved_preset_test extends externallib_advanced_testcase {
/**
* Test the behaviour of delete_saved_preset().
*
* @covers ::execute
*/
public function test_delete_saved_preset(): void {
$this->resetAfterTest();
// Create course, database activity and users.
$course = $this->getDataGenerator()->create_course();
$data = $this->getDataGenerator()->create_module('data', ['course' => $course->id]);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$manager = manager::create_from_instance($data);
$initialpresets = $manager->get_available_presets();
$preset1name = 'Admin preset';
$preset2name = 'Teacher preset';
// Trying to delete a preset when there is no saved preset created.
$result = delete_saved_preset::execute($data->id, [$preset1name, $preset2name]);
$result = external_api::clean_returnvalue(delete_saved_preset::execute_returns(), $result);
$this->assertFalse($result['result']);
$this->assertCount(2, $result['warnings']);
// Check no preset has been deleted.
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets), count($currentpresets));
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object)[
'name' => $preset1name,
'description' => 'Testing preset description',
];
$adminpreset = $plugingenerator->create_preset($data, $record);
// Update initial preset list.
$initialpresets = $manager->get_available_presets();
// There is a warning for non-existing preset.
$result = delete_saved_preset::execute($data->id, ['Another preset']);
$result = external_api::clean_returnvalue(delete_saved_preset::execute_returns(), $result);
$this->assertFalse($result['result']);
$this->assertCount(1, $result['warnings']);
// Check no preset has been deleted.
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets), count($currentpresets));
// Create a saved preset by teacher.
$this->setUser($teacher);
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object)[
'name' => $preset2name,
'description' => 'Testing preset description',
];
$teacherpreset = $plugingenerator->create_preset($data, $record);
// Update initial preset list.
$this->setAdminUser();
$initialpresets = $manager->get_available_presets();
// Student can't delete presets.
$this->setUser($student);
$result = delete_saved_preset::execute($data->id, [$preset1name, $preset2name]);
$result = external_api::clean_returnvalue(delete_saved_preset::execute_returns(), $result);
$this->assertFalse($result['result']);
$this->assertCount(2, $result['warnings']);
// Check no preset has been deleted.
$this->setAdminUser();
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets), count($currentpresets));
// Teacher can delete their preset.
$this->setUser($teacher);
$result = delete_saved_preset::execute($data->id, [$preset2name]);
$result = external_api::clean_returnvalue(delete_saved_preset::execute_returns(), $result);
$this->assertTrue($result['result']);
$this->assertCount(0, $result['warnings']);
// Check the preset has been deleted.
$this->setAdminUser();
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets) - 1, count($currentpresets));
foreach ($currentpresets as $currentpreset) {
$this->assertNotEquals($currentpreset->name, $preset2name);
}
// Teacher can't delete other users' preset.
$this->setUser($teacher);
$result = delete_saved_preset::execute($data->id, [$preset1name]);
$result = external_api::clean_returnvalue(delete_saved_preset::execute_returns(), $result);
$this->assertFalse($result['result']);
$this->assertCount(1, $result['warnings']);
// Check no preset has been deleted.
$this->setAdminUser();
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets) - 1, count($currentpresets));
}
}
+235
View File
@@ -0,0 +1,235 @@
<?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 mod_data\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use core_external\external_api;
use mod_data\manager;
/**
* External function tests class for get_mapping_information.
*
* @package mod_data
* @category external
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\external\get_mapping_information
*/
class get_mapping_information_test extends \advanced_testcase {
/**
* Data provider for test_get_mapping_information().
*
* @return array[]
*/
public function get_mapping_information_provider(): array {
// Image gallery preset is: ['title' => 'text', 'description' => 'textarea', 'image' => 'picture'];
$titlefield = new \stdClass();
$titlefield->name = 'title';
$titlefield->type = 'text';
$descfield = new \stdClass();
$descfield->name = 'description';
$descfield->type = 'textarea';
$imagefield = new \stdClass();
$imagefield->name = 'image';
$imagefield->type = 'picture';
$difffield = new \stdClass();
$difffield->name = 'title';
$difffield->type = 'textarea';
$newfield = new \stdClass();
$newfield->name = 'number';
$newfield->type = 'number';
return [
'Empty database / Empty importer' => [
'currentfields' => [],
'newfields' => [],
'pluginname' => '',
'fieldstocreate' => '',
'fieldstoremove' => '',
],
'Empty database / Importer with fields' => [
'currentfields' => [],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'image, title, description',
'fieldstoremove' => '',
],
'Database with fields / Empty importer' => [
'currentfields' => [$imagefield, $titlefield, $descfield],
'newfields' => [],
'pluginname' => '',
'fieldstocreate' => '',
'fieldstoremove' => 'image, title, description',
],
'Same fields' => [
'currentfields' => [$imagefield, $titlefield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => '',
'fieldstoremove' => '',
],
'Fields to create' => [
'currentfields' => [$titlefield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'image',
'fieldstoremove' => '',
],
'Fields to remove' => [
'currentfields' => [$imagefield, $titlefield, $descfield, $difffield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => '',
'fieldstoremove' => 'title',
],
'Fields to update' => [
'currentfields' => [$imagefield, $difffield, $descfield],
'newfields' => [$imagefield, $titlefield, $descfield],
'pluginname' => 'imagegallery',
'fieldstocreate' => 'title',
'fieldstoremove' => 'title',
],
'Fields to create, remove and update' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $newfield],
'pluginname' => '',
'fieldstocreate' => 'number',
'fieldstoremove' => 'image, title',
],
];
}
/**
* Test for get_mapping_information method.
*
* @dataProvider get_mapping_information_provider
* @covers ::execute
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param string $fieldstocreate Expected fields on $fieldstocreate.
* @param string $fieldstoremove Expected fields on $fieldstoremove.
*/
public function test_get_mapping_information(
array $currentfields,
array $newfields,
string $pluginname,
string $fieldstocreate,
string $fieldstoremove
): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$module = $manager->get_coursemodule();
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$result = get_mapping_information::execute($module->id, $USER->id . '/' . $saved->name);
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertEquals($result['data']['fieldstocreate'], $fieldstocreate);
$this->assertEquals($result['data']['fieldstoremove'], $fieldstoremove);
// Create presets and importers.
if ($pluginname) {
$result = get_mapping_information::execute($module->id, '/' . $pluginname);;
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertEquals($result['data']['fieldstoremove'], $fieldstoremove);
$this->assertEquals($result['data']['fieldstocreate'], $fieldstocreate);
}
}
/**
* Test for get_mapping_information method for wrong presets.
*
* @covers ::execute
*
*/
public function test_get_mapping_information_for_wrong_preset(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
$module = $manager->get_coursemodule();
// We get warnings with empty preset name.
$result = get_mapping_information::execute($module->id, '');
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertFalse(array_key_exists('data', $result));
$this->assertTrue(array_key_exists('warnings', $result));
// We get warnings with non-existing preset name.
$result = get_mapping_information::execute($module->id, $USER->id . '/Non-existing');
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertFalse(array_key_exists('data', $result));
$this->assertTrue(array_key_exists('warnings', $result));
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($activity, $record);
// We get no warning with the right preset.
$result = get_mapping_information::execute($module->id, $USER->id . '/' . $saved->name);
$result = external_api::clean_returnvalue(get_mapping_information::execute_returns(), $result);
$this->assertTrue(array_key_exists('data', $result));
$this->assertFalse(array_key_exists('warnings', $result));
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
numberfield,textfield,filefield1,filefield2,picturefield
3,"a simple text",samplefile.png,samplefile_1.png,picturefile.png
1 numberfield textfield filefield1 filefield2 picturefield
2 3 a simple text samplefile.png samplefile_1.png picturefile.png
@@ -0,0 +1,2 @@
numberfield,textfield,filefield1,filefield2,picturefield
3,"a simple text",samplefile.png,samplefile.png,picturefile.png
1 numberfield textfield filefield1 filefield2 picturefield
2 3 a simple text samplefile.png samplefile.png picturefile.png
+21
View File
@@ -0,0 +1,21 @@
id,dataid,type,name,description,param1,param2,param3,param4,param5
1,1,latlong,location,Your current location,,-1,,NULL,NULL
2,1,multimenu,beer,Which beer is good?,,,,NULL,NULL
3,1,checkbox,month,One month or Two,,,,NULL,NULL
4,1,number,tfn,Tax file number,,,,NULL,NULL
5,1,radiobutton,gender,Male or Female,,,,NULL,NULL
6,1,url,webpage,Your webpage,,,,NULL,NULL
7,1,text,firstname,Your first name,,,,NULL,NULL
8,1,textarea,about,About you,,60,35,1,NULL
9,1,menu,state,State,,,,NULL,NULL
10,1,text,address,address of the participant,,,,NULL,NULL
11,1,menu,Sport,Favourite sport,,,,NULL,NULL
12,1,text,suburb,suburb,,,,NULL,NULL
13,1,text,mobile,mobile number,,,,NULL,NULL
14,1,text,phone,phone number,,,,NULL,NULL
15,1,text,email,email address,,,,NULL,NULL
16,1,text,license,Driver's license,,,,NULL,NULL
17,1,text,passport,Passport number,,,,NULL,NULL
18,1,menu,browser,prefered browser,,,,NULL,NULL
19,1,menu,OS,Operating System,,,,NULL,NULL
20,1,text,nickname,Nick name,,,,NULL,NULL
1 id dataid type name description param1 param2 param3 param4 param5
2 1 1 latlong location Your current location -1 NULL NULL
3 2 1 multimenu beer Which beer is good? NULL NULL
4 3 1 checkbox month One month or Two NULL NULL
5 4 1 number tfn Tax file number NULL NULL
6 5 1 radiobutton gender Male or Female NULL NULL
7 6 1 url webpage Your webpage NULL NULL
8 7 1 text firstname Your first name NULL NULL
9 8 1 textarea about About you 60 35 1 NULL
10 9 1 menu state State NULL NULL
11 10 1 text address address of the participant NULL NULL
12 11 1 menu Sport Favourite sport NULL NULL
13 12 1 text suburb suburb NULL NULL
14 13 1 text mobile mobile number NULL NULL
15 14 1 text phone phone number NULL NULL
16 15 1 text email email address NULL NULL
17 16 1 text license Driver's license NULL NULL
18 17 1 text passport Passport number NULL NULL
19 18 1 menu browser prefered browser NULL NULL
20 19 1 menu OS Operating System NULL NULL
21 20 1 text nickname Nick name NULL NULL
+3
View File
@@ -0,0 +1,3 @@
ID,Param2
1,"My first entry"
2,"My second entry"
1 ID Param2
2 1 My first entry
3 2 My second entry
@@ -0,0 +1,3 @@
ID,Param2,Approved
1,"My first entry",1
2,"My second entry",0
1 ID Param2 Approved
2 1 My first entry 1
3 2 My second entry 0
@@ -0,0 +1,4 @@
ID,Username,Param2,Username,"Email address"
1,otherusername1,"My first entry",student,student@moodle.org
2,otherusername2,"My second entry",student2,student@moodle.org
3,otherusername3,"My third entry",student,student@moodle.org
1 ID Username Param2 Username Email address
2 1 otherusername1 My first entry student student@moodle.org
3 2 otherusername2 My second entry student2 student@moodle.org
4 3 otherusername3 My third entry student student@moodle.org
Binary file not shown.
@@ -0,0 +1,3 @@
ID,Param2,Username,"Email address"
1,"My first entry",student,student@moodle.org
2,"My second entry",student2,student@moodle.org
1 ID Param2 Username Email address
2 1 My first entry student student@moodle.org
3 2 My second entry student2 student@moodle.org
+101
View File
@@ -0,0 +1,101 @@
id,userid,groupid,dataid,timecreated,timemodified,approved
1,1,0,1,1234567891,1234567892,1
2,2,1,1,1234567891,1234567892,1
3,3,2,1,1234567891,1234567892,1
4,4,1,1,1234567891,1234567892,1
5,5,1,1,1234567891,1234567892,1
6,6,2,1,1234567891,1234567892,1
7,7,0,1,1234567891,1234567892,1
8,8,2,1,1234567891,1234567892,1
9,9,1,1,1234567891,1234567892,1
10,10,1,1,1234567891,1234567892,1
11,11,1,1,1234567891,1234567892,1
12,12,2,1,1234567891,1234567892,1
13,13,2,1,1234567891,1234567892,1
14,14,0,1,1234567891,1234567892,1
15,15,2,1,1234567891,1234567892,1
16,16,0,1,1234567891,1234567892,1
17,17,1,1,1234567891,1234567892,1
18,18,2,1,1234567891,1234567892,1
19,19,0,1,1234567891,1234567892,1
20,20,1,1,1234567891,1234567892,1
21,21,0,1,1234567891,1234567892,1
22,22,2,1,1234567891,1234567892,1
23,23,0,1,1234567891,1234567892,1
24,24,2,1,1234567891,1234567892,1
25,25,2,1,1234567891,1234567892,1
26,26,0,1,1234567891,1234567892,1
27,27,1,1,1234567891,1234567892,1
28,28,2,1,1234567891,1234567892,1
29,29,0,1,1234567891,1234567892,1
30,30,2,1,1234567891,1234567892,1
31,31,0,1,1234567891,1234567892,1
32,32,1,1,1234567891,1234567892,1
33,33,1,1,1234567891,1234567892,1
34,34,2,1,1234567891,1234567892,1
35,35,1,1,1234567891,1234567892,1
36,36,0,1,1234567891,1234567892,1
37,37,0,1,1234567891,1234567892,1
38,38,2,1,1234567891,1234567892,1
39,39,1,1,1234567891,1234567892,1
40,40,1,1,1234567891,1234567892,1
41,41,0,1,1234567891,1234567892,1
42,42,1,1,1234567891,1234567892,1
43,43,0,1,1234567891,1234567892,1
44,44,1,1,1234567891,1234567892,1
45,45,0,1,1234567891,1234567892,1
46,46,0,1,1234567891,1234567892,1
47,47,1,1,1234567891,1234567892,1
48,48,0,1,1234567891,1234567892,1
49,49,0,1,1234567891,1234567892,1
50,50,1,1,1234567891,1234567892,1
51,51,0,1,1234567891,1234567892,1
52,52,0,1,1234567891,1234567892,1
53,53,1,1,1234567891,1234567892,1
54,54,1,1,1234567891,1234567892,1
55,55,0,1,1234567891,1234567892,1
56,56,2,1,1234567891,1234567892,1
57,57,2,1,1234567891,1234567892,1
58,58,2,1,1234567891,1234567892,1
59,59,1,1,1234567891,1234567892,1
60,60,1,1,1234567891,1234567892,1
61,61,0,1,1234567891,1234567892,1
62,62,2,1,1234567891,1234567892,1
63,63,0,1,1234567891,1234567892,1
64,64,0,1,1234567891,1234567892,1
65,65,1,1,1234567891,1234567892,1
66,66,1,1,1234567891,1234567892,1
67,67,0,1,1234567891,1234567892,1
68,68,0,1,1234567891,1234567892,1
69,69,2,1,1234567891,1234567892,1
70,70,2,1,1234567891,1234567892,1
71,71,0,1,1234567891,1234567892,1
72,72,1,1,1234567891,1234567892,1
73,73,1,1,1234567891,1234567892,1
74,74,0,1,1234567891,1234567892,1
75,75,0,1,1234567891,1234567892,1
76,76,2,1,1234567891,1234567892,1
77,77,2,1,1234567891,1234567892,1
78,78,0,1,1234567891,1234567892,1
79,79,1,1,1234567891,1234567892,1
80,80,1,1,1234567891,1234567892,1
81,81,0,1,1234567891,1234567892,1
82,82,1,1,1234567891,1234567892,1
83,83,1,1,1234567891,1234567892,1
84,84,1,1,1234567891,1234567892,1
85,85,1,1,1234567891,1234567892,1
86,86,0,1,1234567891,1234567892,1
87,87,0,1,1234567891,1234567892,1
88,88,0,1,1234567891,1234567892,1
89,89,1,1,1234567891,1234567892,1
90,90,1,1,1234567891,1234567892,0
91,91,2,1,1234567891,1234567892,0
92,92,0,1,1234567891,1234567892,0
93,93,2,1,1234567891,1234567892,0
94,94,1,1,1234567891,1234567892,0
95,95,1,1,1234567891,1234567892,0
96,96,1,1,1234567891,1234567892,0
97,97,0,1,1234567891,1234567892,0
98,98,1,1,1234567891,1234567892,0
99,99,2,1,1234567891,1234567892,0
100,100,0,1,1234567891,1234567892,0
1 id userid groupid dataid timecreated timemodified approved
2 1 1 0 1 1234567891 1234567892 1
3 2 2 1 1 1234567891 1234567892 1
4 3 3 2 1 1234567891 1234567892 1
5 4 4 1 1 1234567891 1234567892 1
6 5 5 1 1 1234567891 1234567892 1
7 6 6 2 1 1234567891 1234567892 1
8 7 7 0 1 1234567891 1234567892 1
9 8 8 2 1 1234567891 1234567892 1
10 9 9 1 1 1234567891 1234567892 1
11 10 10 1 1 1234567891 1234567892 1
12 11 11 1 1 1234567891 1234567892 1
13 12 12 2 1 1234567891 1234567892 1
14 13 13 2 1 1234567891 1234567892 1
15 14 14 0 1 1234567891 1234567892 1
16 15 15 2 1 1234567891 1234567892 1
17 16 16 0 1 1234567891 1234567892 1
18 17 17 1 1 1234567891 1234567892 1
19 18 18 2 1 1234567891 1234567892 1
20 19 19 0 1 1234567891 1234567892 1
21 20 20 1 1 1234567891 1234567892 1
22 21 21 0 1 1234567891 1234567892 1
23 22 22 2 1 1234567891 1234567892 1
24 23 23 0 1 1234567891 1234567892 1
25 24 24 2 1 1234567891 1234567892 1
26 25 25 2 1 1234567891 1234567892 1
27 26 26 0 1 1234567891 1234567892 1
28 27 27 1 1 1234567891 1234567892 1
29 28 28 2 1 1234567891 1234567892 1
30 29 29 0 1 1234567891 1234567892 1
31 30 30 2 1 1234567891 1234567892 1
32 31 31 0 1 1234567891 1234567892 1
33 32 32 1 1 1234567891 1234567892 1
34 33 33 1 1 1234567891 1234567892 1
35 34 34 2 1 1234567891 1234567892 1
36 35 35 1 1 1234567891 1234567892 1
37 36 36 0 1 1234567891 1234567892 1
38 37 37 0 1 1234567891 1234567892 1
39 38 38 2 1 1234567891 1234567892 1
40 39 39 1 1 1234567891 1234567892 1
41 40 40 1 1 1234567891 1234567892 1
42 41 41 0 1 1234567891 1234567892 1
43 42 42 1 1 1234567891 1234567892 1
44 43 43 0 1 1234567891 1234567892 1
45 44 44 1 1 1234567891 1234567892 1
46 45 45 0 1 1234567891 1234567892 1
47 46 46 0 1 1234567891 1234567892 1
48 47 47 1 1 1234567891 1234567892 1
49 48 48 0 1 1234567891 1234567892 1
50 49 49 0 1 1234567891 1234567892 1
51 50 50 1 1 1234567891 1234567892 1
52 51 51 0 1 1234567891 1234567892 1
53 52 52 0 1 1234567891 1234567892 1
54 53 53 1 1 1234567891 1234567892 1
55 54 54 1 1 1234567891 1234567892 1
56 55 55 0 1 1234567891 1234567892 1
57 56 56 2 1 1234567891 1234567892 1
58 57 57 2 1 1234567891 1234567892 1
59 58 58 2 1 1234567891 1234567892 1
60 59 59 1 1 1234567891 1234567892 1
61 60 60 1 1 1234567891 1234567892 1
62 61 61 0 1 1234567891 1234567892 1
63 62 62 2 1 1234567891 1234567892 1
64 63 63 0 1 1234567891 1234567892 1
65 64 64 0 1 1234567891 1234567892 1
66 65 65 1 1 1234567891 1234567892 1
67 66 66 1 1 1234567891 1234567892 1
68 67 67 0 1 1234567891 1234567892 1
69 68 68 0 1 1234567891 1234567892 1
70 69 69 2 1 1234567891 1234567892 1
71 70 70 2 1 1234567891 1234567892 1
72 71 71 0 1 1234567891 1234567892 1
73 72 72 1 1 1234567891 1234567892 1
74 73 73 1 1 1234567891 1234567892 1
75 74 74 0 1 1234567891 1234567892 1
76 75 75 0 1 1234567891 1234567892 1
77 76 76 2 1 1234567891 1234567892 1
78 77 77 2 1 1234567891 1234567892 1
79 78 78 0 1 1234567891 1234567892 1
80 79 79 1 1 1234567891 1234567892 1
81 80 80 1 1 1234567891 1234567892 1
82 81 81 0 1 1234567891 1234567892 1
83 82 82 1 1 1234567891 1234567892 1
84 83 83 1 1 1234567891 1234567892 1
85 84 84 1 1 1234567891 1234567892 1
86 85 85 1 1 1234567891 1234567892 1
87 86 86 0 1 1234567891 1234567892 1
88 87 87 0 1 1234567891 1234567892 1
89 88 88 0 1 1234567891 1234567892 1
90 89 89 1 1 1234567891 1234567892 1
91 90 90 1 1 1234567891 1234567892 0
92 91 91 2 1 1234567891 1234567892 0
93 92 92 0 1 1234567891 1234567892 0
94 93 93 2 1 1234567891 1234567892 0
95 94 94 1 1 1234567891 1234567892 0
96 95 95 1 1 1234567891 1234567892 0
97 96 96 1 1 1234567891 1234567892 0
98 97 97 0 1 1234567891 1234567892 0
99 98 98 1 1 1234567891 1234567892 0
100 99 99 2 1 1234567891 1234567892 0
101 100 100 0 1 1234567891 1234567892 0
@@ -0,0 +1,166 @@
<?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/>.
/**
* Behat data generator for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_data_generator extends behat_generator_base {
/**
* Get a list of the entities that Behat can create using the generator step.
*
* @return array
*/
protected function get_creatable_entities(): array {
return [
'entries' => [
'singular' => 'entry',
'datagenerator' => 'entry',
'required' => ['database'],
'switchids' => ['database' => 'databaseid', 'user' => 'userid', 'group' => 'groupid'],
],
'fields' => [
'singular' => 'field',
'datagenerator' => 'field',
'required' => ['database', 'type', 'name'],
'switchids' => ['database' => 'databaseid'],
],
'templates' => [
'singular' => 'template',
'datagenerator' => 'template',
'required' => ['database', 'name'],
'switchids' => ['database' => 'databaseid'],
],
'presets' => [
'singular' => 'preset',
'datagenerator' => 'preset',
'required' => ['database', 'name'],
'switchids' => ['database' => 'databaseid', 'user' => 'userid'],
],
];
}
/**
* Get the database id using an activity idnumber.
*
* @param string $idnumber
* @return int The database id
*/
protected function get_database_id(string $idnumber): int {
$cm = $this->get_cm_by_activity_name('data', $idnumber);
return $cm->instance;
}
/**
* Add an entry.
*
* @param array $data Entry data.
*/
public function process_entry(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
unset($data['databaseid']);
$userid = 0;
if (array_key_exists('userid', $data)) {
$userid = $data['userid'];
unset($data['userid']);
}
if (array_key_exists('groupid', $data)) {
$groupid = $data['groupid'];
unset($data['groupid']);
} else {
$groupid = 0;
}
$data = array_reduce(array_keys($data), function ($fields, $fieldname) use ($data, $database) {
global $DB;
$field = $DB->get_record('data_fields', ['name' => $fieldname, 'dataid' => $database->id], 'id', MUST_EXIST);
$fields[$field->id] = $data[$fieldname];
return $fields;
}, []);
$this->get_data_generator()->create_entry($database, $data, $groupid, [], null, $userid);
}
/**
* Add a field.
*
* @param array $data Field data.
*/
public function process_field(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
unset($data['databaseid']);
$this->get_data_generator()->create_field((object) $data, $database);
}
/**
* Add a template.
*
* @param array $data Template data.
*/
public function process_template(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
if (empty($data['content'])) {
data_generate_default_template($database, $data['name']);
} else {
$newdata = new stdClass();
$newdata->id = $database->id;
$newdata->{$data['name']} = $data['content'];
$DB->update_record('data', $newdata);
}
}
/**
* Saves a preset.
*
* @param array $data Preset data.
*/
protected function process_preset(array $data): void {
global $DB;
$instance = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
$this->get_data_generator()->create_preset($instance, (object) $data);
}
/**
* Get the module data generator.
*
* @return mod_data_generator Database data generator.
*/
protected function get_data_generator(): mod_data_generator {
return $this->componentdatagenerator;
}
}
+424
View File
@@ -0,0 +1,424 @@
<?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/>.
/**
* Data generator class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_data\manager;
use mod_data\preset;
defined('MOODLE_INTERNAL') || die();
/**
* Data generator class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_data_generator extends testing_module_generator {
/**
* @var int keep track of how many database fields have been created.
*/
protected $databasefieldcount = 0;
/**
* @var int keep track of how many database records have been created.
*/
protected $databaserecordcount = 0;
/**
* To be called from data reset code only,
* do not use in tests.
* @return void
*/
public function reset() {
$this->databasefieldcount = 0;
$this->databaserecordcount = 0;
parent::reset();
}
/**
* Creates a mod_data instance
*
* @param array $record
* @param array $options
* @return StdClass
*/
public function create_instance($record = null, array $options = null) {
// Note, the parent class does not type $record to cast to array and then to object.
$record = (object) (array) $record;
if (!isset($record->assessed)) {
$record->assessed = 0;
}
if (!isset($record->scale)) {
$record->scale = 0;
}
return parent::create_instance((array) $record, $options);
}
/**
* Creates a field for a mod_data instance.
*
* @param stdClass $record
* @param stdClass|null $data
* @return data_field_base
*/
public function create_field(stdClass $record = null, $data = null) {
$record = (array) $record;
$this->databasefieldcount++;
if (!isset($data->course)) {
throw new coding_exception('course must be present in phpunit_util::create_field() $data');
}
if (!isset($data->id)) {
throw new coding_exception('dataid must be present in phpunit_util::create_field() $data');
} else {
$record['dataid'] = $data->id;
}
if (!isset($record['type'])) {
throw new coding_exception('type must be present in phpunit_util::create_field() $record');
}
if (!isset($record['required'])) {
$record['required'] = 0;
}
if (!isset($record['name'])) {
$record['name'] = "testField - " . $this->databasefieldcount;
}
if (!isset($record['description'])) {
$record['description'] = " This is testField - " . $this->databasefieldcount;
}
if (isset($record['param1']) && !empty($record['param1'])) {
// Some fields have multiline entries.
$record['param1'] = str_replace('\n', "\n", $record['param1']);
}
if (!isset($record['param1'])) {
if ($record['type'] == 'checkbox') {
$record['param1'] = implode("\n", array('opt1', 'opt2', 'opt3', 'opt4'));
} else if ($record['type'] == 'radiobutton') {
$record['param1'] = implode("\n", array('radioopt1', 'radioopt2', 'radioopt3', 'radioopt4'));
} else if ($record['type'] == 'menu') {
$record['param1'] = implode("\n", array('menu1', 'menu2', 'menu3', 'menu4'));
} else if ($record['type'] == 'multimenu') {
$record['param1'] = implode("\n", array('multimenu1', 'multimenu2', 'multimenu3', 'multimenu4'));
} else if (($record['type'] === 'text') || ($record['type'] === 'url')) {
$record['param1'] = 1;
} else if ($record['type'] == 'latlong') {
$record['param1'] = 'Google Maps';
} else {
$record['param1'] = '';
}
}
if (!isset($record['param2'])) {
if ($record['type'] === 'textarea') {
$record['param2'] = 60;
} else if ($record['type'] == 'latlong') {
$record['param2'] = -1;
} else {
$record['param2'] = '';
}
}
if (!isset($record['param3'])) {
if (($record['type'] === 'textarea')) {
$record['param3'] = 35;
} else if ($record['type'] == 'picture' || $record['type'] == 'file') {
$record['param3'] = 0;
} else {
$record['param3'] = '';
}
}
if (!isset($record['param4'])) {
if (($record['type'] === 'textarea')) {
$record['param4'] = 1;
}
}
if (!isset($record['param5'])) {
if (($record['type'] === 'textarea')) {
$record['param5'] = 0;
}
}
$record = (object) $record;
$field = data_get_field($record, $data);
$field->insert_field();
return $field;
}
/**
* Creates a field for a mod_data instance.
* Keep in mind the default data field params created in create_field() function!
* ...if you haven't provided your own custom data field parameters there.
* The developers using the generator must adhere to the following format :
*
* Syntax : $contents[ fieldid ] = fieldvalue
* $contents['checkbox'] = array('val1', 'val2', 'val3' .....)
* $contents['data'] = 'dd-mm-yyyy'
* $contents['menu'] = 'value';
* $contents['multimenu'] = array('val1', 'val2', 'val3' .....)
* $contents['number'] = 'numeric value'
* $contents['radiobuton'] = 'value'
* $contents['text'] = 'text'
* $contents['textarea'] = 'text'
* $contents['url'] = 'example.url' or array('example.url', 'urlname')
* $contents['latlong'] = array('value for lattitude', 'value for longitude')
* $contents['file'] = 'filename or draftitemid'
* $contents['picture'] = array('filename or draftitemid', 'alternative text')
*
* @param stdClass $data record from table {data}
* @param array $contents
* @param int $groupid
* @param array $tags
* @param array $options
* @param int $userid if defined, it will be the author of the entry
* @return int id of the generated record in table {data_records}
*/
public function create_entry($data, array $contents, $groupid = 0, $tags = [], array $options = null, int $userid = 0) {
global $DB, $USER, $CFG;
// Set current user if defined.
if (!empty($userid)) {
$currentuser = $USER;
$user = \core_user::get_user($userid);
$this->set_user($user);
}
$this->databaserecordcount++;
$recordid = data_add_record($data, $groupid);
if (isset($options['approved'])) {
data_approve_entry($recordid, !empty($options['approved']));
} else {
$approved = null;
}
$fields = $DB->get_records('data_fields', array('dataid' => $data->id));
// Validating whether required field are filled.
foreach ($fields as $field) {
$fieldhascontent = true;
$field = data_get_field($field, $data);
$fieldid = $field->field->id;
if ($field->type === 'date') {
$values = array();
$temp = explode('-', $contents[$fieldid], 3);
$values['field_' . $fieldid . '_day'] = (int)trim($temp[0]);
$values['field_' . $fieldid . '_month'] = (int)trim($temp[1]);
$values['field_' . $fieldid . '_year'] = (int)trim($temp[2]);
// Year should be less than 2038, so it can be handled by 32 bit windows.
if ($values['field_' . $fieldid . '_year'] > 2038) {
throw new coding_exception('DateTime::getTimestamp resturns false on 32 bit win for year beyond ' .
'2038. Please use year less than 2038.');
}
$contents[$fieldid] = $values;
foreach ($values as $fieldname => $value) {
if (!$field->notemptyfield($value, $fieldname)) {
$fieldhascontent = false;
}
}
} else if ($field->type === 'textarea') {
$values = array();
$values['field_' . $fieldid] = $contents[$fieldid];
$values['field_' . $fieldid . '_content1'] = 1;
$contents[$fieldid] = $values;
$fieldname = 'field_' . $fieldid;
if (!$field->notemptyfield($values[$fieldname], $fieldname)) {
$fieldhascontent = false;
}
} else if ($field->type === 'url') {
$values = array();
if (is_array($contents[$fieldid])) {
foreach ($contents[$fieldid] as $key => $value) {
$values['field_' . $fieldid . '_' . $key] = $value;
}
} else {
$values['field_' . $fieldid . '_0'] = $contents[$fieldid];
}
$contents[$fieldid] = $values;
$fieldname = 'field_' . $fieldid . '_0';
if (!$field->notemptyfield($values[$fieldname], $fieldname)) {
$fieldhascontent = false;
}
} else if ($field->type === 'latlong') {
$values = array();
foreach ($contents[$fieldid] as $key => $value) {
$values['field_' . $fieldid . '_' . $key] = $value;
}
$contents[$fieldid] = $values;
$fieldname = 'field_' . $fieldid . '_0';
if (!$field->notemptyfield($values[$fieldname], $fieldname)) {
$fieldhascontent = false;
}
} else if ($field->type === 'file' || $field->type === 'picture') {
if (is_array($contents[$fieldid])) {
list($itemid, $alttext) = $contents[$fieldid];
} else {
$itemid = $contents[$fieldid];
$alttext = '';
}
if (strlen($itemid) && !is_numeric($itemid)) {
// We expect draftarea item id here but it can also be a filename, in this case provider will generate file.
$filename = $itemid;
$usercontext = context_user::instance($USER->id);
$itemid = file_get_unused_draft_itemid();
get_file_storage()->create_file_from_string(['component' => 'user', 'filearea' => 'draft',
'contextid' => $usercontext->id, 'itemid' => $itemid, 'filepath' => '/',
'filename' => $filename],
file_get_contents($CFG->dirroot.'/mod/data/field/picture/pix/sample.png'));
}
$fieldname = 'field_' . $fieldid . '_file';
if ($field->type === 'file') {
$contents[$fieldid] = $itemid;
} else {
$contents[$fieldid] = [
$fieldname => $itemid,
'field_' . $fieldid . '_alttext' => $alttext
];
}
if (!$field->notemptyfield($itemid, $fieldname)) {
$fieldhascontent = false;
}
} else {
if ($field->notemptyfield($contents[$fieldid], 'field_' . $fieldid . '_0')) {
continue;
}
}
if ($field->field->required && !$fieldhascontent) {
return false;
}
}
foreach ($contents as $fieldid => $content) {
$field = data_get_field_from_id($fieldid, $data);
if (is_array($content) and in_array($field->type, array('date', 'textarea', 'url', 'picture', 'latlong'))) {
foreach ($content as $fieldname => $value) {
$field->update_content($recordid, $value, $fieldname);
}
} else {
$field->update_content($recordid, $content);
}
}
if (!empty($tags)) {
$cm = get_coursemodule_from_instance('data', $data->id);
core_tag_tag::set_item_tags('mod_data', 'data_records', $recordid,
context_module::instance($cm->id), $tags);
}
if (isset($currentuser)) {
$this->set_user($currentuser);
}
return $recordid;
}
/**
* Creates a preset from a mod_data instance.
*
* @param stdClass $instance The mod_data instance.
* @param stdClass|null $record The preset information, like 'name'.
* @return preset The preset that has been created.
*/
public function create_preset(stdClass $instance, stdClass $record = null): preset {
global $USER;
if (is_null($record)) {
$record = new stdClass();
}
// Set current user if defined.
if (isset($record->userid) && $record->userid != $USER->id) {
$currentuser = $USER;
$user = \core_user::get_user($record->userid);
$this->set_user($user);
}
// Fill in optional values if not specified.
$presetname = 'New preset ' . microtime();
if (isset($record->name)) {
$presetname = $record->name;
}
$presetdescription = null;
if (isset($record->description)) {
$presetdescription = $record->description;
}
$manager = manager::create_from_instance($instance);
$preset = preset::create_from_instance($manager, $presetname, $presetdescription);
$preset->save();
if (isset($currentuser)) {
$this->set_user($currentuser);
}
return $preset;
}
}
+350
View File
@@ -0,0 +1,350 @@
<?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 mod_data;
use stdClass;
/**
* PHPUnit data generator testcase.
*
* @package mod_data
* @category phpunit
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data_generator
*/
class generator_test extends \advanced_testcase {
/**
* @covers ::create_instance
*/
public function test_generator(): void {
global $DB;
$this->resetAfterTest(true);
$this->assertEquals(0, $DB->count_records('data'));
$course = $this->getDataGenerator()->create_course();
/** @var mod_data_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$this->assertInstanceOf('mod_data_generator', $generator);
$this->assertEquals('data', $generator->get_modulename());
$generator->create_instance(['course' => $course->id]);
$generator->create_instance(['course' => $course->id]);
$data = $generator->create_instance(['course' => $course->id]);
$this->assertEquals(3, $DB->count_records('data'));
$cm = get_coursemodule_from_instance('data', $data->id);
$this->assertEquals($data->id, $cm->instance);
$this->assertEquals('data', $cm->modname);
$this->assertEquals($course->id, $cm->course);
$context = \context_module::instance($cm->id);
$this->assertEquals($data->cmid, $context->instanceid);
// Test gradebook integration using low level DB access - DO NOT USE IN PLUGIN CODE!
$data = $generator->create_instance(['course' => $course->id, 'assessed' => 1, 'scale' => 100]);
$gitem = $DB->get_record('grade_items', [
'courseid' => $course->id,
'itemtype' => 'mod',
'itemmodule' => 'data',
'iteminstance' => $data->id,
]);
$this->assertNotEmpty($gitem);
$this->assertEquals(100, $gitem->grademax);
$this->assertEquals(0, $gitem->grademin);
$this->assertEquals(GRADE_TYPE_VALUE, $gitem->gradetype);
}
/**
* @covers ::create_field
*/
public function test_create_field(): void {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$this->assertEquals(0, $DB->count_records('data'));
$course = $this->getDataGenerator()->create_course();
/** @var mod_data_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$this->assertInstanceOf('mod_data_generator', $generator);
$this->assertEquals('data', $generator->get_modulename());
$data = $generator->create_instance(['course' => $course->id]);
$this->assertEquals(1, $DB->count_records('data'));
$cm = get_coursemodule_from_instance('data', $data->id);
$this->assertEquals($data->id, $cm->instance);
$this->assertEquals('data', $cm->modname);
$this->assertEquals($course->id, $cm->course);
$context = \context_module::instance($cm->id);
$this->assertEquals($data->cmid, $context->instanceid);
$fieldtypes = ['checkbox', 'date', 'menu', 'multimenu', 'number', 'radiobutton', 'text', 'textarea', 'url'];
$count = 1;
// Creating test Fields with default parameter values.
foreach ($fieldtypes as $fieldtype) {
// Creating variables dynamically.
$fieldname = 'field-' . $count;
$record = new \stdClass();
$record->name = $fieldname;
$record->type = $fieldtype;
${$fieldname} = $this->getDataGenerator()->get_plugin_generator('mod_data')->create_field($record, $data);
$this->assertInstanceOf('data_field_' . $fieldtype, ${$fieldname});
$count++;
}
$this->assertEquals(count($fieldtypes), $DB->count_records('data_fields', ['dataid' => $data->id]));
}
/**
* @covers ::create_entry
*/
public function test_create_entry(): void {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$this->assertEquals(0, $DB->count_records('data'));
$user1 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
$groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'groupA']);
$this->getDataGenerator()->create_group_member(['userid' => $user1->id, 'groupid' => $groupa->id]);
/** @var mod_data_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$this->assertInstanceOf('mod_data_generator', $generator);
$this->assertEquals('data', $generator->get_modulename());
$data = $generator->create_instance(['course' => $course->id]);
$this->assertEquals(1, $DB->count_records('data'));
$cm = get_coursemodule_from_instance('data', $data->id);
$this->assertEquals($data->id, $cm->instance);
$this->assertEquals('data', $cm->modname);
$this->assertEquals($course->id, $cm->course);
$context = \context_module::instance($cm->id);
$this->assertEquals($data->cmid, $context->instanceid);
$fieldtypes = ['checkbox', 'date', 'menu', 'multimenu', 'number', 'radiobutton', 'text', 'textarea', 'url',
'latlong', 'file', 'picture',
];
$count = 1;
// Creating test Fields with default parameter values.
foreach ($fieldtypes as $fieldtype) {
// Creating variables dynamically.
$fieldname = 'field-' . $count;
$record = new \stdClass();
$record->name = $fieldname;
$record->type = $fieldtype;
$record->required = 1;
$this->getDataGenerator()->get_plugin_generator('mod_data')->create_field($record, $data);
$count++;
}
$fields = $DB->get_records('data_fields', ['dataid' => $data->id], 'id');
$contents = [];
$contents[] = ['opt1', 'opt2', 'opt3', 'opt4'];
$contents[] = '01-01-2037'; // It should be lower than 2038, to avoid failing on 32-bit windows.
$contents[] = 'menu1';
$contents[] = ['multimenu1', 'multimenu2', 'multimenu3', 'multimenu4'];
$contents[] = '12345';
$contents[] = 'radioopt1';
$contents[] = 'text for testing';
$contents[] = '<p>text area testing<br /></p>';
$contents[] = ['example.url', 'sampleurl'];
$contents[] = [-31.9489873, 115.8382036]; // Latlong.
$contents[] = 'Filename.pdf'; // File - filename.
$contents[] = ['Cat1234.jpg', 'Cat']; // Picture - filename with alt text.
$count = 0;
$fieldcontents = [];
foreach ($fields as $fieldrecord) {
$fieldcontents[$fieldrecord->id] = $contents[$count++];
}
$tags = ['Cats', 'mice'];
$this->setUser($user1);
$datarecordid = $this->getDataGenerator()->get_plugin_generator('mod_data')->create_entry(
$data,
$fieldcontents,
$groupa->id,
$tags
);
$this->assertEquals(1, $DB->count_records('data_records', ['dataid' => $data->id]));
$this->assertEquals(count($contents), $DB->count_records('data_content', ['recordid' => $datarecordid]));
$entry = $DB->get_record('data_records', ['id' => $datarecordid]);
$this->assertEquals($entry->groupid, $groupa->id);
$contents = $DB->get_records('data_content', ['recordid' => $datarecordid], 'id');
$contentstartid = 0;
$flag = 0;
foreach ($contents as $key => $content) {
if (!$flag++) {
$contentstartid = $key;
}
$this->assertFalse($content->content == null);
}
$this->assertEquals($contents[$contentstartid]->content, 'opt1##opt2##opt3##opt4');
$this->assertEquals($contents[++$contentstartid]->content, '2114380800');
$this->assertEquals($contents[++$contentstartid]->content, 'menu1');
$this->assertEquals($contents[++$contentstartid]->content, 'multimenu1##multimenu2##multimenu3##multimenu4');
$this->assertEquals($contents[++$contentstartid]->content, '12345');
$this->assertEquals($contents[++$contentstartid]->content, 'radioopt1');
$this->assertEquals($contents[++$contentstartid]->content, 'text for testing');
$this->assertEquals($contents[++$contentstartid]->content, '<p>text area testing<br /></p>');
$this->assertEquals($contents[$contentstartid]->content1, '1');
$this->assertEquals($contents[++$contentstartid]->content, 'http://example.url');
$this->assertEquals($contents[$contentstartid]->content1, 'sampleurl');
$this->assertEquals(
['Cats', 'mice'],
array_values(\core_tag_tag::get_item_tags_array('mod_data', 'data_records', $datarecordid))
);
}
/**
* Test for create_preset().
*
* @dataProvider create_preset_provider
* @covers ::create_preset
* @param stdClass|null $record data for the preset that will be created (like name or description)
*/
public function test_create_preset(?stdClass $record): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
if (!is_null($record) && property_exists($record, 'user')) {
$user = $this->getDataGenerator()->create_and_enrol($course, 'teacher', (object) ['username' => $record->user]);
$record->userid = $user->id;
unset($record->user);
}
// Check initially there are no saved presets.
$manager = manager::create_from_coursemodule($cm);
$savedpresets = $manager->get_available_saved_presets();
$this->assertEmpty($savedpresets);
// Create one preset with the configuration in $record.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$preset = $plugingenerator->create_preset($activity, $record);
// Check the preset has been saved.
$savedpresets = $manager->get_available_saved_presets();
$this->assertCount(1, $savedpresets);
// Check the preset name has the expected value.
if (is_null($record) || !property_exists($record, 'name')) {
$this->assertStringStartsWith('New preset', $preset->name);
} else {
$this->assertEquals($record->name, $preset->name);
}
// Check the preset description has the expected value.
if (is_null($record) || !property_exists($record, 'description')) {
$this->assertEmpty($preset->description);
} else {
$this->assertEquals($record->description, $preset->description);
}
// Check the preset author has the expected value.
if (is_null($record) || !property_exists($record, 'userid')) {
$this->assertEquals($USER->id, $preset->get_userid());
} else {
$this->assertEquals($record->userid, $preset->get_userid());
}
// Check the file has been updated properly.
$this->assertNotNull($preset->storedfile);
}
/**
* Data provider for test_create_preset().
*
* @return array
*/
public function create_preset_provider(): array {
return [
'Create using the default configuration' => [
'record' => null,
],
'Create with a given name but no description' => [
'record' => (object) [
'name' => 'World recipes preset',
],
],
'Create with a given description but no name' => [
'record' => (object) [
'description' => 'This is a preset to collect the most popular world recipes.',
],
],
'Create with a given name and description' => [
'record' => (object) [
'name' => 'World recipes preset',
'description' => 'This is a preset to collect the most popular world recipes.',
],
],
'Create with a given user but no description or name' => [
'record' => (object) [
'user' => 'teacher1',
],
],
'Create with a given name and user but no description' => [
'record' => (object) [
'name' => 'World recipes preset',
'user' => 'teacher1',
],
],
'Create with a given description and user but no name' => [
'record' => (object) [
'description' => 'This is a preset to collect the most popular world recipes.',
'user' => 'teacher1',
],
],
'Create with a given name, description and user' => [
'record' => (object) [
'name' => 'World recipes preset',
'description' => 'This is a preset to collect the most popular world recipes.',
'user' => 'teacher1',
],
],
];
}
}
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
<?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 mod_data;
use mod_data\external\record_exporter;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/data/locallib.php');
/**
* Unit tests for locallib.php
*
* @package mod_data
* @copyright 2022 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class locallib_test extends \advanced_testcase {
/**
* Confirms that search is working
* @covers ::data_search_entries
*/
public function test_data_search_entries(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$record = new \stdClass();
$record->course = $course->id;
$record->name = "Mod data delete test";
$record->intro = "Some intro of some sort";
$module = $this->getDataGenerator()->create_module('data', $record);
$titlefield = $this->getDataGenerator()->get_plugin_generator('mod_data')->create_field(
(object) [
'name' => 'title',
'type' => 'text',
'required' => 1
],
$module);
$captionfield = $this->getDataGenerator()->get_plugin_generator('mod_data')->create_field(
(object) [
'name' => 'caption',
'type' => 'text',
'required' => 1
],
$module);
$this->getDataGenerator()->get_plugin_generator('mod_data')->create_entry($module, [
$titlefield->field->id => 'Entry 1',
$captionfield->field->id => 'caption'
]);
$this->getDataGenerator()->get_plugin_generator('mod_data')->create_entry($module, [
$titlefield->field->id => 'Entry 2',
$captionfield->field->id => ''
]);
$cm = get_coursemodule_from_id('data', $module->cmid);
// Search for entries without any search query set, we should return them all.
list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
data_search_entries($module, $cm, \context_course::instance($course->id), 'list', 0);
$this->assertCount(2, $records);
// Search for entries for "caption" we should return only one of them.
list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
data_search_entries($module, $cm, \context_course::instance($course->id), 'list', 0, 'caption');
$this->assertCount(1, $records);
// Same search but we order by title.
list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
data_search_entries($module, $cm, \context_course::instance($course->id), 'list', 0, 'caption',
$titlefield->field->id, 'ASC');
$this->assertCount(1, $records);
$this->assert_record_entries_contains($records, $captionfield->field->id, 'caption');
// Now with advanced search.
$defaults = [];
$fn = $ln = ''; // Defaults for first and last name.
// Force value for advanced search.
$_GET['f_' . $captionfield->field->id] = 'caption';
list($searcharray, $searchtext) = data_build_search_array($module, false, [], $defaults, $fn, $ln);
list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
data_search_entries($module, $cm, \context_course::instance($course->id), 'list', 0, $searchtext,
$titlefield->field->id, 'ASC', 0, 0, true, $searcharray);
$this->assertCount(1, $records);
$this->assert_record_entries_contains($records, $captionfield->field->id, 'caption');
}
/**
* Assert that all records contains a value for the matching field id.
*
* @param array $records
* @param int $fieldid
* @param string $content
* @return void
*/
private function assert_record_entries_contains($records, $fieldid, $content) {
global $DB;
foreach ($records as $record) {
$fieldscontent = $DB->get_records('data_content', ['recordid' => $record->id]);
foreach ($fieldscontent as $fieldcontent) {
if ($fieldcontent->id == $fieldid) {
$this->assertStringContainsString($fieldcontent->content, $content);
}
}
}
}
}
+773
View File
@@ -0,0 +1,773 @@
<?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 mod_data;
use context_module;
use moodle_url;
use core_component;
use stdClass;
/**
* Manager tests class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\manager
*/
class manager_test extends \advanced_testcase {
/**
* Test for static create methods.
*
* @covers ::create_from_instance
* @covers ::create_from_coursemodule
* @covers ::create_from_data_record
*/
public function test_create(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
$manager = manager::create_from_instance($activity);
$manageractivity = $manager->get_instance();
$this->assertEquals($activity->id, $manageractivity->id);
$managercm = $manager->get_coursemodule();
$this->assertEquals($cm->id, $managercm->id);
$managercontext = $manager->get_context();
$this->assertEquals($context->id, $managercontext->id);
$manager = manager::create_from_coursemodule($cm);
$manageractivity = $manager->get_instance();
$this->assertEquals($activity->id, $manageractivity->id);
$managercm = $manager->get_coursemodule();
$this->assertEquals($cm->id, $managercm->id);
$managercontext = $manager->get_context();
$this->assertEquals($context->id, $managercontext->id);
$datarecord = (object)[
'dataid' => $activity->id,
'id' => 0,
'userid' => 0,
'groupid' => 0,
'timecreated' => 0,
'timemodified' => 0,
'approved' => 0,
];
$manager = manager::create_from_data_record($datarecord);
$manageractivity = $manager->get_instance();
$this->assertEquals($activity->id, $manageractivity->id);
$managercm = $manager->get_coursemodule();
$this->assertEquals($cm->id, $managercm->id);
$managercontext = $manager->get_context();
$this->assertEquals($context->id, $managercontext->id);
}
/**
* Test set_module_viewed
* @covers ::set_module_viewed
*/
public function test_set_module_viewed(): void {
global $CFG;
$CFG->enablecompletion = 1;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$instance = $this->getDataGenerator()->create_module(
'data',
['course' => $course->id],
['completion' => 2, 'completionview' => 1]
);
$manager = manager::create_from_instance($instance);
$context = $manager->get_context();
$cm = $manager->get_coursemodule();
// Trigger and capture the event.
$sink = $this->redirectEvents();
$manager->set_module_viewed($course);
$events = $sink->get_events();
// 2 additional events thanks to completion.
$this->assertCount(3, $events);
$event = array_shift($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_data\event\course_module_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodleurl = new moodle_url('/mod/data/view.php', ['id' => $cm->id]);
$this->assertEquals($moodleurl, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
// Check completion status.
$completion = new \completion_info($course);
$completiondata = $completion->get_data($cm);
$this->assertEquals(1, $completiondata->completionstate);
}
/**
* Test set_template_viewed
* @covers ::set_template_viewed
*/
public function test_set_template_viewed(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$instance = $this->getDataGenerator()->create_module(
'data',
['course' => $course->id]
);
$manager = manager::create_from_instance($instance);
$context = $manager->get_context();
// Trigger and capture the event.
$sink = $this->redirectEvents();
$manager->set_template_viewed();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('mod_data\event\template_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodleurl = new moodle_url('/mod/data/templates.php', ['d' => $instance->id]);
$this->assertEquals($moodleurl, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
}
/**
* Test for has_records().
*
* @covers ::has_records
*/
public function test_has_records(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$data = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($data);
// Empty database should return false.
$this->assertFalse($manager->has_records());
// Create data record.
$datarecords = new \stdClass();
$datarecords->userid = '2';
$datarecords->dataid = $data->id;
$datarecords->id = $DB->insert_record('data_records', $datarecords);
// Database with records should return true.
$this->assertTrue($manager->has_records());
}
/**
* Test for has_fields().
*
* @covers ::has_fields
*/
public function test_has_fields(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Empty database should return false.
$this->assertFalse($manager->has_fields());
// Add a field to the activity.
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$fieldrecord = new \stdClass();
$fieldrecord->name = 'field1';
$fieldrecord->type = 'text';
$datagenerator->create_field($fieldrecord, $activity);
// Database with fields should return true.
$this->assertTrue($manager->has_fields());
}
/**
* Test for get_available_presets().
*
* @covers ::get_available_presets
*/
public function test_get_available_presets(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($user);
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
// Check available presets meet the datapreset plugins when there are no any preset saved by users.
$datapresetplugins = core_component::get_plugin_list('datapreset');
$manager = manager::create_from_coursemodule($cm);
$presets = $manager->get_available_presets();
$this->assertCount(count($datapresetplugins), $presets);
// Confirm that, at least, the "Image gallery" is one of them.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
// Login as admin and create some presets saved manually by users.
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$savedpresets = [];
for ($i = 1; $i <= 3; $i++) {
$preset = (object) [
'name' => 'Preset name ' . $i,
];
$plugingenerator->create_preset($activity, $preset);
$savedpresets[] = $preset;
}
$savedpresetsnames = array_map(function($preset) {
return $preset->name;
}, $savedpresets);
$this->setUser($user);
// Check available presets meet the datapreset plugins + presets saved manually by users.
$presets = $manager->get_available_presets();
$this->assertCount(count($datapresetplugins) + count($savedpresets), $presets);
// Confirm that, apart from the "Image gallery" preset, the ones created manually have been also returned.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
foreach ($savedpresets as $savedpreset) {
$this->assertContains($savedpreset->name, $namepresets);
}
// Check all the presets have the proper value for the isplugin attribute.
foreach ($presets as $preset) {
if (in_array($preset->name, $savedpresetsnames)) {
$this->assertFalse($preset->isplugin);
} else {
$this->assertTrue($preset->isplugin);
}
}
// Unassign the capability to the teacher role and check that only plugin presets are returned (because the saved presets
// have been created by admin).
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
unassign_capability('mod/data:viewalluserpresets', $teacherrole->id);
$presets = $manager->get_available_presets();
$this->assertCount(count($datapresetplugins), $presets);
// Confirm that, at least, the "Image gallery" is one of them.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
foreach ($savedpresets as $savedpreset) {
$this->assertNotContains($savedpreset->name, $namepresets);
}
// Create a preset with the current user and check that, although the viewalluserpresets is not assigned to the teacher
// role, the preset is returned because the teacher is the owner.
$savedpreset = (object) [
'name' => 'Preset created by teacher',
];
$plugingenerator->create_preset($activity, $savedpreset);
$presets = $manager->get_available_presets();
// The presets total is all the plugin presets plus the preset created by the teacher.
$this->assertCount(count($datapresetplugins) + 1, $presets);
// Confirm that, at least, the "Image gallery" is one of them.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
// Confirm that savedpresets are still not returned.
foreach ($savedpresets as $savedpreset) {
$this->assertNotContains($savedpreset->name, $namepresets);
}
// Confirm the new preset created by the teacher is returned too.
$this->assertContains('Preset created by teacher', $namepresets);
}
/**
* Test for get_available_plugin_presets().
*
* @covers ::get_available_plugin_presets
*/
public function test_get_available_plugin_presets(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Check available plugin presets meet the datapreset plugins.
$datapresetplugins = core_component::get_plugin_list('datapreset');
$manager = manager::create_from_instance($activity);
$presets = $manager->get_available_plugin_presets();
$this->assertCount(count($datapresetplugins), $presets);
// Confirm that, at least, the "Image gallery" is one of them.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
// Create a preset saved manually by users.
$savedpreset = (object) [
'name' => 'Preset name 1',
];
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$plugingenerator->create_preset($activity, $savedpreset);
// Check available plugin presets don't contain the preset saved manually.
$presets = $manager->get_available_plugin_presets();
$this->assertCount(count($datapresetplugins), $presets);
// Confirm that, at least, the "Image gallery" is one of them.
$namepresets = array_map(function($preset) {
return $preset->name;
}, $presets);
$this->assertContains('Image gallery', $namepresets);
// Confirm that the preset saved manually hasn't been returned.
$this->assertNotContains($savedpreset->name, $namepresets);
// Check all the presets have the proper value for the isplugin attribute.
foreach ($presets as $preset) {
$this->assertTrue($preset->isplugin);
}
}
/**
* Test for get_available_saved_presets().
*
* @covers ::get_available_saved_presets
*/
public function test_get_available_saved_presets(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($user);
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
// Check available saved presets is empty (because, for now, no user preset has been created).
$manager = manager::create_from_coursemodule($cm);
$presets = $manager->get_available_saved_presets();
$this->assertCount(0, $presets);
// Create some presets saved manually by the admin user.
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$savedpresets = [];
for ($i = 1; $i <= 3; $i++) {
$preset = (object) [
'name' => 'Preset name ' . $i,
];
$plugingenerator->create_preset($activity, $preset);
$savedpresets[] = $preset;
}
// Create one more preset saved manually by the teacher user.
$this->setUser($user);
$teacherpreset = (object) [
'name' => 'Preset created by teacher',
];
$plugingenerator->create_preset($activity, $teacherpreset);
$savedpresets[] = $teacherpreset;
$savedpresetsnames = array_map(function($preset) {
return $preset->name;
}, $savedpresets);
// Check available saved presets only contain presets saved manually by users.
$presets = $manager->get_available_saved_presets();
$this->assertCount(count($savedpresets), $presets);
// Confirm that it contains only the presets created manually.
foreach ($presets as $preset) {
$this->assertContains($preset->name, $savedpresetsnames);
$this->assertFalse($preset->isplugin);
}
// Unassign the mod/data:viewalluserpresets capability to the teacher role and check that saved presets are not returned.
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
unassign_capability('mod/data:viewalluserpresets', $teacherrole->id);
$presets = $manager->get_available_saved_presets();
$this->assertCount(1, $presets);
$preset = reset($presets);
$this->assertEquals($teacherpreset->name, $preset->name);
}
/**
* Test for can_view_preset().
*
* @covers ::can_view_preset
* @dataProvider can_view_preset_provider
* @param string $rolename the user role name
* @param bool $ownpreset if the preset belongs to the user
* @param bool|null $useridparam if the method should be called with a user id param
* @param bool $plugin if the preset is a plugin or not
* @param bool $expected the expected result
*/
public function test_can_view_preset(string $rolename, bool $ownpreset, ?bool $useridparam, bool $plugin, bool $expected): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, $rolename);
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
$manager = manager::create_from_coursemodule($cm);
// Create preset.
if ($ownpreset) {
$this->setUser($user);
} else {
$this->setAdminUser();
}
if ($plugin) {
$preset = preset::create_from_plugin($manager, 'imagegallery');
} else {
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$preset = $plugingenerator->create_preset($activity, (object)['name' => 'Preset name']);
}
// Setup user param.
if ($useridparam) {
// Login as a different user to validate the userid param is working.
$otheruser = $this->getDataGenerator()->create_user();
$this->setUser($otheruser);
$useridparam = $user->id;
} else {
$this->setUser($user);
}
$result = $manager->can_view_preset($preset, $useridparam);
$this->assertEquals($expected, $result);
}
/**
* Data provider for test_can_view_preset.
*
* @return array
*/
public function can_view_preset_provider(): array {
return [
// User presets.
'Teacher owned preset without user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => true,
'useridparam' => null,
'plugin' => false,
'expected' => true,
],
'Teacher owned preset with user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => true,
'useridparam' => true,
'plugin' => false,
'expected' => true,
],
'Teacher not owned preset without user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => false,
'useridparam' => null,
'plugin' => false,
'expected' => true,
],
'Teacher not owned preset with user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => false,
'useridparam' => true,
'plugin' => false,
'expected' => true,
],
'Student owned preset without user id param' => [
'rolename' => 'student',
'ownpreset' => true,
'useridparam' => null,
'plugin' => false,
'expected' => true,
],
'Student owned preset with user id param' => [
'rolename' => 'student',
'ownpreset' => true,
'useridparam' => true,
'plugin' => false,
'expected' => true,
],
'Student not owned preset without user id param' => [
'rolename' => 'student',
'ownpreset' => false,
'useridparam' => null,
'plugin' => false,
'expected' => false,
],
'Student not owned preset with user id param' => [
'rolename' => 'student',
'ownpreset' => false,
'useridparam' => true,
'plugin' => false,
'expected' => false,
],
// Plugin presets.
'Teacher plugin preset without user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => false,
'useridparam' => null,
'plugin' => true,
'expected' => true,
],
'Teacher plugin preset with user id param' => [
'rolename' => 'editingteacher',
'ownpreset' => false,
'useridparam' => true,
'plugin' => true,
'expected' => true,
],
'Student plugin preset without user id param' => [
'rolename' => 'student',
'ownpreset' => false,
'useridparam' => null,
'plugin' => true,
'expected' => true,
],
'Student plugin preset with user id param' => [
'rolename' => 'student',
'ownpreset' => false,
'useridparam' => true,
'plugin' => true,
'expected' => true,
],
];
}
/**
* Test for can_export_entries().
*
* @covers ::can_export_entries
*/
public function test_can_export_entries(): void {
global $DB;
$this->resetAfterTest();
// Create course with activity and enrol users.
$course = $this->getDataGenerator()->create_course();
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$cm = get_coursemodule_from_id(manager::MODULE, $activity->cmid, 0, false, MUST_EXIST);
$manager = manager::create_from_coursemodule($cm);
// Add a field.
/** @var \mod_data_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$fieldrecord = (object)[
'name' => 'myfield',
'type' => 'text',
];
$field = $generator->create_field($fieldrecord, $activity);
// Teacher with default capabilities can export entries.
$this->setUser($teacher);
$result = $manager->can_export_entries();
$this->assertEquals(true, $result);
// Teacher without exportallentries can still export entries.
unassign_capability('mod/data:exportallentries', $teacherrole->id);
$result = $manager->can_export_entries();
$this->assertEquals(true, $result);
// Teacher without exportallentries and exportentry can't export entries (unless they have created some entries).
unassign_capability('mod/data:exportentry', $teacherrole->id);
$result = $manager->can_export_entries();
$this->assertEquals(false, $result);
$generator->create_entry(
$activity,
[$field->field->id => 'Example entry'],
);
$result = $manager->can_export_entries();
$this->assertEquals(true, $result);
// Student without entries can't export.
$this->setUser($student);
$result = $manager->can_export_entries();
$this->assertEquals(false, $result);
// However, student who has created any entry, can export.
$generator->create_entry(
$activity,
[$field->field->id => 'Another example entry'],
);
$this->setUser($student);
$result = $manager->can_export_entries();
$this->assertEquals(true, $result);
}
/*
* Test reset_all_templates.
*
* @covers ::reset_all_templates
*/
public function test_reset_all_templates(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$instance = $this->getDataGenerator()->create_module(
'data',
['course' => $course->id]
);
$manager = manager::create_from_instance($instance);
// Create some initial templates.
$initialtemplates = new stdClass();
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
$initialtemplates->$templatename = "Initial $templatename";
}
$manager->update_templates($initialtemplates);
$instance = $manager->get_instance();
$record = $DB->get_record('data', ['id' => $instance->id]);
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
$this->assertEquals($initialtemplates->$templatename, $instance->$templatename);
$this->assertEquals($initialtemplates->$templatename, $record->$templatename);
}
// Reset all templates.
$result = $manager->reset_all_templates();
$this->assertTrue($result);
$instance = $manager->get_instance();
$record = $DB->get_record('data', ['id' => $instance->id]);
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
$this->assertEquals('', $instance->$templatename);
$this->assertEquals('', $record->$templatename);
}
}
/**
* Test reset_template.
*
* @covers ::reset_template
* @dataProvider reset_template_provider
* @param string $templatetoreset the template to reset
* @param string[] $expected the expected templates to be reset
*/
public function test_reset_template(string $templatetoreset, array $expected): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$instance = $this->getDataGenerator()->create_module(
'data',
['course' => $course->id]
);
$manager = manager::create_from_instance($instance);
// Create some initial templates.
$initialtemplates = new stdClass();
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
$initialtemplates->$templatename = "Initial $templatename";
}
$manager->update_templates($initialtemplates);
$instance = $manager->get_instance();
$record = $DB->get_record('data', ['id' => $instance->id]);
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
$this->assertEquals($initialtemplates->$templatename, $instance->$templatename);
$this->assertEquals($initialtemplates->$templatename, $record->$templatename);
}
// Reset template.
$result = $manager->reset_template($templatetoreset);
$this->assertTrue($result);
$instance = $manager->get_instance();
$record = $DB->get_record('data', ['id' => $instance->id]);
foreach (manager::TEMPLATES_LIST as $templatename => $unused) {
if (in_array($templatename, $expected)) {
$this->assertEquals('', $instance->$templatename);
$this->assertEquals('', $record->$templatename);
} else {
$this->assertEquals($initialtemplates->$templatename, $instance->$templatename);
$this->assertEquals($initialtemplates->$templatename, $record->$templatename);
}
}
}
/**
* Data provider for test_reset_templatet.
*
* @return array
*/
public function reset_template_provider(): array {
return [
// User presets.
'listtemplate' => [
'templatename' => 'listtemplate',
'expected' => ['listtemplate', 'listtemplateheader', 'listtemplatefooter'],
],
'singletemplate' => [
'templatename' => 'singletemplate',
'expected' => ['singletemplate'],
],
'asearchtemplate' => [
'templatename' => 'asearchtemplate',
'expected' => ['asearchtemplate'],
],
'addtemplate' => [
'templatename' => 'addtemplate',
'expected' => ['addtemplate'],
],
'rsstemplate' => [
'templatename' => 'rsstemplate',
'expected' => ['rsstemplate', 'rsstitletemplate'],
],
'csstemplate' => [
'templatename' => 'csstemplate',
'expected' => ['csstemplate'],
],
'jstemplate' => [
'templatename' => 'jstemplate',
'expected' => ['jstemplate'],
],
];
}
}
+459
View File
@@ -0,0 +1,459 @@
<?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 mod_data;
use mod_data\local\importer\preset_existing_importer;
use mod_data\local\importer\preset_importer;
/**
* Preset importer tests class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\local\importer\preset_importer
*/
class preset_importer_test extends \advanced_testcase {
/**
* Data provider for build providers for test_needs_mapping and test_set_affected_fields.
*
* @return array[]
*/
public function preset_importer_provider(): array {
// Image gallery preset is: ['title' => 'text', 'description' => 'textarea', 'image' => 'picture'];
$titlefield = new \stdClass();
$titlefield->name = 'title';
$titlefield->type = 'text';
$descfield = new \stdClass();
$descfield->name = 'description';
$descfield->type = 'textarea';
$imagefield = new \stdClass();
$imagefield->name = 'image';
$imagefield->type = 'picture';
$difffield = new \stdClass();
$difffield->name = 'title';
$difffield->type = 'textarea';
$newfield = new \stdClass();
$newfield->name = 'number';
$newfield->type = 'number';
return [
'Empty database / Empty importer' => [
'currentfields' => [],
'newfields' => [],
'pluginname' => '',
],
'Empty database / Importer with fields' => [
'currentfields' => [],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Database with fields / Empty importer' => [
'currentfields' => [$titlefield, $descfield, $imagefield],
'newfields' => [],
'pluginname' => '',
],
'Same fields' => [
'currentfields' => [$titlefield, $descfield, $imagefield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to create' => [
'currentfields' => [$titlefield, $descfield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to remove' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to update' => [
'currentfields' => [$difffield, $descfield, $imagefield],
'newfields' => [$titlefield, $descfield, $imagefield],
'pluginname' => 'imagegallery',
],
'Fields to create, remove and update' => [
'currentfields' => [$titlefield, $descfield, $imagefield, $difffield],
'newfields' => [$titlefield, $descfield, $newfield],
'pluginname' => '',
],
];
}
/**
* Data provider for needs_mapping().
*
* @return array[]
*/
public function needs_mapping_provider(): array {
$basedprovider = $this->preset_importer_provider();
$basedprovider['Empty database / Empty importer']['needsmapping'] = false;
$basedprovider['Empty database / Importer with fields']['needsmapping'] = false;
$basedprovider['Database with fields / Empty importer']['needsmapping'] = true;
$basedprovider['Same fields']['needsmapping'] = false;
$basedprovider['Fields to create']['needsmapping'] = true;
$basedprovider['Fields to remove']['needsmapping'] = true;
$basedprovider['Fields to update']['needsmapping'] = true;
$basedprovider['Fields to create, remove and update']['needsmapping'] = true;
return $basedprovider;
}
/**
* Test for needs_mapping method.
*
* @dataProvider needs_mapping_provider
* @covers ::needs_mapping
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param bool $expectedresult Expected exception.
*/
public function test_needs_mapping(
array $currentfields,
array $newfields,
string $pluginname,
bool $expectedresult
): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals($savedimporter->needs_mapping(), $expectedresult);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$this->assertEquals($pluginimporter->needs_mapping(), $expectedresult);
}
}
/**
* Data provider for test_set_affected_fields().
*
* @return array[]
*/
public function set_affected_provider(): array {
$basedprovider = $this->preset_importer_provider();
$basedprovider['Empty database / Empty importer']['fieldstocreate'] = 0;
$basedprovider['Empty database / Empty importer']['fieldstoremove'] = 0;
$basedprovider['Empty database / Empty importer']['fieldstoupdate'] = 0;
$basedprovider['Empty database / Importer with fields']['fieldstocreate'] = 3;
$basedprovider['Empty database / Importer with fields']['fieldstoremove'] = 0;
$basedprovider['Empty database / Importer with fields']['fieldstoupdate'] = 0;
$basedprovider['Database with fields / Empty importer']['fieldstocreate'] = 0;
$basedprovider['Database with fields / Empty importer']['fieldstoremove'] = 3;
$basedprovider['Database with fields / Empty importer']['fieldstoupdate'] = 0;
$basedprovider['Same fields']['fieldstocreate'] = 0;
$basedprovider['Same fields']['fieldstoremove'] = 0;
$basedprovider['Same fields']['fieldstoupdate'] = 3;
$basedprovider['Fields to create']['fieldstocreate'] = 1;
$basedprovider['Fields to create']['fieldstoremove'] = 0;
$basedprovider['Fields to create']['fieldstoupdate'] = 2;
$basedprovider['Fields to remove']['fieldstocreate'] = 0;
$basedprovider['Fields to remove']['fieldstoremove'] = 1;
$basedprovider['Fields to remove']['fieldstoupdate'] = 3;
$basedprovider['Fields to update']['fieldstocreate'] = 1;
$basedprovider['Fields to update']['fieldstoremove'] = 1;
$basedprovider['Fields to update']['fieldstoupdate'] = 2;
$basedprovider['Fields to create, remove and update']['fieldstocreate'] = 1;
$basedprovider['Fields to create, remove and update']['fieldstoremove'] = 2;
$basedprovider['Fields to create, remove and update']['fieldstoupdate'] = 2;
return $basedprovider;
}
/**
* Test for set_affected_fields method.
*
* @dataProvider set_affected_provider
* @covers ::set_affected_fields
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param int $fieldstocreate Expected number of fields on $fieldstocreate.
* @param int $fieldstoremove Expected number of fields on $fieldstoremove.
* @param int $fieldstoupdate Expected number of fields on $fieldstoupdate.
*/
public function test_set_affected_fields(
array $currentfields,
array $newfields,
string $pluginname,
int $fieldstocreate,
int $fieldstoremove,
int $fieldstoupdate
): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals(count($savedimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($savedimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($savedimporter->fieldstoupdate), $fieldstoupdate);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$this->assertEquals(count($pluginimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($pluginimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($pluginimporter->fieldstoupdate), $fieldstoupdate);
}
}
/**
* Test for get_mapping_information method.
*
* @dataProvider set_affected_provider
* @covers ::get_mapping_information
*
* @param array $currentfields Fields of the current activity.
* @param array $newfields Fields to be imported.
* @param string $pluginname The plugin preset to be imported.
* @param int $fieldstocreate Expected number of fields on $fieldstocreate.
* @param int $fieldstoremove Expected number of fields on $fieldstoremove.
* @param int $fieldstoupdate Expected number of fields on $fieldstoupdate.
*/
public function test_get_mapping_information(
array $currentfields,
array $newfields,
string $pluginname,
int $fieldstocreate,
int $fieldstoremove,
int $fieldstoupdate
): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($currentfields as $field) {
$plugingenerator->create_field($field, $activity);
}
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Add current fields to the activity.
foreach ($newfields as $field) {
$plugingenerator->create_field($field, $presetactivity);
}
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$information = $savedimporter->get_mapping_information();
$this->assertEquals($savedimporter->needs_mapping(), $information['needsmapping']);
$this->assertEquals(count($savedimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($savedimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($savedimporter->fieldstoupdate), $fieldstoupdate);
// Create presets and importers.
if ($pluginname) {
$plugin = preset::create_from_plugin(null, $pluginname);
$pluginimporter = new preset_existing_importer($manager, '/' . $pluginname);
$information = $pluginimporter->get_mapping_information();
$this->assertEquals($pluginimporter->needs_mapping(), $information['needsmapping']);
$this->assertEquals(count($pluginimporter->fieldstoremove), $fieldstoremove);
$this->assertEquals(count($pluginimporter->fieldstocreate), $fieldstocreate);
$this->assertEquals(count($pluginimporter->fieldstoupdate), $fieldstoupdate);
}
}
/**
* Data provider for get_field_names().
*
* @return array[]
*/
public function get_field_names_provider(): array {
return [
'Empty list' => [
'fields' => [],
'expected' => '',
],
'List with one field' => [
'fields' => ['fieldname' => 'text'],
'expected' => 'fieldname',
],
'List of fields with same type' => [
'fields' => ['textfield' => 'text', 'other' => 'text'],
'expected' => 'textfield, other',
],
'List of fields with different type' => [
'fields' => ['textfield' => 'text', 'number' => 'number'],
'expected' => 'textfield, number',
],
];
}
/**
* Test for get_field_names method.
*
* @dataProvider get_field_names_provider
* @covers ::get_field_names
*
* @param array $fields List of fields to get the names from.
* @param string $expected The list of field names expected.
*/
public function test_get_field_names(array $fields, string $expected): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
foreach ($fields as $fieldname => $fieldtype) {
$newfield = new \stdClass();
$newfield->name = $fieldname;
$newfield->type = $fieldtype;
$createdfield = $plugingenerator->create_field($newfield, $presetactivity);
}
$manager = manager::create_from_instance($presetactivity);
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
$savedimporter = new preset_existing_importer($manager, $USER->id . '/Testing preset name');
$this->assertEquals($expected, $savedimporter->get_field_names($manager->get_field_records()));
}
/**
* Test for create_from_plugin_or_directory creation static method.
*
* @covers ::create_from_plugin_or_directory
*
*/
public function test_create_from_plugin_or_directory(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
$presetactivity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$saved = $plugingenerator->create_preset($presetactivity, $record);
// A plugin preset returns an instance of preset_existing_importer.
$preset = preset_importer::create_from_plugin_or_directory($manager, '/imagegallery');
$this->assertInstanceOf('\mod_data\local\importer\preset_existing_importer', $preset);
// A saved preset returns an instance of preset_existing_importer.
$preset = preset_importer::create_from_plugin_or_directory($manager, $USER->id . '/Testing preset name');
$this->assertInstanceOf('\mod_data\local\importer\preset_existing_importer', $preset);
// An empty preset name throws an exception.
$this->expectException('moodle_exception');
try {
preset_importer::create_from_plugin_or_directory($manager, '');
} finally {
// A non-existing preset name throws an exception.
$this->expectException('moodle_exception');
preset_importer::create_from_plugin_or_directory($manager, $USER->id . '/Non-existing');
}
}
}
+968
View File
@@ -0,0 +1,968 @@
<?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 mod_data;
use file_archive;
use stdClass;
use zip_archive;
/**
* Preset tests class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\preset
*/
class preset_test extends \advanced_testcase {
/**
* Test for static create_from_plugin method.
*
* @covers ::create_from_plugin
*/
public function test_create_from_plugin(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Check create_from_plugin is working as expected when an existing plugin is given.
$pluginname = 'imagegallery';
$result = preset::create_from_plugin(null, $pluginname);
$this->assertTrue($result->isplugin);
$this->assertEquals(get_string('modulename', "datapreset_$pluginname"), $result->name);
$this->assertEquals($pluginname, $result->shortname);
$this->assertEquals(get_string('modulename_help', "datapreset_$pluginname"), $result->description);
$this->assertEmpty($result->get_userid());
$this->assertEmpty($result->storedfile);
$this->assertNull($result->get_path());
// Check create_from_plugin is working as expected when an unexisting plugin is given.
$pluginname = 'unexisting';
$result = preset::create_from_plugin(null, $pluginname);
$this->assertNull($result);
}
/**
* Test for static create_from_storedfile method.
*
* @covers ::create_from_storedfile
*/
public function test_create_from_storedfile(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$plugingenerator->create_preset($activity, $record);
$savedpresets = $manager->get_available_saved_presets();
$savedpreset = reset($savedpresets);
// Check create_from_storedfile is working as expected with a valid preset file.
$result = preset::create_from_storedfile($manager, $savedpreset->storedfile);
$this->assertFalse($result->isplugin);
$this->assertEquals($record->name, $result->name);
$this->assertEquals($record->name, $result->shortname);
$this->assertEquals($record->description, $result->description);
$this->assertEquals($savedpreset->storedfile->get_userid(), $result->get_userid());
$this->assertNotEmpty($result->storedfile);
$this->assertEquals('/' . $record->name . '/', $result->get_path());
// Check create_from_storedfile is not creating a preset object when an invalid file is given.
$draftid = file_get_unused_draft_itemid();
$filerecord = [
'component' => 'user',
'filearea' => 'draft',
'contextid' => \context_user::instance($USER->id)->id,
'itemid' => $draftid,
'filename' => 'preset.xml',
'filepath' => '/'
];
$fs = get_file_storage();
$file = $fs->create_file_from_string($filerecord, 'This is the file content');
$result = preset::create_from_storedfile($manager, $file);
$this->assertNull($result);
}
/**
* Test for static create_from_instance method.
*
* @covers ::create_from_instance
*/
public function test_create_from_instance(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$plugingenerator->create_preset($activity, $record);
$savedpresets = $manager->get_available_saved_presets();
$savedpreset = reset($savedpresets);
// Check create_from_instance is working as expected when a preset with this name exists.
$result = preset::create_from_instance($manager, $record->name, $record->description);
$this->assertFalse($result->isplugin);
$this->assertEquals($record->name, $result->name);
$this->assertEquals($record->name, $result->shortname);
$this->assertEquals($record->description, $result->description);
$this->assertEquals($savedpreset->storedfile->get_userid(), $result->get_userid());
$this->assertNotEmpty($result->storedfile);
$this->assertEquals('/' . $record->name . '/', $result->get_path());
// Check create_from_instance is working as expected when there is no preset with the given name.
$presetname = 'Unexisting preset';
$presetdescription = 'This is the description for the unexisting preset';
$result = preset::create_from_instance($manager, $presetname, $presetdescription);
$this->assertFalse($result->isplugin);
$this->assertEquals($presetname, $result->name);
$this->assertEquals($presetname, $result->shortname);
$this->assertEquals($presetdescription, $result->description);
$this->assertEmpty($result->get_userid());
$this->assertEmpty($result->storedfile);
$this->assertEquals('/' . $presetname . '/', $result->get_path());
}
/**
* Test for static create_from_fullname method.
*
* @covers ::create_from_fullname
*/
public function test_create_from_fullname(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$savedpreset = $plugingenerator->create_preset($activity, $record);
// Check instantiate from plugin.
$pluginname = 'imagegallery';
$fullname = '0/imagegallery';
$result = preset::create_from_fullname($manager, $fullname);
$this->assertTrue($result->isplugin);
$this->assertEquals(get_string('modulename', "datapreset_$pluginname"), $result->name);
$this->assertEquals($pluginname, $result->shortname);
$this->assertEquals(get_string('modulename_help', "datapreset_$pluginname"), $result->description);
$this->assertEmpty($result->get_userid());
$this->assertEmpty($result->storedfile);
$this->assertNull($result->get_path());
// Check instantiate from user preset
// Check create_from_instance is working as expected when a preset with this name exists.
$fullname = $savedpreset->get_userid() . '/' . $savedpreset->name;
$result = preset::create_from_fullname($manager, $fullname);
$this->assertFalse($result->isplugin);
$this->assertEquals($savedpreset->name, $result->name);
$this->assertEquals($savedpreset->shortname, $result->shortname);
$this->assertEquals($savedpreset->description, $savedpreset->description);
$this->assertEquals($savedpreset->storedfile->get_userid(), $result->get_userid());
$this->assertNotEmpty($result->storedfile);
$this->assertEquals('/' . $savedpreset->name . '/', $result->get_path());
}
/**
* Test for the save a preset method when the preset hasn't been saved before.
*
* @covers ::save
*/
public function test_save_new_preset(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Save should return false when trying to save a plugin preset.
$preset = preset::create_from_plugin(null, 'imagegallery');
$result = $preset->save();
$this->assertFalse($result);
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$plugingenerator->create_preset($activity, $record);
// Save should return false when trying to save an existing saved preset.
$preset = preset::create_from_instance($manager, $record->name, $record->description);
$result = $preset->save();
$this->assertFalse($result);
// The preset should be saved when it's new and there is no any other having the same name.
$savedpresets = $manager->get_available_saved_presets();
$this->assertCount(1, $savedpresets);
$presetname = 'New preset';
$presetdescription = 'This is the description for the new preset';
$preset = preset::create_from_instance($manager, $presetname, $presetdescription);
$result = $preset->save();
$this->assertTrue($result);
// Check the preset has been created.
$savedpresets = $manager->get_available_saved_presets();
$this->assertCount(2, $savedpresets);
$savedpresetsnames = array_map(function($preset) {
return $preset->name;
}, $savedpresets);
$this->assertContains($presetname, $savedpresetsnames);
}
/**
* Test for the save a preset method when is an existing preset that has been saved before.
*
* @covers ::save
*/
public function test_save_existing_preset(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$oldpresetname = $record->name;
$plugingenerator->create_preset($activity, $record);
// Save should return false when trying to save an existing preset.
$preset = preset::create_from_instance($manager, $record->name, $record->description);
$result = $preset->save();
$this->assertFalse($result);
// Check no new preset has been created.
$this->assertCount(1, $manager->get_available_saved_presets());
// Save should overwrite existing preset if name or description have changed.
$preset->name = 'New preset name';
$preset->description = 'New preset description';
$result = $preset->save();
$this->assertTrue($result);
// Check the preset files have been renamed.
$presetfiles = array_merge(array_values(manager::TEMPLATES_LIST), ['preset.xml', '.']);
foreach ($presetfiles as $templatefile) {
$file = preset::get_file($preset->get_path(), $templatefile);
$this->assertNotNull($file);
}
// Check old preset files have been removed.
$oldpath = "{$oldpresetname}";
foreach ($presetfiles as $templatefile) {
$file = preset::get_file($oldpath, $templatefile);
$this->assertNull($file);
}
// Check no new preset has been created.
$savedpresets = $manager->get_available_saved_presets();
$this->assertCount(1, $savedpresets);
// Check the preset has the expected values.
$savedpreset = reset($savedpresets);
$this->assertEquals($preset->name, $savedpreset->name);
$this->assertEquals($preset->description, $savedpreset->description);
$this->assertNotEmpty($preset->storedfile);
// Check the storedfile has been updated properly.
$this->assertEquals($preset->name, trim($savedpreset->storedfile->get_filepath(), '/'));
// Create another saved preset with empty description.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset 2',
];
$plugingenerator->create_preset($activity, $record);
$this->assertCount(2, $manager->get_available_saved_presets());
// Description should be saved too when it was empty in the original preset and a new value is assigned to it.
$preset = preset::create_from_instance($manager, $record->name);
$preset->description = 'New preset description';
$result = $preset->save();
$this->assertTrue($result);
$savedpresets = $manager->get_available_saved_presets();
$this->assertCount(2, $savedpresets);
foreach ($savedpresets as $savedpreset) {
if ($savedpreset->name == $record->name) {
$this->assertEquals($preset->description, $savedpreset->description);
}
}
}
/**
* Test for the export a preset method.
*
* @covers ::export
*/
public function test_export(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Export should return empty string when trying to export a plugin preset.
$preset = preset::create_from_plugin(null, 'imagegallery');
$result = $preset->export();
$this->assertEmpty($result);
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
// For now, default templates are not created automatically. This will be changed in MDL-75234.
foreach (manager::TEMPLATES_LIST as $templatename => $notused) {
data_generate_default_template($activity, $templatename);
}
$preset = preset::create_from_instance($manager, $activity->name);
$result = $preset->export();
$presetfilenames = array_merge(array_values(manager::TEMPLATES_LIST), ['preset.xml']);
$ziparchive = new zip_archive();
$ziparchive->open($result, file_archive::OPEN);
$files = $ziparchive->list_files();
foreach ($files as $file) {
$this->assertContains($file->pathname, $presetfilenames);
// Check the file is not empty (except CSS, JS and listtemplateheader/footer files which are empty by default).
$extension = pathinfo($file->pathname, PATHINFO_EXTENSION);
$ishtmlorxmlfile = in_array($extension, ['html', 'xml']);
$expectedemptyfiles = array_intersect_key(manager::TEMPLATES_LIST, array_flip([
'listtemplateheader',
'listtemplatefooter',
'rsstitletemplate',
]));
if ($ishtmlorxmlfile && !in_array($file->pathname, $expectedemptyfiles)) {
$this->assertGreaterThan(0, $file->size);
} else {
$this->assertEquals(0, $file->size);
}
}
$ziparchive->close();
}
/**
* Test for get_userid().
*
* @covers ::get_userid
*/
public function test_get_userid(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$this->setUser($user);
// Check userid is null for plugin preset.
$manager = manager::create_from_instance($activity);
$pluginpresets = $manager->get_available_plugin_presets();
$pluginpreset = reset($pluginpresets);
$this->assertNull($pluginpreset->get_userid());
// Check userid meets the user that has created the preset when it's a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$savedpreset = (object) [
'name' => 'Preset created by teacher',
];
$plugingenerator->create_preset($activity, $savedpreset);
$savedpresets = $manager->get_available_saved_presets();
$savedpreset = reset($savedpresets);
$this->assertEquals($user->id, $savedpreset->get_userid());
// Check userid is null when preset hasn't any file associated.
$preset = preset::create_from_instance($manager, 'Unexisting preset');
$this->assertNull($preset->get_userid());
}
/**
* Test for get_path().
*
* @covers ::get_path
*/
public function test_get_path(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
// Check path is null for plugin preset.
$manager = manager::create_from_instance($activity);
$pluginpresets = $manager->get_available_plugin_presets();
$pluginpreset = reset($pluginpresets);
$this->assertNull($pluginpreset->get_path());
// Check path meets expected value when it's a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$savedpreset = (object) [
'name' => 'Saved preset',
];
$plugingenerator->create_preset($activity, $savedpreset);
$savedpresets = $manager->get_available_saved_presets();
$savedpreset = reset($savedpresets);
$this->assertEquals("/{$savedpreset->name}/", $savedpreset->get_path());
// Check path is /presetname/ when preset hasn't any file associated.
$presetname = 'Unexisting preset';
$preset = preset::create_from_instance($manager, $presetname);
$this->assertEquals("/{$presetname}/", $preset->get_path());
}
/**
* Test for is_directory_a_preset().
*
* @dataProvider is_directory_a_preset_provider
* @covers ::is_directory_a_preset
* @param string $directory
* @param bool $expected
*/
public function test_is_directory_a_preset(string $directory, bool $expected): void {
$this->resetAfterTest();
$this->setAdminUser();
$result = preset::is_directory_a_preset($directory);
$this->assertEquals($expected, $result);
}
/**
* Data provider for test_is_directory_a_preset().
*
* @return array
*/
public function is_directory_a_preset_provider(): array {
global $CFG;
return [
'Valid preset directory' => [
'directory' => $CFG->dirroot . '/mod/data/preset/imagegallery',
'expected' => true,
],
'Invalid preset directory' => [
'directory' => $CFG->dirroot . '/mod/data/field/checkbox',
'expected' => false,
],
'Unexisting preset directory' => [
'directory' => $CFG->dirroot . 'unexistingdirectory',
'expected' => false,
],
];
}
/**
* Test for get_name_from_plugin().
*
* @covers ::get_name_from_plugin
*/
public function test_get_name_from_plugin(): void {
$this->resetAfterTest();
$this->setAdminUser();
// The expected name for plugins with modulename in lang is this value.
$name = preset::get_name_from_plugin('imagegallery');
$this->assertEquals('Image gallery', $name);
// However, if the plugin doesn't exist or the modulename is not defined, the preset shortname will be returned.
$presetshortname = 'nonexistingpreset';
$name = preset::get_name_from_plugin($presetshortname);
$this->assertEquals($presetshortname, $name);
}
/**
* Test for get_description_from_plugin().
*
* @covers ::get_description_from_plugin
*/
public function test_get_description_from_plugin(): void {
$this->resetAfterTest();
$this->setAdminUser();
// The expected name for plugins with modulename in lang is this value.
$description = preset::get_description_from_plugin('imagegallery');
$this->assertEquals('Use this preset to collect images.', $description);
// However, if the plugin doesn't exist or the modulename is not defined, empty string will be returned.
$presetshortname = 'nonexistingpreset';
$description = preset::get_description_from_plugin($presetshortname);
$this->assertEmpty($description);
}
/**
* Test for generate_preset_xml().
*
* @covers ::generate_preset_xml
* @dataProvider generate_preset_xml_provider
* @param array $params activity config settings
* @param string|null $description preset description
*/
public function test_generate_preset_xml(array $params, ?string $description): void {
$this->resetAfterTest();
$this->setAdminUser();
// Make accessible the method.
$reflection = new \ReflectionClass(preset::class);
$method = $reflection->getMethod('generate_preset_xml');
// The method should return empty string when trying to generate preset.xml for a plugin preset.
$preset = preset::create_from_plugin(null, 'imagegallery');
$result = $method->invokeArgs($preset, []);
$this->assertEmpty($result);
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, array_merge(['course' => $course], $params));
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
$manager = manager::create_from_instance($activity);
$preset = preset::create_from_instance($manager, $activity->name, $description);
// Call the generate_preset_xml method.
$result = $method->invokeArgs($preset, []);
// Check is a valid XML.
$parsedxml = simplexml_load_string($result);
// Check the description has the expected value.
$this->assertEquals($description, strval($parsedxml->description));
// Check settings have the expected values.
foreach ($params as $paramname => $paramvalue) {
$this->assertEquals($paramvalue, strval($parsedxml->settings->{$paramname}));
}
// Check field have the expected values.
$this->assertEquals($fieldrecord->name, strval($parsedxml->field->name));
$this->assertEquals($fieldrecord->type, strval($parsedxml->field->type));
}
/**
* Data provider for generate_preset_xml().
*
* @return array
*/
public function generate_preset_xml_provider(): array {
return [
'Generate preset.xml with the default params and empty description' => [
'params' => [],
'description' => null,
],
'Generate preset.xml with a description but the default params' => [
'params' => [],
'description' => 'This is a description',
],
'Generate preset.xml with empty description but changing some params' => [
'params' => [
'requiredentries' => 2,
'approval' => 1,
],
'description' => null,
],
'Generate preset.xml with a description and changing some params' => [
'params' => [
'maxentries' => 5,
'manageapproved' => 0,
],
'description' => 'This is a description',
],
];
}
/**
* Test for get_file().
*
* @covers ::get_file
*/
public function test_get_file(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
$presetname = 'Saved preset';
// Check file doesn't exist if the preset hasn't been saved yet.
$preset = preset::create_from_instance($manager, $presetname);
$file = preset::get_file($preset->get_path(), 'preset.xml');
$this->assertNull($file);
// Check file is not empty when there is a saved preset with this name.
$preset->save();
$file = preset::get_file($preset->get_path(), 'preset.xml');
$this->assertNotNull($file);
$this->assertStringContainsString($presetname, $file->get_filepath());
$this->assertEquals('preset.xml', $file->get_filename());
// Check invalid preset file name doesn't exist.
$file = preset::get_file($preset->get_path(), 'unexistingpreset.xml');
$this->assertNull($file);
}
/**
* Test for can_manage().
*
* @covers ::can_manage
*/
public function test_can_manage(): void {
$this->resetAfterTest();
// Create course, database activity and users.
$course = $this->getDataGenerator()->create_course();
$data = $this->getDataGenerator()->create_module('data', ['course' => $course->id]);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$manager = manager::create_from_instance($data);
$preset1name = 'Admin preset';
$preset2name = 'Teacher preset';
// Create a saved preset by admin.
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => $preset1name,
'description' => 'Testing preset description',
];
$adminpreset = $plugingenerator->create_preset($data, $record);
// Create a saved preset by teacher.
$this->setUser($teacher);
$record = (object) [
'name' => $preset2name,
'description' => 'Testing preset description',
];
$teacherpreset = $plugingenerator->create_preset($data, $record);
// Plugins can't be deleted.
$pluginpresets = manager::get_available_plugin_presets();
$pluginpreset = reset($pluginpresets);
$this->assertFalse($pluginpreset->can_manage());
// Admin can delete all saved presets.
$this->setAdminUser();
$this->assertTrue($adminpreset->can_manage());
$this->assertTrue($teacherpreset->can_manage());
// Teacher can delete their own preset only.
$this->setUser($teacher);
$this->assertFalse($adminpreset->can_manage());
$this->assertTrue($teacherpreset->can_manage());
// Student can't delete any of the presets.
$this->setUser($student);
$this->assertFalse($adminpreset->can_manage());
$this->assertFalse($teacherpreset->can_manage());
}
/**
* Test for delete().
*
* @covers ::delete
*/
public function test_delete(): void {
$this->resetAfterTest();
// Create course, database activity and users.
$course = $this->getDataGenerator()->create_course();
$data = $this->getDataGenerator()->create_module('data', ['course' => $course->id]);
$manager = manager::create_from_instance($data);
$presetname = 'Admin preset';
// Create a saved preset by admin.
$this->setAdminUser();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => $presetname,
'description' => 'Testing preset description',
];
$adminpreset = $plugingenerator->create_preset($data, $record);
$initialpresets = $manager->get_available_presets();
// Plugins can't be deleted.
$pluginpresets = manager::get_available_plugin_presets();
$pluginpreset = reset($pluginpresets);
$result = $pluginpreset->delete();
$currentpluginpresets = manager::get_available_plugin_presets();
$this->assertEquals(count($pluginpresets), count($currentpluginpresets));
$result = $adminpreset->delete();
$this->assertTrue($result);
// After deleting the preset, there is no file linked.
$adminpreset = preset::create_from_instance($manager, $presetname);
$this->assertEmpty($adminpreset->storedfile);
// Check the preset has been deleted.
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets) - 1, count($currentpresets));
// The behavior of trying to delete a preset twice.
$result = $adminpreset->delete();
$this->assertFalse($result);
// Check the preset has not been deleted.
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets) - 1, count($currentpresets));
$emptypreset = preset::create_from_instance($manager, $presetname);
// The behavior of deleting an empty preset.
$result = $emptypreset->delete();
$this->assertFalse($result);
// Check the preset has not been deleted.
$currentpresets = $manager->get_available_presets();
$this->assertEquals(count($initialpresets) - 1, count($currentpresets));
}
/**
* Test for the get_fields method.
*
* @covers ::get_fields
*/
public function test_get_fields(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$preset = $plugingenerator->create_preset($activity, $record);
// Check regular fields.
$fields = $preset->get_fields();
$this->assertCount(1, $fields);
$this->assertArrayHasKey('field-1', $fields);
$field = $fields['field-1'];
$this->assertEquals('text', $field->type);
$this->assertEquals('field-1', $field->get_name());
$this->assertEquals(false, $field->get_preview());
// Check preview fields.
$savedpresets = $manager->get_available_saved_presets();
$preset = reset($savedpresets);
$fields = $preset->get_fields(true);
$this->assertCount(1, $fields);
$this->assertArrayHasKey('field-1', $fields);
$field = $fields['field-1'];
$this->assertEquals('text', $field->type);
$this->assertEquals('field-1', $field->get_name());
$this->assertEquals(true, $field->get_preview());
}
/**
* Test for the get_sample_entries method.
*
* @covers ::get_sample_entries
*/
public function test_get_sample_entries(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Create a course and a database activity.
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Add a field to the activity.
$fieldrecord = new stdClass();
$fieldrecord->name = 'field-1';
$fieldrecord->type = 'text';
$datagenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$datagenerator->create_field($fieldrecord, $activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$preset = $plugingenerator->create_preset($activity, $record);
$entries = $preset->get_sample_entries(3);
$this->assertCount(3, $entries);
foreach ($entries as $entry) {
$this->assertEquals($user->id, $entry->userid);
$this->assertEquals($user->email, $entry->email);
$this->assertEquals($user->firstname, $entry->firstname);
$this->assertEquals($user->lastname, $entry->lastname);
$this->assertEquals($activity->id, $entry->dataid);
$this->assertEquals(0, $entry->groupid);
$this->assertEquals(1, $entry->approved);
}
}
/**
* Test for the get_template_content method.
*
* @covers ::get_template_content
*/
public function test_get_template_content(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course();
// Module data with templates.
$templates = [
'singletemplate' => 'Single template content',
'listtemplate' => 'List template content',
'listtemplateheader' => 'List template content header',
'listtemplatefooter' => 'List template content footer',
'addtemplate' => 'Add template content',
'rsstemplate' => 'RSS template content',
'rsstitletemplate' => 'RSS title template content',
'csstemplate' => 'CSS template content',
'jstemplate' => 'JS template content',
'asearchtemplate' => 'Advanced search template content',
];
$params = array_merge(['course' => $course], $templates);
// Create a database activity.
$activity = $this->getDataGenerator()->create_module(manager::MODULE, $params);
$manager = manager::create_from_instance($activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$preset = $plugingenerator->create_preset($activity, $record);
// Test user preset templates.
foreach ($templates as $templatename => $templatecontent) {
$content = $preset->get_template_content($templatename);
$this->assertEquals($templatecontent, $content);
}
// Test plugin preset content.
$pluginname = 'imagegallery';
$preset = preset::create_from_plugin($manager, $pluginname);
foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) {
// Get real file contents.
$path = $manager->path . '/preset/' . $pluginname . '/' . $templatefile;
$templatecontent = file_get_contents($path);
$content = $preset->get_template_content($templatename);
$this->assertEquals($templatecontent, $content);
}
}
/**
* Test for the get_fullname method.
*
* @covers ::get_fullname
*/
public function test_get_fullname(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course();
// Create a database activity.
$activity = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]);
$manager = manager::create_from_instance($activity);
// Create a saved preset.
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$record = (object) [
'name' => 'Testing preset name',
'description' => 'Testing preset description',
];
$preset = $plugingenerator->create_preset($activity, $record);
// Test user preset templates.
$this->assertEquals("{$user->id}/Testing preset name", $preset->get_fullname());
// Test plugin preset content.
$pluginname = 'imagegallery';
$preset = preset::create_from_plugin($manager, $pluginname);
$this->assertEquals("0/imagegallery", $preset->get_fullname());
}
}
+316
View File
@@ -0,0 +1,316 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package mod_data
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_data\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_userlist;
use mod_data\privacy\provider;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider tests class.
*
* @package mod_data
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/** @var stdClass The student object. */
protected $student;
/** @var stdClass The student object. */
protected $student2;
/** @var stdClass The student object. */
protected $student3;
/** @var stdClass The data object. */
protected $datamodule;
/** @var stdClass The course object. */
protected $course;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->resetAfterTest();
global $DB;
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$params = [
'course' => $course->id,
'name' => 'Database module',
'comments' => 1,
'assessed' => 1,
];
// The database activity.
$datamodule = $this->get_generator()->create_instance($params);
$fieldtypes = array('checkbox', 'date', 'menu', 'multimenu', 'number', 'radiobutton', 'text', 'textarea', 'url',
'latlong', 'file', 'picture');
// Creating test Fields with default parameter values.
foreach ($fieldtypes as $count => $fieldtype) {
// Creating variables dynamically.
$fieldname = 'field' . $count;
$record = new \stdClass();
$record->name = $fieldname;
$record->description = $fieldname . ' descr';
$record->type = $fieldtype;
${$fieldname} = $this->get_generator()->create_field($record, $datamodule);
}
$cm = get_coursemodule_from_instance('data', $datamodule->id);
// Create a student.
$student1 = $generator->create_user();
$student2 = $generator->create_user();
$student3 = $generator->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$generator->enrol_user($student1->id, $course->id, $studentrole->id);
$generator->enrol_user($student2->id, $course->id, $studentrole->id);
$generator->enrol_user($student3->id, $course->id, $studentrole->id);
// Add records.
$this->setUser($student1);
$record1id = $this->generate_data_record($datamodule);
$this->generate_data_record($datamodule);
$this->setUser($student2);
$this->generate_data_record($datamodule);
$this->generate_data_record($datamodule);
$this->generate_data_record($datamodule);
$this->setUser($student3);
$this->generate_data_record($datamodule);
$this->student = $student1;
$this->student2 = $student2;
$this->student3 = $student3;
$this->datamodule = $datamodule;
$this->course = $course;
}
/**
* Get mod_data generator
*
* @return mod_data_generator
*/
protected function get_generator() {
return $this->getDataGenerator()->get_plugin_generator('mod_data');
}
/**
* Generates one record in the database module as the current student
*
* @param stdClass $datamodule
* @return mixed
*/
protected function generate_data_record($datamodule) {
global $DB;
static $counter = 0;
$counter++;
$contents = array();
$contents[] = array('opt1', 'opt2', 'opt3', 'opt4');
$contents[] = sprintf("%02f", $counter) . '-01-2000';
$contents[] = 'menu1';
$contents[] = array('multimenu1', 'multimenu2', 'multimenu3', 'multimenu4');
$contents[] = 5 * $counter;
$contents[] = 'radioopt1';
$contents[] = 'text for testing' . $counter;
$contents[] = "<p>text area testing $counter<br /></p>";
$contents[] = array('example.url', 'sampleurl' . $counter);
$contents[] = [-31.9489873, 115.8382036]; // Latlong.
$contents[] = "Filename{$counter}.pdf"; // File - filename.
$contents[] = array("Cat{$counter}.jpg", 'Cat' . $counter); // Picture - filename with alt text.
$count = 0;
$fieldcontents = array();
$fields = $DB->get_records('data_fields', array('dataid' => $datamodule->id), 'id');
foreach ($fields as $fieldrecord) {
$fieldcontents[$fieldrecord->id] = $contents[$count++];
}
$tags = ['Cats', 'mice' . $counter];
return $this->get_generator()->create_entry($datamodule, $fieldcontents, 0, $tags);
}
/**
* Test for provider::get_metadata().
*/
public function test_get_metadata(): void {
$collection = new collection('mod_data');
$newcollection = provider::get_metadata($collection);
$itemcollection = $newcollection->get_collection();
$this->assertCount(7, $itemcollection);
$table = reset($itemcollection);
$this->assertEquals('data_records', $table->get_name());
$table = next($itemcollection);
$this->assertEquals('data_content', $table->get_name());
}
/**
* Test for provider::get_contexts_for_userid().
*/
public function test_get_contexts_for_userid(): void {
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$contextlist = provider::get_contexts_for_userid($this->student->id);
$this->assertCount(1, $contextlist);
$contextforuser = $contextlist->current();
$cmcontext = \context_module::instance($cm->id);
$this->assertEquals($cmcontext->id, $contextforuser->id);
}
/**
* Test for provider::get_users_in_context().
*/
public function test_get_users_in_context(): void {
$component = 'mod_data';
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$cmcontext = \context_module::instance($cm->id);
$userlist = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(3, $userlist);
$expected = [$this->student->id, $this->student2->id, $this->student3->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual);
}
/**
* Get test privacy writer
*
* @param context $context
* @return \core_privacy\tests\request\content_writer
*/
protected function get_writer($context) {
return \core_privacy\local\request\writer::with_context($context);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context(): void {
global $DB;
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$cmcontext = \context_module::instance($cm->id);
$records = $DB->get_records_select('data_records', 'userid = :userid ORDER BY id', ['userid' => $this->student->id]);
$record = reset($records);
$contents = $DB->get_records('data_content', ['recordid' => $record->id]);
// Export all of the data for the context.
$this->export_context_data_for_user($this->student->id, $cmcontext, 'mod_data');
$writer = $this->get_writer($cmcontext);
$data = $writer->get_data([$record->id]);
$this->assertNotEmpty($data);
foreach ($contents as $content) {
$data = $writer->get_data([$record->id, $content->id]);
$this->assertNotEmpty($data);
$hasfile = in_array($data->field['type'], ['file', 'picture']);
$this->assertEquals($hasfile, !empty($writer->get_files([$record->id, $content->id])));
}
$tags = $writer->get_related_data([$record->id], 'tags');
$this->assertNotEmpty($tags);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$cmcontext = \context_module::instance($cm->id);
provider::delete_data_for_all_users_in_context($cmcontext);
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertFalse($this->get_writer($cmcontext)->has_any_data());
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$cmcontext = \context_module::instance($cm->id);
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]);
provider::delete_data_for_user($appctxt);
provider::export_user_data($appctxt);
$this->assertFalse($this->get_writer($cmcontext)->has_any_data());
}
/**
* Test for provider::delete_data_for_users().
*/
public function test_delete_data_for_users(): void {
$cm = get_coursemodule_from_instance('data', $this->datamodule->id);
$cmcontext = \context_module::instance($cm->id);
$userstodelete = [$this->student->id, $this->student2->id];
// Ensure student, student 2 and student 3 have data before being deleted.
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertTrue($this->get_writer($cmcontext)->has_any_data());
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertTrue($this->get_writer($cmcontext)->has_any_data());
// Delete data for student 1 and 2.
$approvedlist = new approved_userlist($cmcontext, 'mod_data', $userstodelete);
provider::delete_data_for_users($approvedlist);
// Reset the writer so it doesn't contain the data from before deletion.
\core_privacy\local\request\writer::reset();
// Ensure data is now deleted for student and student 2.
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertFalse($this->get_writer($cmcontext)->has_any_data());
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertFalse($this->get_writer($cmcontext)->has_any_data());
// Ensure data still intact for student 3.
$appctxt = new \core_privacy\local\request\approved_contextlist($this->student3, 'mod_data', [$cmcontext->id]);
provider::export_user_data($appctxt);
$this->assertTrue($this->get_writer($cmcontext)->has_any_data());
}
}
File diff suppressed because it is too large Load Diff
+999
View File
@@ -0,0 +1,999 @@
<?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 mod_data;
use context_module;
use rating_manager;
use stdClass;
/**
* Template tests class for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_data\template
*/
class template_test extends \advanced_testcase {
/**
* Setup to ensure that fixtures are loaded.
*/
public static function setupBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/rating/lib.php');
}
/**
* Test for static create methods.
*
* @covers ::parse_entries
* @dataProvider parse_entries_provider
* @param string $templatecontent the template string
* @param string $expected expected output
* @param string $rolename the user rolename
* @param bool $enableexport is portfolio export is enabled
* @param bool $approved if the entry is approved
* @param bool $enablecomments is comments are enabled
* @param bool $enableratings if ratings are enabled
* @param array $options extra parser options
* @param bool $otherauthor if the entry is from another user
*/
public function test_parse_entries(
string $templatecontent,
string $expected,
string $rolename = 'editingteacher',
bool $enableexport = false,
bool $approved = true,
bool $enablecomments = false,
bool $enableratings = false,
array $options = [],
bool $otherauthor = false
): void {
global $DB, $PAGE;
// Comments, tags, approval, user role.
$this->resetAfterTest();
$params = ['approval' => true];
// Enable comments.
if ($enablecomments) {
set_config('usecomments', 1);
$params['comments'] = true;
$PAGE->reset_theme_and_output();
$PAGE->set_url('/mod/data/view.php');
}
$course = $this->getDataGenerator()->create_course();
$params['course'] = $course;
$activity = $this->getDataGenerator()->create_module('data', $params);
$cm = get_coursemodule_from_id('data', $activity->cmid, 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
$user = $this->getDataGenerator()->create_user();
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleids[$rolename]);
$author = $user;
if ($otherauthor) {
$user2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleids[$rolename]);
$author = $user2;
}
// Generate an entry.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$fieldrecord = (object)['name' => 'myfield', 'type' => 'text'];
$field = $generator->create_field($fieldrecord, $activity);
$otherfieldrecord = (object)['name' => 'otherfield', 'type' => 'text'];
$otherfield = $generator->create_field($otherfieldrecord, $activity);
$this->setUser($user);
$entryid = $generator->create_entry(
$activity,
[
$field->field->id => 'Example entry',
$otherfield->field->id => 'Another example',
],
0,
['Cats', 'Dogs'],
['approved' => $approved]
);
if ($enableexport) {
$this->enable_portfolio($user);
}
$manager = manager::create_from_instance($activity);
$entry = (object)[
'id' => $entryid,
'approved' => $approved,
'timecreated' => 1657618639,
'timemodified' => 1657618650,
'userid' => $author->id,
'groupid' => 0,
'dataid' => $activity->id,
'picture' => 0,
'firstname' => $author->firstname,
'lastname' => $author->lastname,
'firstnamephonetic' => $author->firstnamephonetic,
'lastnamephonetic' => $author->lastnamephonetic,
'middlename' => $author->middlename,
'alternatename' => $author->alternatename,
'imagealt' => 'PIXEXAMPLE',
'email' => $author->email,
];
$entries = [$entry];
if ($enableratings) {
$entries = $this->enable_ratings($context, $activity, $entries, $user);
}
// Some cooked variables for the regular expression.
$replace = [
'{authorfullname}' => fullname($author),
'{timeadded}' => userdate($entry->timecreated, get_string('strftimedatemonthabbr', 'langconfig')),
'{timemodified}' => userdate($entry->timemodified, get_string('strftimedatemonthabbr', 'langconfig')),
'{fieldid}' => $field->field->id,
'{fieldname}' => $field->field->name,
'{fielddescription}' => $field->field->description,
'{entryid}' => $entry->id,
'{cmid}' => $cm->id,
'{courseid}' => $course->id,
'{authorid}' => $author->id
];
$parser = new template($manager, $templatecontent, $options);
$result = $parser->parse_entries($entries);
// We don't want line breaks for the validations.
$result = str_replace("\n", '', $result);
$regexp = str_replace(array_keys($replace), array_values($replace), $expected);
$this->assertMatchesRegularExpression($regexp, $result);
}
/**
* Data provider for test_parse_entries().
*
* @return array of scenarios
*/
public function parse_entries_provider(): array {
return [
// Teacher scenarios.
'Teacher id tag' => [
'templatecontent' => 'Some ##id## tag',
'expected' => '|Some {entryid} tag|',
'rolename' => 'editingteacher',
],
'Teacher delete tag' => [
'templatecontent' => 'Some ##delete## tag',
'expected' => '|Some .*delete.*{entryid}.*sesskey.*Delete.* tag|',
'rolename' => 'editingteacher',
],
'Teacher edit tag' => [
'templatecontent' => 'Some ##edit## tag',
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* tag|',
'rolename' => 'editingteacher',
],
'Teacher more tag' => [
'templatecontent' => 'Some ##more## tag',
'expected' => '|Some .*more.*{cmid}.*rid.*{entryid}.*More.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Teacher more tag with showmore set to false' => [
'templatecontent' => 'Some ##more## tag',
'expected' => '|Some tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => false],
],
'Teacher moreurl tag' => [
'templatecontent' => 'Some ##moreurl## tag',
'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Teacher moreurl tag with showmore set to false' => [
'templatecontent' => 'Some ##moreurl## tag',
'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => false],
],
'Teacher delcheck tag' => [
'templatecontent' => 'Some ##delcheck## tag',
'expected' => '|Some .*input.*checkbox.*value.*{entryid}.* tag|',
'rolename' => 'editingteacher',
],
'Teacher user tag' => [
'templatecontent' => 'Some ##user## tag',
'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.*{authorfullname}.* tag|',
'rolename' => 'editingteacher',
],
'Teacher userpicture tag' => [
'templatecontent' => 'Some ##userpicture## tag',
'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.* tag|',
'rolename' => 'editingteacher',
],
'Teacher export tag' => [
'templatecontent' => 'Some ##export## tag',
'expected' => '|Some .*portfolio/add.* tag|',
'rolename' => 'editingteacher',
'enableexport' => true,
],
'Teacher export tag not configured' => [
'templatecontent' => 'Some ##export## tag',
'expected' => '|Some tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
],
'Teacher timeadded tag' => [
'templatecontent' => 'Some ##timeadded## tag',
'expected' => '|Some <span.*>{timeadded}</span> tag|',
'rolename' => 'editingteacher',
],
'Teacher timemodified tag' => [
'templatecontent' => 'Some ##timemodified## tag',
'expected' => '|Some <span.*>{timemodified}</span> tag|',
'rolename' => 'editingteacher',
],
'Teacher approve tag approved entry' => [
'templatecontent' => 'Some ##approve## tag',
'expected' => '|Some tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
],
'Teacher approve tag disapproved entry' => [
'templatecontent' => 'Some ##approve## tag',
'expected' => '|Some .*approve.*{entryid}.*sesskey.*Approve.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => false,
],
'Teacher disapprove tag approved entry' => [
'templatecontent' => 'Some ##disapprove## tag',
'expected' => '|Some .*disapprove.*{entryid}.*sesskey.*Undo approval.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
],
'Teacher disapprove tag disapproved entry' => [
'templatecontent' => 'Some ##disapprove## tag',
'expected' => '|Some tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => false,
],
'Teacher approvalstatus tag approved entry' => [
'templatecontent' => 'Some ##approvalstatus## tag',
'expected' => '|Some tag|', // We do not display the approval status anymore.
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
],
'Teacher approvalstatus tag disapproved entry' => [
'templatecontent' => 'Some ##approvalstatus## tag',
'expected' => '|Some .*Pending approval.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => false,
],
'Teacher approvalstatusclass tag approved entry' => [
'templatecontent' => 'Some ##approvalstatusclass## tag',
'expected' => '|Some approved tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
],
'Teacher approvalstatusclass tag disapproved entry' => [
'templatecontent' => 'Some ##approvalstatusclass## tag',
'expected' => '|Some notapproved tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => false,
],
'Teacher tags tag' => [
'templatecontent' => 'Some ##tags## tag',
'expected' => '|Some .*Cats.* tag|',
'rolename' => 'editingteacher',
],
'Teacher field name tag' => [
'templatecontent' => 'Some [[myfield]] tag',
'expected' => '|Some .*Example entry.* tag|',
'rolename' => 'editingteacher',
],
'Teacher field#id tag' => [
'templatecontent' => 'Some [[myfield#id]] tag',
'expected' => '|Some {fieldid} tag|',
'rolename' => 'editingteacher',
],
'Teacher field#name tag' => [
'templatecontent' => 'Some [[myfield#name]] tag',
'expected' => '|Some {fieldname} tag|',
'rolename' => 'editingteacher',
],
'Teacher field#description tag' => [
'templatecontent' => 'Some [[myfield#description]] tag',
'expected' => '|Some {fielddescription} tag|',
'rolename' => 'editingteacher',
],
'Teacher comments name tag with comments enabled' => [
'templatecontent' => 'Some ##comments## tag',
'expected' => '|Some .*Comments.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => true,
],
'Teacher comments name tag with comments disabled' => [
'templatecontent' => 'Some ##comments## tag',
'expected' => '|Some tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
],
'Teacher comment forced with comments enables' => [
'templatecontent' => 'No tags',
'expected' => '|No tags.*Comments.*|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => true,
'enableratings' => false,
'options' => ['comments' => true],
],
'Teacher comment forced without comments enables' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags$|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['comments' => true],
],
'Teacher adding ratings without ratings configured' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags$|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['ratings' => true],
],
'Teacher adding ratings with ratings configured' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags.*Average of ratings|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => true,
'options' => ['ratings' => true],
],
'Teacher actionsmenu tag with default options' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* .*delete.*{entryid}.*sesskey.*Delete.* tag|',
'rolename' => 'editingteacher',
],
'Teacher actionsmenu tag with default options (check Show more is not there)' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|^Some((?!Show more).)*tag$|',
'rolename' => 'editingteacher',
],
'Teacher actionsmenu tag with show more enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*view.*{cmid}.*rid.*{entryid}.*Show more.* .*Edit.* .*Delete.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => false,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Teacher actionsmenu tag with export enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*Edit.* .*Delete.* .*portfolio/add.* tag|',
'rolename' => 'editingteacher',
'enableexport' => true,
],
'Teacher actionsmenu tag with approved enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*Edit.* .*Delete.* .*disapprove.*{entryid}.*sesskey.*Undo approval.* tag|',
'rolename' => 'editingteacher',
'enableexport' => false,
'approved' => true,
],
'Teacher actionsmenu tag with export, approved and showmore enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*Show more.* .*Edit.* .*Delete.* .*Undo approval.* .*Export to portfolio.* tag|',
'rolename' => 'editingteacher',
'enableexport' => true,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Teacher otherfields tag' => [
'templatecontent' => 'Some ##otherfields## tag',
'expected' => '|Some .*{fieldname}.*Example entry.*otherfield.*Another example.* tag|',
'rolename' => 'editingteacher',
],
'Teacher otherfields tag with some field in the template' => [
'templatecontent' => 'Some [[myfield]] and ##otherfields## tag',
'expected' => '|Some .*Example entry.* and .*otherfield.*Another example.* tag|',
'rolename' => 'editingteacher',
],
// Student scenarios.
'Student id tag' => [
'templatecontent' => 'Some ##id## tag',
'expected' => '|Some {entryid} tag|',
'rolename' => 'student',
],
'Student delete tag' => [
'templatecontent' => 'Some ##delete## tag',
'expected' => '|Some .*delete.*{entryid}.*sesskey.*Delete.* tag|',
'rolename' => 'student',
],
'Student delete tag on other author entry' => [
'templatecontent' => 'Some ##delete## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => [],
'otherauthor' => true,
],
'Student edit tag' => [
'templatecontent' => 'Some ##edit## tag',
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* tag|',
'rolename' => 'student',
],
'Student edit tag on other author entry' => [
'templatecontent' => 'Some ##edit## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => [],
'otherauthor' => true,
],
'Student more tag' => [
'templatecontent' => 'Some ##more## tag',
'expected' => '|Some .*more.*{cmid}.*rid.*{entryid}.*More.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Student more tag with showmore set to false' => [
'templatecontent' => 'Some ##more## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => false],
],
'Student moreurl tag' => [
'templatecontent' => 'Some ##moreurl## tag',
'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Student moreurl tag with showmore set to false' => [
'templatecontent' => 'Some ##moreurl## tag',
'expected' => '|Some .*/mod/data/view.*{cmid}.*rid.*{entryid}.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => false],
],
'Student delcheck tag' => [
'templatecontent' => 'Some ##delcheck## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
],
'Student user tag' => [
'templatecontent' => 'Some ##user## tag',
'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.*{authorfullname}.* tag|',
'rolename' => 'student',
],
'Student userpicture tag' => [
'templatecontent' => 'Some ##userpicture## tag',
'expected' => '|Some .*user/view.*{authorid}.*course.*{courseid}.* tag|',
'rolename' => 'student',
],
'Student export tag' => [
'templatecontent' => 'Some ##export## tag',
'expected' => '|Some .*portfolio/add.* tag|',
'rolename' => 'student',
'enableexport' => true,
],
'Student export tag not configured' => [
'templatecontent' => 'Some ##export## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
],
'Student export tag on other user entry' => [
'templatecontent' => 'Some ##export## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => [],
'otherauthor' => true,
],
'Student timeadded tag' => [
'templatecontent' => 'Some ##timeadded## tag',
'expected' => '|Some <span.*>{timeadded}</span> tag|',
'rolename' => 'student',
],
'Student timemodified tag' => [
'templatecontent' => 'Some ##timemodified## tag',
'expected' => '|Some <span.*>{timemodified}</span> tag|',
'rolename' => 'student',
],
'Student approve tag approved entry' => [
'templatecontent' => 'Some ##approve## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
],
'Student approve tag disapproved entry' => [
'templatecontent' => 'Some ##approve## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => false,
],
'Student disapprove tag approved entry' => [
'templatecontent' => 'Some ##disapprove## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
],
'Student disapprove tag disapproved entry' => [
'templatecontent' => 'Some ##disapprove## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => false,
],
'Student approvalstatus tag approved entry' => [
'templatecontent' => 'Some ##approvalstatus## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
],
'Student approvalstatus tag disapproved entry' => [
'templatecontent' => 'Some ##approvalstatus## tag',
'expected' => '|Some .*Pending approval.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => false,
],
'Student approvalstatusclass tag approved entry' => [
'templatecontent' => 'Some ##approvalstatusclass## tag',
'expected' => '|Some approved tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
],
'Student approvalstatusclass tag disapproved entry' => [
'templatecontent' => 'Some ##approvalstatusclass## tag',
'expected' => '|Some notapproved tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => false,
],
'Student tags tag' => [
'templatecontent' => 'Some ##tags## tag',
'expected' => '|Some .*Cats.* tag|',
'rolename' => 'student',
],
'Student field name tag' => [
'templatecontent' => 'Some [[myfield]] tag',
'expected' => '|Some .*Example entry.* tag|',
'rolename' => 'student',
],
'Student field#id name tag' => [
'templatecontent' => 'Some [[myfield#id]] tag',
'expected' => '|Some {fieldid} tag|',
'rolename' => 'student',
],
'Student field#name tag' => [
'templatecontent' => 'Some [[myfield#name]] tag',
'expected' => '|Some {fieldname} tag|',
'rolename' => 'student',
],
'Student field#description tag' => [
'templatecontent' => 'Some [[myfield#description]] tag',
'expected' => '|Some {fielddescription} tag|',
'rolename' => 'student',
],
'Student comments name tag with comments enabled' => [
'templatecontent' => 'Some ##comments## tag',
'expected' => '|Some .*Comments.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => true,
],
'Student comments name tag with comments disabled' => [
'templatecontent' => 'Some ##comments## tag',
'expected' => '|Some tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
],
'Student comment forced with comments enables' => [
'templatecontent' => 'No tags',
'expected' => '|No tags.*Comments.*|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => true,
'enableratings' => false,
'options' => ['comments' => true]
],
'Student comment forced without comments enables' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags$|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['comments' => true]
],
'Student adding ratings without ratings configured' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags$|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['ratings' => true]
],
'Student adding ratings with ratings configured' => [
'templatecontent' => 'No tags',
'expected' => '|^No tags$|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
'enablecomments' => false,
'enableratings' => true,
'options' => ['ratings' => true]
],
'Student actionsmenu tag with default options' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*edit.*{entryid}.*sesskey.*Edit.* .*delete.*{entryid}.*sesskey.*Delete.* tag|',
'rolename' => 'student',
],
'Student actionsmenu tag with default options (check Show more is not there)' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|^Some((?!Show more).)*tag$|',
'rolename' => 'student',
],
'Student actionsmenu tag with show more enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*view.*{cmid}.*rid.*{entryid}.*Show more.* .*Edit.* .*Delete.* tag|',
'rolename' => 'student',
'enableexport' => false,
'approved' => false,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Student actionsmenu tag with export enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*Edit.* .*Delete.* .*portfolio/add.* tag|',
'rolename' => 'student',
'enableexport' => true,
],
'Student actionsmenu tag with approved enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|^Some((?!Approve).)*tag$|',
'rolename' => 'student',
'enableexport' => false,
'approved' => true,
],
'Student actionsmenu tag with export, approved and showmore enabled' => [
'templatecontent' => 'Some ##actionsmenu## tag',
'expected' => '|Some .*Show more.* .*Edit.* .*Delete.* .*Export to portfolio.* tag|',
'rolename' => 'student',
'enableexport' => true,
'approved' => true,
'enablecomments' => false,
'enableratings' => false,
'options' => ['showmore' => true],
],
'Student otherfields tag' => [
'templatecontent' => 'Some ##otherfields## tag',
'expected' => '|Some .*{fieldname}.*Example entry.*otherfield.*Another example.* tag|',
'rolename' => 'student',
],
'Student otherfields tag with some field in the template' => [
'templatecontent' => 'Some [[myfield]] and ##otherfields## tag',
'expected' => '|Some .*Example entry.* and .*otherfield.*Another example.* tag|',
'rolename' => 'student',
],
];
}
/**
* Create all the necessary data to enable portfolio export in mod_data
*
* @param stdClass $user the current user record.
*/
protected function enable_portfolio(stdClass $user) {
global $DB;
set_config('enableportfolios', 1);
$plugin = 'download';
$name = 'Download';
$portfolioinstance = (object) [
'plugin' => $plugin,
'name' => $name,
'visible' => 1
];
$portfolioinstance->id = $DB->insert_record('portfolio_instance', $portfolioinstance);
$userinstance = (object) [
'instance' => $portfolioinstance->id,
'userid' => $user->id,
'name' => 'visible',
'value' => 1
];
$DB->insert_record('portfolio_instance_user', $userinstance);
$DB->insert_record('portfolio_log', [
'portfolio' => $portfolioinstance->id,
'userid' => $user->id,
'caller_class' => 'data_portfolio_caller',
'caller_component' => 'mod_data',
'time' => time(),
]);
}
/**
* Enable the ratings on the database entries.
*
* @param context_module $context the activity context
* @param stdClass $activity the activity record
* @param array $entries database entries
* @param stdClass $user the current user record
* @return stdClass the entries with the rating attribute
*/
protected function enable_ratings(context_module $context, stdClass $activity, array $entries, stdClass $user) {
global $CFG;
$ratingoptions = (object)[
'context' => $context,
'component' => 'mod_data',
'ratingarea' => 'entry',
'items' => $entries,
'aggregate' => RATING_AGGREGATE_AVERAGE,
'scaleid' => $activity->scale,
'userid' => $user->id,
'returnurl' => $CFG->wwwroot . '/mod/data/view.php',
'assesstimestart' => $activity->assesstimestart,
'assesstimefinish' => $activity->assesstimefinish,
];
$rm = new rating_manager();
return $rm->get_ratings($ratingoptions);
}
/**
* Test parse add entry template parsing.
*
* @covers ::parse_add_entry
* @dataProvider parse_add_entry_provider
* @param string $templatecontent the template string
* @param string $expected expected output
* @param bool $newentry if it is a new entry or editing and existing one
*/
public function test_parse_add_entry(
string $templatecontent,
string $expected,
bool $newentry = false
): void {
global $DB, $PAGE;
// Comments, tags, approval, user role.
$this->resetAfterTest();
$params = ['approval' => true];
$course = $this->getDataGenerator()->create_course();
$params['course'] = $course;
$activity = $this->getDataGenerator()->create_module('data', $params);
$author = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
// Generate an entry.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
$fieldrecord = (object)[
'name' => 'myfield',
'type' => 'text',
'description' => 'This is a field'
];
$field = $generator->create_field($fieldrecord, $activity);
$otherfieldrecord = (object)['name' => 'otherfield', 'type' => 'text'];
$otherfield = $generator->create_field($otherfieldrecord, $activity);
if ($newentry) {
$entryid = null;
$entry = null;
} else {
$entryid = $generator->create_entry(
$activity,
[
$field->field->id => 'Example entry',
$otherfield->field->id => 'Another example',
],
0,
['Cats', 'Dogs']
);
$entry = (object)[
'd' => $activity->id,
'rid' => $entryid,
"field_{$field->field->id}" => "New value",
"field_{$otherfield->field->id}" => "Altered value",
];
}
$manager = manager::create_from_instance($activity);
// Some cooked variables for the regular expression.
$replace = [
'{fieldid}' => $field->field->id,
'{fieldname}' => $field->field->name,
'{fielddescription}' => $field->field->description,
'{otherid}' => $otherfield->field->id,
];
$processdata = (object)[
'generalnotifications' => ['GENERAL'],
'fieldnotifications' => [
$field->field->name => ['FIELD'],
$otherfield->field->name => ['OTHERFIELD'],
],
];
$parser = new template($manager, $templatecontent);
$result = $parser->parse_add_entry($processdata, $entryid, $entry);
// We don't want line breaks for the validations.
$result = str_replace("\n", '', $result);
$regexp = str_replace(array_keys($replace), array_values($replace), $expected);
$this->assertMatchesRegularExpression($regexp, $result);
}
/**
* Data provider for test_parse_add_entry().
*
* @return array of scenarios
*/
public function parse_add_entry_provider(): array {
return [
// Editing an entry.
'Teacher editing entry tags tag' => [
'templatecontent' => 'Some ##tags## tag',
'expected' => '|GENERAL.*Some .*select .*tags.*Cats.* tag|',
'newentry' => false,
],
'Teacher editing entry field name tag' => [
'templatecontent' => 'Some [[myfield]] tag',
'expected' => '|GENERAL.*Some .*FIELD.*field_{fieldid}.*input.*New value.* tag|',
'newentry' => false,
],
'Teacher editing entry field#id tag' => [
'templatecontent' => 'Some [[myfield#id]] tag',
'expected' => '|GENERAL.*Some field_{fieldid} tag|',
'newentry' => false,
],
'Teacher editing field#name tag' => [
'templatecontent' => 'Some [[myfield#name]] tag',
'expected' => '|GENERAL.*Some {fieldname} tag|',
'newentry' => false,
],
'Teacher editing field#description tag' => [
'templatecontent' => 'Some [[myfield#description]] tag',
'expected' => '|GENERAL.*Some {fielddescription} tag|',
'newentry' => false,
],
'Teacher editing entry field otherfields tag' => [
'templatecontent' => 'Some [[myfield]] and ##otherfields## tag',
'expected' => '|GENERAL.*Some .*FIELD.*field_{fieldid}.*input.*New value.* '
. 'and .*OTHERFIELD.*field_{otherid}.*input.*Altered value.* tag|',
'newentry' => false,
],
// New entry.
'Teacher new entry tags tag' => [
'templatecontent' => 'Some ##tags## tag',
'expected' => '|GENERAL.*Some .*select .*tags\[\].* tag|',
'newentry' => true,
],
'Teacher new entry field name tag' => [
'templatecontent' => 'Some [[myfield]] tag',
'expected' => '|GENERAL.*Some .*FIELD.*field_{fieldid}.*input.*value="".* tag|',
'newentry' => true,
],
'Teacher new entry field#id name tag' => [
'templatecontent' => 'Some [[myfield#id]] tag',
'expected' => '|GENERAL.*Some field_{fieldid} tag|',
'newentry' => true,
],
'Teacher new entry field#name tag' => [
'templatecontent' => 'Some [[myfield#name]] tag',
'expected' => '|GENERAL.*Some {fieldname} tag|',
'newentry' => false,
],
'Teacher new entry field#description tag' => [
'templatecontent' => 'Some [[myfield#description]] tag',
'expected' => '|GENERAL.*Some {fielddescription} tag|',
'newentry' => false,
],
'Teacher new entry field otherfields tag' => [
'templatecontent' => 'Some [[myfield]] and ##otherfields## tag',
'expected' => '|GENERAL.*Some .*FIELD.*field_{fieldid}.*input.*New value.* '
. '.* and .*OTHERFIELD.*field_{otherid}.*input.*Altered value.* |',
'newentry' => false,
],
];
}
}