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,85 @@
<?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_glossary\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_glossary
* @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_glossary');
$record = ['assesstimefinish' => 100, 'assesstimestart' => 100, 'ratingtime' => 1, 'assessed' => 2, 'scale' => 1];
list($course, $glossary) = $this->create_course_and_module('glossary', $record);
// Glossary entries.
$entry1 = $gg->create_content($glossary, array('approved' => 1));
$gg->create_content($glossary, array('approved' => 0, 'userid' => $USER->id));
$gg->create_content($glossary, array('approved' => 0, 'userid' => -1));
$gg->create_content($glossary, array('approved' => 1));
$timestamp = 10000;
$DB->set_field('glossary_entries', 'timecreated', $timestamp);
$DB->set_field('glossary_entries', 'timemodified', $timestamp);
$ratingoptions = new \stdClass;
$ratingoptions->context = \context_module::instance($glossary->cmid);
$ratingoptions->ratingarea = 'entry';
$ratingoptions->component = 'mod_glossary';
$ratingoptions->itemid = $entry1->id;
$ratingoptions->scaleid = 2;
$ratingoptions->userid = $USER->id;
$rating = new \rating($ratingoptions);
$rating->update_rating(2);
$rating = $DB->get_record('rating', ['itemid' => $entry1->id]);
// Do backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newglossary = $DB->get_record('glossary', ['course' => $newcourseid]);
$this->assertFieldsNotRolledForward($glossary, $newglossary, ['timecreated', 'timemodified']);
$props = ['assesstimefinish', 'assesstimestart'];
$this->assertFieldsRolledForward($glossary, $newglossary, $props);
$newentries = $DB->get_records('glossary_entries', ['glossaryid' => $newglossary->id]);
$newcm = $DB->get_record('course_modules', ['course' => $newcourseid, 'instance' => $newglossary->id]);
// Entries test.
foreach ($newentries as $entry) {
$this->assertEquals($timestamp, $entry->timecreated);
$this->assertEquals($timestamp, $entry->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,77 @@
<?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 glossary activity.
*
* @package mod_glossary
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode;
/**
* Glossary-related steps definitions.
*
* @package mod_glossary
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_glossary extends behat_base {
/**
* Adds an entry to the current glossary with the provided data. You should be in the glossary page.
*
* @Given /^I add a glossary entry with the following data:$/
* @param TableNode $data
*/
public function i_add_a_glossary_entry_with_the_following_data(TableNode $data) {
$this->execute("behat_forms::press_button", get_string('addsingleentry', 'mod_glossary'));
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
$this->execute("behat_forms::press_button", get_string('savechanges'));
}
/**
* Adds a category with the specified name to the current glossary. You need to be in the glossary page.
*
* @Given /^I add a glossary entries category named "(?P<category_name_string>(?:[^"]|\\")*)"$/
* @param string $categoryname Category name
*/
public function i_add_a_glossary_entries_category_named($categoryname) {
$params = [
get_string('categoryview', 'mod_glossary'),
get_string('explainalphabet', 'glossary')
];
$this->execute("behat_forms::i_select_from_the_singleselect", $params);
$this->execute("behat_forms::press_button", get_string('editcategories', 'mod_glossary'));
$this->execute("behat_forms::press_button", get_string('addcategory', 'glossary'));
$this->execute('behat_forms::i_set_the_field_to', array('name', $this->escape($categoryname)));
$this->execute("behat_forms::press_button", get_string('savechanges'));
$this->execute("behat_forms::press_button", get_string('back', 'mod_glossary'));
}
}
+116
View File
@@ -0,0 +1,116 @@
@mod @mod_glossary
Feature: Glossary entries can be organised in categories
In order to organise glossary entries
As a teacher
I need to be able to create, edit and delete categories
@javascript
Scenario: Glossary entries can be organised in categories and categories can be autolinked
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 | displayformat | course | idnumber |
| glossary | MyGlossary | Test glossary description | encyclopedia | C1 | glossary1 |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| label | name | check autolinking of CategoryAutoLinks and CategoryNoLinks text | C1 | label1 |
And the "glossary" filter is "on"
# Log in as a teacher and make sure nothing is yet autolinked
When I am on the "Course 1" course page logged in as teacher1
Then I should see "CategoryAutoLinks"
And I should see "CategoryNoLinks"
And "a.glossary.autolink" "css_element" should not exist
# Create, edit and delete categories
And I am on the MyGlossary "glossary activity" page
And I select "Browse by category" from the "Browse the glossary using this index" singleselect
And I press "Edit categories"
And I press "Add category"
And I set the field "name" to "CategoryNoLinks"
And I press "Save changes"
And I should see "0 Entries" in the "CategoryNoLinks" "table_row"
And I press "Add category"
And I set the field "name" to "CategoryAutoLinks"
And I set the field "usedynalink" to "Yes"
And I press "Save changes"
And I should see "0 Entries" in the "CategoryAutoLinks" "table_row"
And I press "Add category"
And I set the field "name" to "Category2"
And I press "Save changes"
And I click on "Edit" "link" in the "Category2" "table_row"
And I set the field "name" to "Category3"
And I press "Save changes"
And I should see "Category3"
And I should not see "Category2"
And I click on "Delete" "link" in the "Category3" "table_row"
And I press "No"
And I should see "Category3"
And I click on "Delete" "link" in the "Category3" "table_row"
And I press "Yes"
And I should not see "Category3"
And I press "Back"
# Add glossary entries in categories and outside
And I add a glossary entry with the following data:
| Concept | EntryNoCategory |
| Definition | Definition |
And I add a glossary entry with the following data:
| Concept | EntryCategoryNL |
| Definition | Definition |
| Categories | CategoryNoLinks |
And I add a glossary entry with the following data:
| Concept | EntryCategoryAL |
| Definition | Definition |
| Categories | CategoryAutoLinks |
And I press "Add entry"
And I set the following fields to these values:
| Concept | EntryCategoryBoth |
| Definition | Definition |
| Categories | CategoryAutoLinks,CategoryNoLinks |
And I press "Save changes"
# Make sure entries appear in their categories
And I select "Browse by category" from the "Browse the glossary using this index" singleselect
And "//h3[contains(.,'CATEGORYAUTOLINKS')]" "xpath_element" should appear before "//h3[contains(.,'CATEGORYNOLINKS')]" "xpath_element"
And "//h4[contains(.,'EntryCategoryAL')]" "xpath_element" should appear before "//h3[contains(.,'CATEGORYNOLINKS')]" "xpath_element"
And "(//h4[contains(.,'EntryCategoryBoth')])[1]" "xpath_element" should appear before "//h3[contains(.,'CATEGORYNOLINKS')]" "xpath_element"
And "//h3[contains(.,'CATEGORYNOLINKS')]" "xpath_element" should appear before "(//h4[contains(.,'EntryCategoryBoth')])[2]" "xpath_element"
And "//h4[contains(.,'EntryCategoryNL')]" "xpath_element" should appear after "//h3[contains(.,'CATEGORYNOLINKS')]" "xpath_element"
And I should not see "EntryNoCategory"
And I set the field "hook" to "Not categorised"
And I set the field "Categories" to "Not categorised"
And I should see "EntryNoCategory"
And I should not see "EntryCategoryNL"
And I should not see "EntryCategoryAL"
And I should not see "EntryCategoryBoth"
# Check that category is autolinked from the text in the course
And I am on "Course 1" course homepage
And I should see "CategoryAutoLinks"
And I should see "CategoryAutoLinks" in the "a.glossary.autolink" "css_element"
And I should see "CategoryNoLinks"
And "//a[contains(.,'CategoryNoLinks')]" "xpath_element" should not exist
# Delete a category with entries
And I am on the MyGlossary "glossary activity" page
And I select "Browse by category" from the "Browse the glossary using this index" singleselect
And I press "Edit categories"
And I should see "2 Entries" in the "CategoryNoLinks" "table_row"
And I should see "2 Entries" in the "CategoryAutoLinks" "table_row"
And I click on "Delete" "link" in the "CategoryAutoLinks" "table_row"
And I press "Yes"
And I wait to be redirected
And I am on the MyGlossary "glossary activity" page
And I select "Browse by category" from the "Browse the glossary using this index" singleselect
And I should see "EntryCategoryNL"
And I should not see "EntryNoCategory"
And I should not see "EntryCategoryAL"
And I should see "EntryCategoryBoth"
And I set the field "Categories" to "Not categorised"
And I should see "EntryNoCategory"
And I should see "EntryCategoryAL"
And I should not see "EntryCategoryBoth"
@@ -0,0 +1,49 @@
@mod @mod_glossary
Feature: Create a glossary entry.
In order to create glossary entries
As a user
I should be able to enter an entry without using reserved keywords
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 | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | glossary |
| course | C1 |
| name | Test glossary |
Scenario: Glossary entry edition of custom tags works as expected
Given I am on the "Test glossary" "glossary activity" page logged in as "teacher1"
When I press "Add entry"
And I set the following fields to these values:
| Concept | Dummy first entry |
| Definition | Dream is the start of a journey |
| Keyword(s) | " |
And I press "Save changes"
Then I should see "One or more keywords contain a special character which cannot be used."
@javascript @_file_upload
Scenario: Create glossary entry with attached file
Given I am on the "Test glossary" "glossary activity" page logged in as student1
# As a student, add a glossary entry with attachment
And I press "Add entry"
And I set the following fields to these values:
| Concept | Entry 1 |
| Definition | Definition of Entry 1 |
| Attachment | mod/glossary/tests/fixtures/musicians.xml |
And I press "Save changes"
# Confirm you can download attachment from student's entry as teacher
When I am on the "Test glossary" "glossary activity" page logged in as teacher1
Then I should see "Entry 1"
And following "musicians.xml" should download a file that:
| Has mimetype | text/xml |
| Contains text in xml element | Paul McCartney |
@@ -0,0 +1,67 @@
@mod @mod_glossary @core_tag @javascript
Feature: Edited glossary entries handle tags correctly
In order to get glossary entries properly labelled
As a user
I need to introduce the tags while editing
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 | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| course | C1 |
| activity | glossary |
| name | Test glossary |
| intro | A glossary about dreams! |
Scenario: Glossary entry edition of custom tags works as expected
Given I am on the "Test glossary" "glossary activity" page logged in as teacher1
And I press "Add entry"
And I set the following fields to these values:
| Concept | Dummy first entry |
| Definition | Dream is the start of a journey |
| Tags | Example, Entry, Cool |
And I press "Save changes"
Then I should see "Example" in the ".glossary-tags" "css_element"
And I should see "Entry" in the ".glossary-tags" "css_element"
And I should see "Cool" in the ".glossary-tags" "css_element"
And I click on "Edit" "link" in the ".entrylowersection" "css_element"
And I expand all fieldsets
Then I should see "Example" in the ".form-autocomplete-selection" "css_element"
Then I should see "Entry" in the ".form-autocomplete-selection" "css_element"
Then I should see "Cool" in the ".form-autocomplete-selection" "css_element"
Scenario: Glossary entry edition of standard tags works as expected
Given the following "tags" exist:
| name | isstandard |
| OT1 | 1 |
| OT2 | 1 |
| OT3 | 1 |
And I am on the "Test glossary" "glossary activity" page logged in as teacher1
And I press "Add entry"
And I expand all fieldsets
And I open the autocomplete suggestions list
And I should see "OT1" in the ".form-autocomplete-suggestions" "css_element"
And I should see "OT2" in the ".form-autocomplete-suggestions" "css_element"
And I should see "OT3" in the ".form-autocomplete-suggestions" "css_element"
When I set the following fields to these values:
| Concept | Dummy first entry |
| Definition | Dream is the start of a journey |
| Tags | OT1, OT3 |
And I press "Save changes"
Then I should see "OT1" in the ".glossary-tags" "css_element"
And I should see "OT3" in the ".glossary-tags" "css_element"
And I should not see "OT2" in the ".glossary-tags" "css_element"
And I click on "Edit" "link" in the ".entrylowersection" "css_element"
And I expand all fieldsets
And I should see "OT1" in the ".form-autocomplete-selection" "css_element"
And I should see "OT3" in the ".form-autocomplete-selection" "css_element"
And I should not see "OT2" in the ".form-autocomplete-selection" "css_element"
@@ -0,0 +1,35 @@
@mod @mod_glossary
Feature: A teacher can set whether glossary entries are always editable or not
In order to ensure students think before adding new entries
As a teacher
I need to prevent entries to be always editable
Scenario: Glossary entries are not always editable
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 | editalways |
| glossary | Test glossary name | Test glossary description | C1 | glossary1 | 0 |
And the following config values are set as admin:
| maxeditingtime | 5 |
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Test glossary name"
When I add a glossary entry with the following data:
| Concept | Test concept name |
| Definition | Test concept description |
Then "Delete entry: Test concept name" "link" should exist
And "Edit entry: Test concept name" "link" should exist
And I wait "6" seconds
And I reload the page
And "Delete entry: Test concept name" "link" should not exist
And "Edit entry: Test concept name" "link" should not exist
@@ -0,0 +1,80 @@
@mod @mod_glossary
Feature: A teacher can choose whether glossary entries require approval
In order to check entries before they are displayed
As a user
I need to enable entries requiring approval
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@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 |
| student2 | C1 | student |
And the following "activity" exists:
| activity | glossary |
| course | C1 |
| idnumber | 0001 |
| name | Test glossary name |
| intro | Test glossary entries require approval |
| section | 1 |
| defaultapproval | 0 |
Scenario: Approve and undo approve glossary entries
Given I am on the "Test glossary name" "glossary activity" page logged in as student1
When I add a glossary entry with the following data:
| Concept | Just a test concept |
| Definition | Concept definition |
| Keyword(s) | Black |
And I log out
# Test that students can not see the unapproved entry.
And I am on the "Test glossary name" "glossary activity" page logged in as student2
Then I should see "No entries found in this section"
And I log out
# Approve the entry.
And I am on the "Test glossary name" "glossary activity" page logged in as teacher1
And I follow "Pending approval (1)"
Then I should see "(this entry is currently hidden)"
And I follow "Approve"
And I am on the "Test glossary name" "glossary activity" page
Then I should see "Concept definition"
And I log out
# Check that the entry can now be viewed by students.
And I am on the "Test glossary name" "glossary activity" page logged in as student2
Then I should see "Concept definition"
And I log out
# Undo the approval of the previous entry.
And I am on the "Test glossary name" "glossary activity" page logged in as teacher1
And I follow "Undo approval"
And I log out
# Check that the entry is no longer visible by students.
And I am on the "Test glossary name" "glossary activity" page logged in as student2
Then I should see "No entries found in this section"
@javascript
Scenario: View pending approval glossary items
Given I am on the "Test glossary name" "glossary activity" page logged in as student1
When I add a glossary entry with the following data:
| Concept | Just a test concept |
| Definition | Concept definition |
| Keyword(s) | Black |
| Tags | Test |
And I log out
And I log in as "teacher1"
And I turn editing mode on
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I add the "Navigation" block if not present
And I expand "Site pages" node
And I click on "Tags" "link" in the "Navigation" "block"
And I follow "Test"
Then I should see "Glossary entries"
And I should see "Just a test concept"
And I should see "Entry not approved"
@@ -0,0 +1,82 @@
@mod @mod_glossary @core_completion
Feature: View activity completion in the glossary activity
In order to have visibility of glossary completion requirements
As a student
I need to be able to view my glossary 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 | glossary |
| course | C1 |
| name | Music history |
| section | 1 |
And I am on the "Music history" "glossary 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 |
| completionentriesenabled | 1 |
| completionentries | 1 |
And I press "Save and display"
And I log out
Scenario: View automatic completion items as a teacher
Given I am on the "Music history" "glossary activity" page logged in as teacher1
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 1" 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" "glossary activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And I am on the "Music history" "glossary activity" page
And I press "Add entry"
And I set the following fields to these values:
| Concept | Blast beats |
| Definition | Repeated fast tempo hits combining bass, snare and cymbal |
And I press "Save changes"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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" "glossary activity" page logged in as teacher1
And I set the field "rating" to "3"
And I press "Rate"
And I log out
When I am on the "Music history" "glossary activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
@javascript
Scenario: Use manual completion
Given I am on the "Music history" "glossary 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" "glossary 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,108 @@
@mod @mod_glossary @core_completion
Feature: Pass grade completion in the glossary activity
In order to have visibility of glossary completion requirements
As a student
I need to be able to view my glossary 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 | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activity" exists:
| activity | glossary |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
When I am on the "Music history" "glossary 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 |
| Ratings > Grade to pass | 50 |
| Add requirements | 1 |
| View the activity | 1 |
| Receive a grade | 1 |
| Passing grade | 1 |
| completionentriesenabled | 1 |
| completionentries | 1 |
And I press "Save and display"
And I log out
Scenario: View automatic completion items as a teacher
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Music history"
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 1" 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
Scenario: View automatic completion items as a failing student
Given I am on the "Music history" "glossary activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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"
When I am on "Course 1" course homepage
And I follow "Music history"
And I press "Add entry"
And I set the following fields to these values:
| Concept | Blast beats |
| Definition | Repeated fast tempo hits combining bass, snare and cymbal |
And I press "Save changes"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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" "glossary activity" page logged in as teacher1
And I set the field "rating" to "3"
And I press "Rate"
And I log out
When I am on the "Music history" "glossary activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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"
Scenario: View automatic completion items as a passing student
Given I am on the "Music history" "glossary activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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"
When I am on "Course 1" course homepage
And I follow "Music history"
And I press "Add entry"
And I set the following fields to these values:
| Concept | Blast beats |
| Definition | Repeated fast tempo hits combining bass, snare and cymbal |
And I press "Save changes"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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 log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Music history"
And I set the field "rating" to "60"
And I press "Rate"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Music history"
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" 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"
@@ -0,0 +1,99 @@
@mod @mod_glossary
Feature: Glossary can be set to various display formats
In order to display different glossary formats
As a teacher
I can set the glossary activity display format
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Given the following "activities" exist:
| activity | course | name |
| glossary | C1 | Glossary 1 |
And the following "mod_glossary > entries" exist:
| glossary | concept | definition |
| Glossary 1 | Entry 1 | Entry 1 definition |
| Glossary 1 | Entry 2 | Entry 2 definition |
Scenario: Glossary display format is entry list style
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
And I set the following fields to these values:
| displayformat | entrylist |
When I press "Save and display"
# Confirm that glossary display format is entry list.
# In this format, the concept definitions are not displayed.
Then I should not see "by Admin User"
And I should not see "Entry 1 definition"
And I should not see "Entry 2 definition"
And ".entrylist" "css_element" should exist
Scenario: Glossary display format is FAQ-style
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
And I set the following fields to these values:
| displayformat | faq |
When I press "Save and display"
# Confirm that glossary format is FAQ.
# In this format, the words Question and Answer are displayed.
Then I should see "Question:"
And I should see "Answer:"
And ".faq" "css_element" should exist
@_file_upload @javascript
Scenario: Glossary display format is full without author style
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
And I set the following fields to these values:
| displayformat | fullwithoutauthor |
And I press "Save and display"
And I press "Add entry"
# Add an entry with an attachment.
And I set the following fields to these values:
| Concept | Entry 3 |
| Definition | Entry 3 definition |
| Attachment | lib/tests/fixtures/gd-logo.png |
When I press "Save changes"
# Confirm that glossary format is full without author style.
# In this format, the image link should exist and author's name should not be visible.
Then "gd-logo.png" "link" should exist
And I should not see "by Admin User"
And ".fullwithoutauthor" "css_element" should exist
@_file_upload @javascript
Scenario: Glossary display format is encyclopedia style
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
And I set the following fields to these values:
| displayformat | encyclopedia |
And I press "Save and display"
And I press "Add entry"
# Add an entry with an attachment.
And I set the following fields to these values:
| Concept | Entry 3 |
| Definition | Entry 3 definition |
| Attachment | lib/tests/fixtures/gd-logo.png |
When I press "Save changes"
# Confirm that glossary format is encyclopedia.
# In this format, the image element should be displayed.
Then "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist
And ".encyclopedia" "css_element" should exist
Scenario Outline: Glossary display format can be set to dictionary, continuous and full with author
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
# Assign the corresponding display format to glossary activity.
And I set the following fields to these values:
| displayformat | <display_format> |
When I press "Save and display"
# Confirm that glossary format is the display format set in the previous step.
Then I should <visibility> "by Admin User"
And ".<display_format>" "css_element" should exist
Examples:
| display_format | visibility |
| dictionary | not see |
| continuous | not see |
| fullwithauthor | see |
@@ -0,0 +1,44 @@
@mod @mod_glossary @_file_upload
Feature: Importing glossary entries
In order to add glossary entries by bulk
As a teacher
I need to be able to import glossary entries from a file
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | idnumber | name |
| glossary | C1 | glossary1 | Glossary 1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| recent_activity | Course | C1 | course-view-* | side-pre |
| tags | Course | C1 | course-view-* | side-pre |
And I am on the "Glossary 1" "glossary activity" page logged in as teacher1
@javascript @block_recent_activity
Scenario: Importing glossary entries and checking the Recent activity block
Given I press "Import entries"
And I upload "mod/glossary/tests/fixtures/texfilter_glossary_en.xml" file to "File to import" filemanager
When I press "Submit"
Then I should see "103" in the "Total entries:" "table_row"
And I should see "103" in the "Imported entries:" "table_row"
And I am on "Course 1" course homepage
And I should see "Added Glossary" in the "Recent activity" "block"
And I should see "New glossary entries:" in the "Recent activity" "block"
@javascript @block_tags
Scenario: Importing glossary entries and checking Tags block
Given I press "Import entries"
And I upload "mod/glossary/tests/fixtures/musicians.xml" file to "File to import" filemanager
When I press "Submit"
And I am on "Course 1" course homepage
And I click on "Beatles" "link" in the "Tags" "block"
Then I should see "Paul McCartney"
@@ -0,0 +1,34 @@
@mod @mod_glossary
Feature: A teacher can choose whether to allow duplicate entries in a glossary
In order to avoid confusion
As a teacher
I need to avoid having duplicate concept definitions
@javascript
Scenario: Prevent duplicate entries
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 "activity" exists:
| course | C1 |
| activity | glossary |
| name | Test glossary name |
| intro | Test glossary description |
| allowduplicatedentries | 0 |
And I am on the "Test glossary name" "glossary activity" page logged in as teacher1
And I add a glossary entry with the following data:
| Concept | Unique concept |
| Definition | I'm the definition of an unique concept |
When I press "Add entry"
And I set the following fields to these values:
| Concept | Unique concept |
| Definition | There is no definition restriction |
And I press "Save changes"
Then I should see "This concept already exists. No duplicates allowed in this glossary."
And I press "Cancel"
@@ -0,0 +1,48 @@
@mod @mod_glossary
Feature: A teacher can choose whether to provide a printer-friendly glossary entries list
In order to print glossaries easily
As a user
I need to provide users a different view to print the glossary contents
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 |
@javascript
Scenario: Printer-friendly glossary view enabled
Given the following "activity" exists:
| course | C1 |
| activity | glossary |
| name | Test glossary name |
| intro | Test glossary description |
| allowprintview | 1 |
And I am on the "Test glossary name" "glossary activity" page logged in as student1
When I add a glossary entry with the following data:
| Concept | Just a test concept |
| Definition | Concept definition |
And I click on "Export entries" "button"
And I click on "Print" "link"
Then I should see "Just a test concept"
@javascript
Scenario: Printer-friendly glossary view disabled
Given the following "activity" exists:
| course | C1 |
| activity | glossary |
| name | Test glossary name |
| intro | Test glossary description |
| allowprintview | 0 |
And I am on the "Test glossary name" "glossary activity" page logged in as student1
When I add a glossary entry with the following data:
| Concept | Just a test concept |
| Definition | Concept definition |
And "//select[contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')]" "xpath_element" should not exist
@@ -0,0 +1,74 @@
@mod @mod_glossary
Feature: Glossary entries can be searched or browsed by alphabet, category, date or author
In order to find entries in a glossary
As a user
I need to search the entries list by keyword, alphabet, category, date and author
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 | displayformat | course | idnumber |
| glossary | Test glossary name | Test glossary description | fullwithauthor | C1 | g1 |
And the following "mod_glossary > categories" exist:
| glossary | name |
| g1 | The ones I like |
| g1 | All for you |
And the following "mod_glossary > entries" exist:
| glossary | concept | definition | user | categories |
| g1 | Eggplant | Sour eggplants | teacher1 | All for you |
| g1 | Cucumber | Sweet cucumber | student1 | The ones I like |
And I am on the "Test glossary name" "glossary activity" page logged in as teacher1
@javascript
Scenario: Search by keyword and browse by alphabet
When I set the field "hook" to "cucumber"
And I press "Search"
Then I should see "Sweet cucumber"
And I should see "Search: cucumber"
And I click on "E" "link" in the ".entrybox" "css_element"
And I should see "Sour eggplants"
And I should not see "Sweet cucumber"
And I click on "X" "link" in the ".entrybox" "css_element"
And I should not see "Sweet cucumber"
And I should see "No entries found in this section"
@javascript
Scenario: Browse by category
When I select "Browse by category" from the "Browse the glossary using this index" singleselect
And I set the field "Categories" to "The ones I like"
Then I should see "Sweet cucumber"
And I should not see "Sour eggplants"
And I set the field "Categories" to "All for you"
And I should see "Sour eggplants"
And I should not see "Sweet cucumber"
@javascript
Scenario: Browse by date
When I select "Browse by date" from the "Browse the glossary using this index" singleselect
And I follow "By creation date"
Then "Delete entry: Eggplant" "link" should appear before "Delete entry: Cucumber" "link"
And I follow "By last update"
And I follow "By last update change to descending"
And "Delete entry: Cucumber" "link" should appear before "Delete entry: Eggplant" "link"
@javascript
Scenario: Browse by author
When I select "Browse by Author" from the "Browse the glossary using this index" singleselect
And I click on "T" "link" in the ".entrybox" "css_element"
Then I should see "Teacher 1"
And I should see "Sour eggplants"
And I should not see "Sweet cucumber"
And I click on "S" "link" in the ".entrybox" "css_element"
And I should see "Student 1"
And I should see "Sweet cucumber"
And I should not see "Sour eggplants"
+176
View File
@@ -0,0 +1,176 @@
<?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_glossary;
/**
* Concept fetching and caching tests.
*
* @package mod_glossary
* @category test
* @copyright 2014 Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class concept_cache_test extends \advanced_testcase {
/**
* Test convect fetching.
*/
public function test_concept_fetching(): void {
global $CFG, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->glossary_linkbydefault = 1;
$CFG->glossary_linkentries = 0;
// Create a test courses.
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$site = $DB->get_record('course', array('id' => SITEID));
// Create a glossary.
$glossary1a = $this->getDataGenerator()->create_module('glossary',
array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1));
$glossary1b = $this->getDataGenerator()->create_module('glossary',
array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1));
$glossary1c = $this->getDataGenerator()->create_module('glossary',
array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 0));
$glossary2 = $this->getDataGenerator()->create_module('glossary',
array('course' => $course2->id, 'mainglossary' => 1, 'usedynalink' => 1));
$glossary3 = $this->getDataGenerator()->create_module('glossary',
array('course' => $site->id, 'mainglossary' => 1, 'usedynalink' => 1, 'globalglossary' => 1));
/** @var mod_glossary_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry1a1 = $generator->create_content($glossary1a, array('concept' => 'first', 'usedynalink' => 1), array('prvni', 'erste'));
$entry1a2 = $generator->create_content($glossary1a, array('concept' => 'A&B', 'usedynalink' => 1));
$entry1a3 = $generator->create_content($glossary1a, array('concept' => 'neee', 'usedynalink' => 0));
$entry1b1 = $generator->create_content($glossary1b, array('concept' => 'second', 'usedynalink' => 1));
$entry1c1 = $generator->create_content($glossary1c, array('concept' => 'third', 'usedynalink' => 1));
$entry31 = $generator->create_content($glossary3, array('concept' => 'global', 'usedynalink' => 1), array('globalni'));
$cat1 = $generator->create_category($glossary1a, array('name' => 'special'), array($entry1a1, $entry1a2));
\mod_glossary\local\concept_cache::reset_caches();
$concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
$this->assertCount(3, $concepts1[0]);
$this->arrayHasKey($concepts1[0], $glossary1a->id);
$this->arrayHasKey($concepts1[0], $glossary1b->id);
$this->arrayHasKey($concepts1[0], $glossary3->id);
$this->assertCount(3, $concepts1[1]);
$this->arrayHasKey($concepts1[1], $glossary1a->id);
$this->arrayHasKey($concepts1[1], $glossary1b->id);
$this->arrayHasKey($concepts1[0], $glossary3->id);
$this->assertCount(5, $concepts1[1][$glossary1a->id]);
foreach($concepts1[1][$glossary1a->id] as $concept) {
$this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
if ($concept->concept === 'first') {
$this->assertEquals($entry1a1->id, $concept->id);
$this->assertEquals($glossary1a->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'prvni') {
$this->assertEquals($entry1a1->id, $concept->id);
$this->assertEquals($glossary1a->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'erste') {
$this->assertEquals($entry1a1->id, $concept->id);
$this->assertEquals($glossary1a->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'A&amp;B') {
$this->assertEquals($entry1a2->id, $concept->id);
$this->assertEquals($glossary1a->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'special') {
$this->assertEquals($cat1->id, $concept->id);
$this->assertEquals($glossary1a->id, $concept->glossaryid);
$this->assertEquals(1, $concept->category);
} else {
$this->fail('Unexpected concept: ' . $concept->concept);
}
}
$this->assertCount(1, $concepts1[1][$glossary1b->id]);
foreach($concepts1[1][$glossary1b->id] as $concept) {
$this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
if ($concept->concept === 'second') {
$this->assertEquals($entry1b1->id, $concept->id);
$this->assertEquals($glossary1b->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else {
$this->fail('Unexpected concept: ' . $concept->concept);
}
}
$this->assertCount(2, $concepts1[1][$glossary3->id]);
foreach($concepts1[1][$glossary3->id] as $concept) {
$this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
if ($concept->concept === 'global') {
$this->assertEquals($entry31->id, $concept->id);
$this->assertEquals($glossary3->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'globalni') {
$this->assertEquals($entry31->id, $concept->id);
$this->assertEquals($glossary3->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else {
$this->fail('Unexpected concept: ' . $concept->concept);
}
}
$concepts3 = \mod_glossary\local\concept_cache::get_concepts($site->id);
$this->assertCount(1, $concepts3[0]);
$this->arrayHasKey($concepts3[0], $glossary3->id);
$this->assertCount(1, $concepts3[1]);
$this->arrayHasKey($concepts3[0], $glossary3->id);
foreach($concepts3[1][$glossary3->id] as $concept) {
$this->assertSame(array('id', 'glossaryid', 'concept', 'casesensitive', 'category', 'fullmatch'), array_keys((array)$concept));
if ($concept->concept === 'global') {
$this->assertEquals($entry31->id, $concept->id);
$this->assertEquals($glossary3->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else if ($concept->concept === 'globalni') {
$this->assertEquals($entry31->id, $concept->id);
$this->assertEquals($glossary3->id, $concept->glossaryid);
$this->assertEquals(0, $concept->category);
} else {
$this->fail('Unexpected concept: ' . $concept->concept);
}
}
$concepts2 = \mod_glossary\local\concept_cache::get_concepts($course2->id);
$this->assertEquals($concepts3, $concepts2);
// Test uservisible flag.
set_config('enableavailability', 1);
$glossary1d = $this->getDataGenerator()->create_module('glossary',
array('course' => $course1->id, 'mainglossary' => 1, 'usedynalink' => 1,
'availability' => json_encode(\core_availability\tree::get_root_json(
array(\availability_group\condition::get_json())))));
$entry1d1 = $generator->create_content($glossary1d, array('concept' => 'membersonly', 'usedynalink' => 1));
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course1->id);
$this->getDataGenerator()->enrol_user($user->id, $course2->id);
\mod_glossary\local\concept_cache::reset_caches();
$concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
$this->assertCount(4, $concepts1[0]);
$this->assertCount(4, $concepts1[1]);
$this->setUser($user);
\course_modinfo::clear_instance_cache();
\mod_glossary\local\concept_cache::reset_caches();
$concepts1 = \mod_glossary\local\concept_cache::get_concepts($course1->id);
$this->assertCount(3, $concepts1[0]);
$this->assertCount(3, $concepts1[1]);
}
}
@@ -0,0 +1,217 @@
<?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_glossary
* @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_glossary;
use advanced_testcase;
use cm_info;
use coding_exception;
use mod_glossary\completion\custom_completion;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
/**
* Class for unit testing mod_glossary/activity_custom_completion.
*
* @package mod_glossary
* @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 submitted' => [
'completionentries', COMPLETION_ENABLED, 0, COMPLETION_INCOMPLETE, null
],
'Rule available, user has submitted' => [
'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', $rules[0]);
}
/**
* 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 submit available' => [
COMPLETION_ENABLED, ['completionentries']
],
'Completion submit 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());
}
}
+480
View File
@@ -0,0 +1,480 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for lib.php
*
* @package mod_glossary
* @category test
* @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_glossary\event;
/**
* Unit tests for glossary events.
*
* @package mod_glossary
* @category test
* @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Test comment_created event.
*/
public function test_comment_created(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
// Create a record for adding comment.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id);
$cmt = new \stdClass();
$cmt->component = 'mod_glossary';
$cmt->context = $context;
$cmt->course = $course;
$cmt->cm = $cm;
$cmt->area = 'glossary_entry';
$cmt->itemid = $entry->id;
$cmt->showcount = true;
$comment = new \comment($cmt);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$comment->add('New comment');
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\comment_created', $event);
$this->assertEquals($context, $event->get_context());
$url = new \moodle_url('/mod/glossary/view.php', array('id' => $glossary->cmid));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
}
/**
* Test comment_deleted event.
*/
public function test_comment_deleted(): void {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
// Create a record for deleting comment.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id);
$cmt = new \stdClass();
$cmt->component = 'mod_glossary';
$cmt->context = $context;
$cmt->course = $course;
$cmt->cm = $cm;
$cmt->area = 'glossary_entry';
$cmt->itemid = $entry->id;
$cmt->showcount = true;
$comment = new \comment($cmt);
$newcomment = $comment->add('New comment 1');
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$comment->delete($newcomment->id);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\comment_deleted', $event);
$this->assertEquals($context, $event->get_context());
$url = new \moodle_url('/mod/glossary/view.php', array('id' => $glossary->cmid));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
}
public function test_course_module_viewed(): void {
global $DB;
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
$dbcourse = $DB->get_record('course', array('id' => $course->id));
$dbglossary = $DB->get_record('glossary', array('id' => $glossary->id));
$context = \context_module::instance($glossary->cmid);
$mode = 'letter';
$event = \mod_glossary\event\course_module_viewed::create(array(
'objectid' => $dbglossary->id,
'context' => $context,
'other' => array('mode' => $mode)
));
$event->add_record_snapshot('course', $dbcourse);
$event->add_record_snapshot('glossary', $dbglossary);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\course_module_viewed', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEquals($glossary->id, $event->objectid);
$this->assertEquals(new \moodle_url('/mod/glossary/view.php', array('id' => $glossary->cmid, 'mode' => $mode)), $event->get_url());
$this->assertEventContextNotUsed($event);
}
public function test_course_module_instance_list_viewed(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$event = \mod_glossary\event\course_module_instance_list_viewed::create(array(
'context' => \context_course::instance($course->id)
));
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\course_module_instance_list_viewed', $event);
$this->assertEquals(CONTEXT_COURSE, $event->contextlevel);
$this->assertEquals($course->id, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_entry_created(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$context = \context_module::instance($glossary->cmid);
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$eventparams = array(
'context' => $context,
'objectid' => $entry->id,
'other' => array('concept' => $entry->concept)
);
$event = \mod_glossary\event\entry_created::create($eventparams);
$event->add_record_snapshot('glossary_entries', $entry);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_created', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_entry_updated(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$context = \context_module::instance($glossary->cmid);
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$eventparams = array(
'context' => $context,
'objectid' => $entry->id,
'other' => array('concept' => $entry->concept)
);
$event = \mod_glossary\event\entry_updated::create($eventparams);
$event->add_record_snapshot('glossary_entries', $entry);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_updated', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_entry_deleted(): void {
global $DB;
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$context = \context_module::instance($glossary->cmid);
$prevmode = 'view';
$hook = 'ALL';
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$DB->delete_records('glossary_entries', array('id' => $entry->id));
$eventparams = array(
'context' => $context,
'objectid' => $entry->id,
'other' => array(
'mode' => $prevmode,
'hook' => $hook,
'concept' => $entry->concept
)
);
$event = \mod_glossary\event\entry_deleted::create($eventparams);
$event->add_record_snapshot('glossary_entries', $entry);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_deleted', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_category_created(): void {
global $DB;
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$context = \context_module::instance($glossary->cmid);
// Create category and trigger event.
$category = new \stdClass();
$category->name = 'New category';
$category->usedynalink = 0;
$category->id = $DB->insert_record('glossary_categories', $category);
$event = \mod_glossary\event\category_created::create(array(
'context' => $context,
'objectid' => $category->id
));
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\category_created', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
// Update category and trigger event.
$category->name = 'Updated category';
$DB->update_record('glossary_categories', $category);
$event = \mod_glossary\event\category_updated::create(array(
'context' => $context,
'objectid' => $category->id
));
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\category_updated', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
// Delete category and trigger event.
$category = $DB->get_record('glossary_categories', array('id' => $category->id));
$DB->delete_records('glossary_categories', array('id' => $category->id));
$event = \mod_glossary\event\category_deleted::create(array(
'context' => $context,
'objectid' => $category->id
));
$event->add_record_snapshot('glossary_categories', $category);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\category_deleted', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_entry_approved(): void {
global $DB;
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_user();
$rolestudent = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $rolestudent->id);
$teacher = $this->getDataGenerator()->create_user();
$roleteacher = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $roleteacher->id);
$this->setUser($teacher);
$glossary = $this->getDataGenerator()->create_module('glossary',
array('course' => $course, 'defaultapproval' => 0));
$context = \context_module::instance($glossary->cmid);
$this->setUser($student);
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$this->assertEquals(0, $entry->approved);
// Approve entry, trigger and validate event.
$this->setUser($teacher);
$newentry = new \stdClass();
$newentry->id = $entry->id;
$newentry->approved = true;
$newentry->timemodified = time();
$DB->update_record("glossary_entries", $newentry);
$params = array(
'context' => $context,
'objectid' => $entry->id
);
$event = \mod_glossary\event\entry_approved::create($params);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_approved', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
// Disapprove entry, trigger and validate event.
$this->setUser($teacher);
$newentry = new \stdClass();
$newentry->id = $entry->id;
$newentry->approved = false;
$newentry->timemodified = time();
$DB->update_record("glossary_entries", $newentry);
$params = array(
'context' => $context,
'objectid' => $entry->id
);
$event = \mod_glossary\event\entry_disapproved::create($params);
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_disapproved', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
public function test_entry_viewed(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$context = \context_module::instance($glossary->cmid);
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry = $glossarygenerator->create_content($glossary);
$event = \mod_glossary\event\entry_viewed::create(array(
'objectid' => $entry->id,
'context' => $context
));
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_glossary\event\entry_viewed', $event);
$this->assertEquals(CONTEXT_MODULE, $event->contextlevel);
$this->assertEquals($glossary->cmid, $event->contextinstanceid);
$this->assertEventContextNotUsed($event);
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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_glossary\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use core_external\external_api;
use externallib_advanced_testcase;
/**
* External function test for delete_entry.
*
* @package mod_glossary
* @category external
* @covers \mod_glossary\external\delete_entry
* @since Moodle 3.10
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class delete_entry_test extends externallib_advanced_testcase {
/**
* Test the behaviour of delete_entry().
*/
public function test_delete_entry() {
global $DB;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
// Test entry creator can delete.
$result = delete_entry::execute($entry->id);
$result = external_api::clean_returnvalue(delete_entry::execute_returns(), $result);
$this->assertTrue($result['result']);
$this->assertEquals(0, $DB->count_records('glossary_entries', ['id' => $entry->id]));
// Test admin can delete.
$this->setAdminUser();
$entry = $gg->create_content($glossary);
$result = delete_entry::execute($entry->id);
$result = external_api::clean_returnvalue(delete_entry::execute_returns(), $result);
$this->assertTrue($result['result']);
$this->assertEquals(0, $DB->count_records('glossary_entries', ['id' => $entry->id]));
$entry = $gg->create_content($glossary);
// Test a different student is not able to delete.
$this->setUser($anotherstudent);
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
delete_entry::execute($entry->id);
}
}
File diff suppressed because it is too large Load Diff
+72
View File
@@ -0,0 +1,72 @@
<?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_glossary\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use core_external\external_api;
use externallib_advanced_testcase;
/**
* External function test for prepare_entry.
*
* @package mod_glossary
* @category external
* @covers \mod_glossary\external\prepare_entry
* @since Moodle 3.10
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class prepare_entry_test extends externallib_advanced_testcase {
/**
* test_prepare_entry
*/
public function test_prepare_entry() {
global $USER;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setAdminUser();
$aliases = ['alias1', 'alias2'];
$entry = $gg->create_content(
$glossary,
['approved' => 1, 'userid' => $USER->id],
$aliases
);
$cat1 = $gg->create_category($glossary, [], [$entry]);
$gg->create_category($glossary);
$return = prepare_entry::execute($entry->id);
$return = external_api::clean_returnvalue(prepare_entry::execute_returns(), $return);
$this->assertNotEmpty($return['inlineattachmentsid']);
$this->assertNotEmpty($return['attachmentsid']);
$this->assertEquals($aliases, $return['aliases']);
$this->assertEquals([$cat1->id], $return['categories']);
$this->assertCount(2, $return['areas']);
$this->assertNotEmpty($return['areas'][0]['options']);
$this->assertNotEmpty($return['areas'][1]['options']);
}
}
+290
View File
@@ -0,0 +1,290 @@
<?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_glossary\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use core_external\external_api;
use externallib_advanced_testcase;
use mod_glossary_external;
use context_module;
use context_user;
use core_external\util as external_util;
/**
* External function test for update_entry.
*
* @package mod_glossary
* @category external
* @covers \mod_glossary\external\update_entry
* @since Moodle 3.10
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class update_entry_test extends externallib_advanced_testcase {
/**
* test_update_entry_without_optional_settings
*/
public function test_update_entry_without_optional_settings() {
global $CFG, $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$this->setAdminUser();
$concept = 'A concept';
$definition = '<p>A definition</p>';
$return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
$return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
$entryid = $return['entryid'];
// Updates the entry.
$concept .= ' Updated!';
$definition .= ' <p>Updated!</p>';
$return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
$return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
// Get entry from DB.
$entry = $DB->get_record('glossary_entries', ['id' => $entryid]);
$this->assertEquals($concept, $entry->concept);
$this->assertEquals($definition, $entry->definition);
$this->assertEquals($CFG->glossary_linkentries, $entry->usedynalink);
$this->assertEquals($CFG->glossary_casesensitive, $entry->casesensitive);
$this->assertEquals($CFG->glossary_fullmatch, $entry->fullmatch);
$this->assertEmpty($DB->get_records('glossary_alias', ['entryid' => $entryid]));
$this->assertEmpty($DB->get_records('glossary_entries_categories', ['entryid' => $entryid]));
}
/**
* test_update_entry_duplicated
*/
public function test_update_entry_duplicated() {
global $CFG, $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id, 'allowduplicatedentries' => 1]);
// Create three entries.
$this->setAdminUser();
$concept = 'A concept';
$definition = '<p>A definition</p>';
mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
$concept = 'B concept';
$definition = '<p>B definition</p>';
mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
$concept = 'Another concept';
$definition = '<p>Another definition</p>';
$return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
$return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
$entryid = $return['entryid'];
// Updates the entry using an existing entry name when duplicateds are allowed.
$concept = 'A concept';
update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
// Updates the entry using an existing entry name when duplicateds are NOT allowed.
$DB->set_field('glossary', 'allowduplicatedentries', 0, ['id' => $glossary->id]);
$concept = 'B concept';
$this->expectExceptionMessage(get_string('errconceptalreadyexists', 'glossary'));
update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
}
/**
* test_update_entry_with_aliases
*/
public function test_update_entry_with_aliases() {
global $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$this->setAdminUser();
$concept = 'A concept';
$definition = 'A definition';
$paramaliases = 'abc, def, gez';
$options = [
[
'name' => 'aliases',
'value' => $paramaliases,
]
];
$return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
$entryid = $return['entryid'];
// Updates the entry.
$newaliases = 'abz, xyz';
$options[0]['value'] = $newaliases;
$return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
$aliases = $DB->get_records('glossary_alias', ['entryid' => $entryid]);
$this->assertCount(2, $aliases);
foreach ($aliases as $alias) {
$this->assertStringContainsString($alias->alias, $newaliases);
}
}
/**
* test_update_entry_in_categories
*/
public function test_update_entry_in_categories() {
global $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$cat1 = $gg->create_category($glossary);
$cat2 = $gg->create_category($glossary);
$cat3 = $gg->create_category($glossary);
$this->setAdminUser();
$concept = 'A concept';
$definition = 'A definition';
$paramcategories = "$cat1->id, $cat2->id";
$options = [
[
'name' => 'categories',
'value' => $paramcategories,
]
];
$return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
$entryid = $return['entryid'];
// Updates the entry.
$newcategories = "$cat1->id, $cat3->id";
$options[0]['value'] = $newcategories;
$return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
$categories = $DB->get_records('glossary_entries_categories', ['entryid' => $entryid]);
$this->assertCount(2, $categories);
foreach ($categories as $category) {
$this->assertStringContainsString($category->categoryid, $newcategories);
}
}
/**
* test_update_entry_with_attachments
*/
public function test_update_entry_with_attachments() {
global $DB, $USER;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$context = context_module::instance($glossary->cmid);
$this->setAdminUser();
$concept = 'A concept';
$definition = 'A definition';
// Draft files.
$draftidinlineattach = file_get_unused_draft_itemid();
$draftidattach = file_get_unused_draft_itemid();
$usercontext = context_user::instance($USER->id);
$filerecordinline = [
'contextid' => $usercontext->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftidinlineattach,
'filepath' => '/',
'filename' => 'shouldbeanimage.png',
];
$fs = get_file_storage();
// Create a file in a draft area for regular attachments.
$filerecordattach = $filerecordinline;
$attachfilename = 'attachment.txt';
$filerecordattach['filename'] = $attachfilename;
$filerecordattach['itemid'] = $draftidattach;
$fs->create_file_from_string($filerecordinline, 'image contents (not really)');
$fs->create_file_from_string($filerecordattach, 'simple text attachment');
$options = [
[
'name' => 'inlineattachmentsid',
'value' => $draftidinlineattach,
],
[
'name' => 'attachmentsid',
'value' => $draftidattach,
]
];
$return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
$entryid = $return['entryid'];
$entry = $DB->get_record('glossary_entries', ['id' => $entryid]);
list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
$entry = file_prepare_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
$entry->id);
$entry = file_prepare_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary', 'attachment',
$entry->id);
$inlineattachmentsid = $entry->definition_editor['itemid'];
$attachmentsid = $entry->attachment_filemanager;
// Change the file areas.
// Delete one inline editor file.
$selectedfile = (object)[
'filename' => $filerecordinline['filename'],
'filepath' => $filerecordinline['filepath'],
];
$return = repository_delete_selected_files($usercontext, 'user', 'draft', $inlineattachmentsid, [$selectedfile]);
// Add more files.
$filerecordinline['filename'] = 'newvideo.mp4';
$filerecordinline['itemid'] = $inlineattachmentsid;
$filerecordattach['filename'] = 'newattach.txt';
$filerecordattach['itemid'] = $attachmentsid;
$fs->create_file_from_string($filerecordinline, 'image contents (not really)');
$fs->create_file_from_string($filerecordattach, 'simple text attachment');
// Updates the entry.
$options[0]['value'] = $inlineattachmentsid;
$options[1]['value'] = $attachmentsid;
$return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
$return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
$editorfiles = external_util::get_area_files($context->id, 'mod_glossary', 'entry', $entryid);
$attachmentfiles = external_util::get_area_files($context->id, 'mod_glossary', 'attachment', $entryid);
$this->assertCount(1, $editorfiles);
$this->assertCount(2, $attachmentfiles);
$this->assertEquals('newvideo.mp4', $editorfiles[0]['filename']);
$this->assertEquals('attachment.txt', $attachmentfiles[0]['filename']);
$this->assertEquals('newattach.txt', $attachmentfiles[1]['filename']);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<GLOSSARY>
<INFO>
<NAME>Musicians</NAME>
<INTRO></INTRO>
<INTROFORMAT>1</INTROFORMAT>
<ALLOWDUPLICATEDENTRIES>0</ALLOWDUPLICATEDENTRIES>
<DISPLAYFORMAT>dictionary</DISPLAYFORMAT>
<SHOWSPECIAL>1</SHOWSPECIAL>
<SHOWALPHABET>1</SHOWALPHABET>
<SHOWALL>1</SHOWALL>
<ALLOWCOMMENTS>0</ALLOWCOMMENTS>
<USEDYNALINK>1</USEDYNALINK>
<DEFAULTAPPROVAL>1</DEFAULTAPPROVAL>
<GLOBALGLOSSARY>0</GLOBALGLOSSARY>
<ENTBYPAGE>10</ENTBYPAGE>
<ENTRIES>
<ENTRY>
<CONCEPT>Paul McCartney</CONCEPT>
<DEFINITION>&lt;p&gt;Popular British composer, guitarist, and vocalist. &lt;br&gt;&lt;/p&gt;</DEFINITION>
<FORMAT>1</FORMAT>
<USEDYNALINK>1</USEDYNALINK>
<CASESENSITIVE>0</CASESENSITIVE>
<FULLMATCH>0</FULLMATCH>
<TEACHERENTRY>1</TEACHERENTRY>
<TAGS>
<TAG>Beatles</TAG>
<TAG>The Quarrymen</TAG>
<TAG>Wings</TAG>
</TAGS>
</ENTRY>
</ENTRIES>
</INFO>
</GLOSSARY>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,110 @@
<?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_glossary.
*
* @package mod_glossary
* @category test
* @copyright 2021 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_glossary_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 [
'categories' => [
'singular' => 'category',
'datagenerator' => 'category',
'required' => ['glossary', 'name'],
'switchids' => ['glossary' => 'glossaryid'],
],
'entries' => [
'singular' => 'entry',
'datagenerator' => 'entry',
'required' => ['glossary', 'concept', 'definition'],
'switchids' => ['glossary' => 'glossaryid', 'user' => 'userid'],
],
];
}
/**
* Get the glossary id using an activity idnumber.
*
* @param string $idnumber
* @return int The glossary id
*/
protected function get_glossary_id(string $idnumber): int {
$cm = $this->get_cm_by_activity_name('glossary', $idnumber);
return $cm->instance;
}
/**
* Add a category.
*
* @param array $data Category data.
*/
public function process_category(array $data) {
global $DB;
$glossary = $DB->get_record('glossary', ['id' => $data['glossaryid']], '*', MUST_EXIST);
unset($data['glossaryid']);
$this->get_data_generator()->create_category($glossary, $data);
}
/**
* Preprocess entry data.
*
* @param array $data Raw data.
* @return array Processed data.
*/
protected function preprocess_entry(array $data): array {
if (isset($data['categories'])) {
$categorynames = array_map('trim', explode(',', $data['categories']));
$categoryids = array_map(function ($categoryname) {
global $DB;
if (!$id = $DB->get_field('glossary_categories', 'id', ['name' => $categoryname])) {
throw new Exception('The specified category with name "' . $categoryname . '" could not be found.');
}
return $id;
}, $categorynames);
$data['categoryids'] = $categoryids;
unset($data['categories']);
}
return $data;
}
/**
* Get the module data generator.
*
* @return mod_glossary_generator Glossary data generator.
*/
protected function get_data_generator() {
return $this->componentdatagenerator;
}
}
+203
View File
@@ -0,0 +1,203 @@
<?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/>.
/**
* mod_glossary data generator.
*
* @package mod_glossary
* @category test
* @copyright 2013 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* mod_glossary data generator class.
*
* @package mod_glossary
* @category test
* @copyright 2013 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_glossary_generator extends testing_module_generator {
/**
* @var int keep track of how many entries have been created.
*/
protected $entrycount = 0;
/**
* @var int keep track of how many entries have been created.
*/
protected $categorycount = 0;
/**
* To be called from data reset code only,
* do not use in tests.
* @return void
*/
public function reset() {
$this->entrycount = 0;
$this->categorycount = 0;
parent::reset();
}
public function create_instance($record = null, array $options = null) {
global $CFG;
// Add default values for glossary.
$record = (array)$record + array(
'globalglossary' => 0,
'mainglossary' => 0,
'defaultapproval' => $CFG->glossary_defaultapproval,
'allowduplicatedentries' => $CFG->glossary_dupentries,
'allowcomments' => $CFG->glossary_allowcomments,
'usedynalink' => $CFG->glossary_linkbydefault,
'displayformat' => 'dictionary',
'approvaldisplayformat' => 'default',
'entbypage' => !empty($CFG->glossary_entbypage) ? $CFG->glossary_entbypage : 10,
'showalphabet' => 1,
'showall' => 1,
'showspecial' => 1,
'allowprintview' => 1,
'rsstype' => 0,
'rssarticles' => 0,
'grade' => 100,
'assessed' => 0,
);
return parent::create_instance($record, (array)$options);
}
public function create_category($glossary, $record = array(), $entries = array()) {
global $CFG, $DB;
$this->categorycount++;
$record = (array)$record + array(
'name' => 'Glossary category '.$this->categorycount,
'usedynalink' => $CFG->glossary_linkbydefault,
);
$record['glossaryid'] = $glossary->id;
$id = $DB->insert_record('glossary_categories', $record);
if ($entries) {
foreach ($entries as $entry) {
$ce = new stdClass();
$ce->categoryid = $id;
$ce->entryid = $entry->id;
$DB->insert_record('glossary_entries_categories', $ce);
}
}
return $DB->get_record('glossary_categories', array('id' => $id), '*', MUST_EXIST);
}
public function create_content($glossary, $record = array(), $aliases = array()) {
global $DB;
$entry = $this->create_entry((array)$record + ['glossaryid' => $glossary->id]);
if ($aliases) {
foreach ($aliases as $alias) {
$ar = new stdClass();
$ar->entryid = $entry->id;
$ar->alias = $alias;
$DB->insert_record('glossary_alias', $ar);
}
}
if (array_key_exists('tags', $record)) {
$tags = is_array($record['tags']) ? $record['tags'] : preg_split('/,/', $record['tags']);
core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $entry->id,
context_module::instance($glossary->cmid), $tags);
}
return $entry;
}
/**
* Create an entry.
*
* @param array $data Data to create the entry record.
* In addition to columns in the entry table, the following attributes are supported:
* - glossaryid (required): Id of the glossary where the entry will be created.
* - categoryids: Array of ids for the categories this entry belongs to.
*
* @return stdClass Entry record.
*/
public function create_entry(array $data): stdClass {
global $DB, $USER, $CFG;
// Prepare glossary.
$coursemodule = get_coursemodule_from_instance('glossary', $data['glossaryid']);
$glossary = $DB->get_record('glossary', ['id' => $data['glossaryid']], '*', MUST_EXIST);
$glossary->cmid = $coursemodule->id;
unset($data['glossaryid']);
// Prepare category ids.
$categoryids = $data['categoryids'] ?? [];
unset($data['categoryids']);
// Create entry.
$this->entrycount++;
$now = time();
$record = $data + [
'glossaryid' => $glossary->id,
'timecreated' => $now,
'timemodified' => $now,
'userid' => $USER->id,
'concept' => 'Glossary entry '.$this->entrycount,
'definition' => 'Definition of glossary entry '.$this->entrycount,
'definitionformat' => FORMAT_MOODLE,
'definitiontrust' => 0,
'usedynalink' => $CFG->glossary_linkentries,
'casesensitive' => $CFG->glossary_casesensitive,
'fullmatch' => $CFG->glossary_fullmatch
];
if (!isset($record['teacherentry']) || !isset($record['approved'])) {
$context = context_module::instance($glossary->cmid);
if (!isset($record['teacherentry'])) {
$record['teacherentry'] = has_capability('mod/glossary:manageentries', $context, $record['userid']);
}
if (!isset($record['approved'])) {
$defaultapproval = $glossary->defaultapproval;
$record['approved'] = ($defaultapproval || has_capability('mod/glossary:approve', $context));
}
}
$id = $DB->insert_record('glossary_entries', $record);
foreach ($categoryids as $categoryid) {
$DB->insert_record('glossary_entries_categories', ['entryid' => $id, 'categoryid' => $categoryid]);
}
$entries = $DB->get_record('glossary_entries', ['id' => $id], '*', MUST_EXIST);
if (isset($record['tags'])) {
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
$tags = is_array($record['tags']) ? $record['tags'] : explode(',', $record['tags']);
core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $id,
context_module::instance($cm->id), $tags);
}
return $entries;
}
}
+85
View File
@@ -0,0 +1,85 @@
<?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_glossary;
/**
* Genarator tests class for mod_glossary.
*
* @package mod_glossary
* @category test
* @copyright 2013 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends \advanced_testcase {
public function test_create_instance(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$this->assertFalse($DB->record_exists('glossary', array('course' => $course->id)));
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
$records = $DB->get_records('glossary', array('course' => $course->id), 'id');
$this->assertCount(1, $records);
$this->assertTrue(array_key_exists($glossary->id, $records));
$params = array('course' => $course->id, 'name' => 'Another glossary');
$glossary = $this->getDataGenerator()->create_module('glossary', $params);
$records = $DB->get_records('glossary', array('course' => $course->id), 'id');
$this->assertCount(2, $records);
$this->assertEquals('Another glossary', $records[$glossary->id]->name);
}
public function test_create_content(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course));
/** @var mod_glossary_generator $glossarygenerator */
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$entry1 = $glossarygenerator->create_content($glossary);
$entry2 = $glossarygenerator->create_content($glossary,
array('concept' => 'Custom concept', 'tags' => array('Cats', 'mice')), array('alias1', 'alias2'));
$records = $DB->get_records('glossary_entries', array('glossaryid' => $glossary->id), 'id');
$this->assertCount(2, $records);
$this->assertEquals($entry1->id, $records[$entry1->id]->id);
$this->assertEquals($entry2->id, $records[$entry2->id]->id);
$this->assertEquals('Custom concept', $records[$entry2->id]->concept);
$this->assertEquals(array('Cats', 'mice'),
array_values(\core_tag_tag::get_item_tags_array('mod_glossary', 'glossary_entries', $entry2->id)));
$aliases = $DB->get_records_menu('glossary_alias', array('entryid' => $entry2->id), 'id ASC', 'id, alias');
$this->assertSame(array('alias1', 'alias2'), array_values($aliases));
// Test adding of category to entry.
$categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
$this->assertCount(0, $categories);
$entry3 = $glossarygenerator->create_content($glossary, array('concept' => 'In category'));
$category1 = $glossarygenerator->create_category($glossary, array());
$categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
$this->assertCount(1, $categories);
$category2 = $glossarygenerator->create_category($glossary, array('name' => 'Some category'), array($entry2, $entry3));
$categories = $DB->get_records('glossary_categories', array('glossaryid' => $glossary->id));
$this->assertCount(2, $categories);
$members = $DB->get_records_menu('glossary_entries_categories', array('categoryid' => $category2->id), 'id ASC', 'id, entryid');
$this->assertSame(array($entry2->id, $entry3->id), array_values($members));
}
}
+770
View File
@@ -0,0 +1,770 @@
<?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/>.
/**
* Glossary lib tests.
*
* @package mod_glossary
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_glossary;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/glossary/lib.php');
require_once($CFG->dirroot . '/mod/glossary/locallib.php');
/**
* Glossary lib testcase.
*
* @package mod_glossary
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
public function test_glossary_view(): void {
global $CFG;
$origcompletion = $CFG->enablecompletion;
$CFG->enablecompletion = true;
$this->resetAfterTest(true);
// Generate all the things.
$c1 = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$g1 = $this->getDataGenerator()->create_module('glossary', array(
'course' => $c1->id,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionview' => 1
));
$g2 = $this->getDataGenerator()->create_module('glossary', array(
'course' => $c1->id,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionview' => 1
));
$u1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($u1->id, $c1->id);
$modinfo = \course_modinfo::instance($c1->id);
$cm1 = $modinfo->get_cm($g1->cmid);
$cm2 = $modinfo->get_cm($g2->cmid);
$ctx1 = $cm1->context;
$completion = new \completion_info($c1);
$this->setUser($u1);
// Confirm what we've set up.
$this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed);
$this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate);
$this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed);
$this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate);
// Simulate the view call.
$sink = $this->redirectEvents();
glossary_view($g1, $c1, $cm1, $ctx1, 'letter');
$events = $sink->get_events();
// Assertions.
$this->assertCount(3, $events);
$this->assertEquals('\core\event\course_module_completion_updated', $events[0]->eventname);
$this->assertEquals('\core\event\course_module_completion_updated', $events[1]->eventname);
$this->assertEquals('\mod_glossary\event\course_module_viewed', $events[2]->eventname);
$this->assertEquals($g1->id, $events[2]->objectid);
$this->assertEquals('letter', $events[2]->other['mode']);
$this->assertEquals(COMPLETION_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed);
$this->assertEquals(COMPLETION_COMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate);
$this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed);
$this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate);
// Tear down.
$sink->close();
$CFG->enablecompletion = $origcompletion;
}
public function test_glossary_entry_view(): void {
$this->resetAfterTest(true);
// Generate all the things.
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$c1 = $this->getDataGenerator()->create_course();
$g1 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id));
$e1 = $gg->create_content($g1);
$u1 = $this->getDataGenerator()->create_user();
$ctx = \context_module::instance($g1->cmid);
$this->getDataGenerator()->enrol_user($u1->id, $c1->id);
// Assertions.
$sink = $this->redirectEvents();
glossary_entry_view($e1, $ctx);
$events = $sink->get_events();
$this->assertCount(1, $events);
$this->assertEquals('\mod_glossary\event\entry_viewed', $events[0]->eventname);
$this->assertEquals($e1->id, $events[0]->objectid);
$sink->close();
}
public function test_glossary_core_calendar_provide_event_action(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_glossary_core_calendar_provide_event_action_as_non_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Now log out.
$CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
// Confirm the event is not shown at all.
$this->assertNull($actionevent);
}
public function test_glossary_core_calendar_provide_event_action_for_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a student.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create the activity.
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Now log out.
$CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_glossary_core_calendar_provide_event_action_in_hidden_section(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a student.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create the activity.
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Set sections 0 as hidden.
set_section_visible($course->id, 0, 0);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
// Confirm the event is not shown at all.
$this->assertNull($actionevent);
}
public function test_glossary_core_calendar_provide_event_action_already_completed(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Get some additional data.
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory);
// Ensure result was null.
$this->assertNull($actionevent);
}
public function test_glossary_core_calendar_provide_event_action_already_completed_for_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$CFG->enablecompletion = 1;
// Create a course.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
// Create a student.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create the activity.
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Get some additional data.
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $glossary->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed for the user.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm, $student->id);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_glossary_core_calendar_provide_event_action($event, $factory, $student->id);
// Ensure result was null.
$this->assertNull($actionevent);
}
/**
* Creates an action event.
*
* @param int $courseid The course id.
* @param int $instanceid The instance id.
* @param string $eventtype The event type.
* @return bool|calendar_event
*/
private function create_action_event($courseid, $instanceid, $eventtype) {
$event = new \stdClass();
$event->name = 'Calendar event';
$event->modulename = 'glossary';
$event->courseid = $courseid;
$event->instance = $instanceid;
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->eventtype = $eventtype;
$event->timestart = time();
return \calendar_event::create($event);
}
/**
* Test the callback responsible for returning the completion rule descriptions.
* This function should work given either an instance of the module (cm_info), such as when checking the active rules,
* or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
*/
public function test_mod_glossary_completion_get_active_rule_descriptions(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
$glossary1 = $this->getDataGenerator()->create_module('glossary', [
'course' => $course->id,
'completion' => 2,
'completionentries' => 3
]);
$glossary2 = $this->getDataGenerator()->create_module('glossary', [
'course' => $course->id,
'completion' => 2,
'completionentries' => 0
]);
$cm1 = \cm_info::create(get_coursemodule_from_instance('glossary', $glossary1->id));
$cm2 = \cm_info::create(get_coursemodule_from_instance('glossary', $glossary2->id));
// Data for the stdClass input type.
// This type of input would occur when checking the default completion rules for an activity type, where we don't have
// any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
$moddefaults = new \stdClass();
$moddefaults->customdata = ['customcompletionrules' => ['completionentries' => 3]];
$moddefaults->completion = 2;
$activeruledescriptions = [get_string('completionentriesdesc', 'glossary', $glossary1->completionentries)];
$this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
$this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($cm2), []);
$this->assertEquals(mod_glossary_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
$this->assertEquals(mod_glossary_get_completion_active_rule_descriptions(new \stdClass()), []);
}
public function test_mod_glossary_get_tagged_entries(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$course3 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course1 = $this->getDataGenerator()->create_course();
// Create and enrol a student.
$student = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
$this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
// Create glossaries and entries.
$glossary1 = $this->getDataGenerator()->create_module('glossary', array('course' => $course1->id));
$glossary2 = $this->getDataGenerator()->create_module('glossary', array('course' => $course2->id));
$glossary3 = $this->getDataGenerator()->create_module('glossary', array('course' => $course3->id));
$entry11 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats', 'Dogs')));
$entry12 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats', 'mice')));
$entry13 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats')));
$entry14 = $glossarygenerator->create_content($glossary1);
$entry15 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats')));
$entry16 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats'), 'approved' => false));
$entry17 = $glossarygenerator->create_content($glossary1, array('tags' => array('Cats'), 'approved' => false, 'userid' => $student->id));
$entry21 = $glossarygenerator->create_content($glossary2, array('tags' => array('Cats')));
$entry22 = $glossarygenerator->create_content($glossary2, array('tags' => array('Cats', 'Dogs')));
$entry23 = $glossarygenerator->create_content($glossary2, array('tags' => array('mice', 'Cats')));
$entry31 = $glossarygenerator->create_content($glossary3, array('tags' => array('mice', 'Cats')));
$tag = \core_tag_tag::get_by_name(0, 'Cats');
// Admin can see everything.
// Get first page of tagged entries (first 5 entries).
$res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */0);
$this->assertMatchesRegularExpression('/'.$entry11->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry12->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry13->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry15->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry16->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry17->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry21->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry22->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry23->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry31->concept.'</', $res->content);
$this->assertEmpty($res->prevpageurl);
$this->assertNotEmpty($res->nextpageurl);
// Get second page of tagged entries (second 5 entries).
$res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */1);
$this->assertDoesNotMatchRegularExpression('/'.$entry11->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry12->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry13->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry15->concept.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry16->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry17->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry21->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry22->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry23->concept.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$entry31->concept.'</', $res->content);
$this->assertNotEmpty($res->prevpageurl);
$this->assertEmpty($res->nextpageurl);
$this->setUser($student);
\core_tag_index_builder::reset_caches();
// User can not see entries in course 3 because he is not enrolled.
$res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$entry = */1);
$this->assertMatchesRegularExpression('/'.$entry22->concept.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$entry23->concept.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry31->concept.'/', $res->content);
// User can search glossary entries inside a course.
$coursecontext = \context_course::instance($course1->id);
$res = mod_glossary_get_tagged_entries($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */$coursecontext->id, /*$rec = */1, /*$entry = */0);
$this->assertMatchesRegularExpression('/'.$entry11->concept.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$entry12->concept.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$entry13->concept.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry14->concept.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$entry15->concept.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry21->concept.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry22->concept.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$entry23->concept.'/', $res->content);
$this->assertEmpty($res->nextpageurl);
// User cannot see unapproved entries unless he is an author.
$this->assertDoesNotMatchRegularExpression('/'.$entry16->concept.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$entry17->concept.'/', $res->content);
}
public function test_glossary_get_entries_search(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Turn on glossary autolinking (usedynalink).
set_config('glossary_linkentries', 1);
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
// Note this entry is not case sensitive by default (casesensitive = 0).
$entry = $glossarygenerator->create_content($glossary);
// Check that a search for the concept return the entry.
$concept = $entry->concept;
$search = glossary_get_entries_search($concept, $course->id);
$this->assertCount(1, $search);
$foundentry = array_shift($search);
$this->assertEquals($foundentry->concept, $entry->concept);
// Now try the same search but with a lowercase term.
$concept = strtolower($entry->concept);
$search = glossary_get_entries_search($concept, $course->id);
$this->assertCount(1, $search);
$foundentry = array_shift($search);
$this->assertEquals($foundentry->concept, $entry->concept);
// Make an entry that is case sensitive (casesensitive = 1).
set_config('glossary_casesensitive', 1);
$entry = $glossarygenerator->create_content($glossary);
$concept = $entry->concept;
$search = glossary_get_entries_search($concept, $course->id);
$this->assertCount(1, $search);
$foundentry = array_shift($search);
$this->assertEquals($foundentry->concept, $entry->concept);
// Now try the same search but with a lowercase term.
$concept = strtolower($entry->concept);
$search = glossary_get_entries_search($concept, $course->id);
$this->assertCount(0, $search);
}
public function test_mod_glossary_can_delete_entry_users(): void {
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
// Test student can delete.
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test teacher can delete.
$this->setUser($teacher);
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test admin can delete.
$this->setAdminUser();
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test a different student is not able to delete.
$this->setUser($anotherstudent);
$this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test exception.
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
mod_glossary_can_delete_entry($entry, $glossary, $context, false);
}
public function test_mod_glossary_can_delete_entry_edit_period(): void {
global $CFG;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id, 'editalways' => 1]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
// Test student can always delete when edit always is set to 1.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test student cannot delete old entries when edit always is set to 0.
$glossary->editalways = 0;
$this->assertFalse(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Test student can delete recent entries when edit always is set to 0.
$entry->timecreated = time();
$this->assertTrue(mod_glossary_can_delete_entry($entry, $glossary, $context));
// Check exception.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->expectExceptionMessage(get_string('errdeltimeexpired', 'glossary'));
mod_glossary_can_delete_entry($entry, $glossary, $context, false);
}
public function test_mod_glossary_delete_entry(): void {
global $DB, $CFG;
$this->resetAfterTest();
require_once($CFG->dirroot . '/rating/lib.php');
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$record = new \stdClass();
$record->course = $course->id;
$record->assessed = RATING_AGGREGATE_AVERAGE;
$scale = $this->getDataGenerator()->create_scale(['scale' => 'A,B,C,D']);
$record->scale = "-$scale->id";
$glossary = $this->getDataGenerator()->create_module('glossary', $record);
$context = \context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student1);
// Create entry with tags and rating.
$entry = $gg->create_content(
$glossary,
['approved' => 1, 'userid' => $student1->id, 'tags' => ['Cats', 'Dogs']],
['alias1', 'alias2']
);
// Rate the entry as user2.
$rating1 = new \stdClass();
$rating1->contextid = $context->id;
$rating1->component = 'mod_glossary';
$rating1->ratingarea = 'entry';
$rating1->itemid = $entry->id;
$rating1->rating = 1; // 1 is A.
$rating1->scaleid = "-$scale->id";
$rating1->userid = $student2->id;
$rating1->timecreated = time();
$rating1->timemodified = time();
$rating1->id = $DB->insert_record('rating', $rating1);
$sink = $this->redirectEvents();
mod_glossary_delete_entry(fullclone($entry), $glossary, $cm, $context, $course);
$events = $sink->get_events();
$event = array_pop($events);
// Check events.
$this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
$this->assertEquals($entry->id, $event->objectid);
$sink->close();
// No entry, no alias, no ratings, no tags.
$this->assertEquals(0, $DB->count_records('glossary_entries', ['id' => $entry->id]));
$this->assertEquals(0, $DB->count_records('glossary_alias', ['entryid' => $entry->id]));
$this->assertEquals(0, $DB->count_records('rating', ['component' => 'mod_glossary', 'itemid' => $entry->id]));
$this->assertEmpty(\core_tag_tag::get_by_name(0, 'Cats'));
}
public function test_mod_glossary_delete_entry_imported(): void {
global $DB;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary1 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$glossary2 = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$context = \context_module::instance($glossary2->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary2->id);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry1 = $gg->create_content($glossary1);
$entry2 = $gg->create_content(
$glossary2,
['approved' => 1, 'userid' => $student->id, 'sourceglossaryid' => $glossary1->id, 'tags' => ['Cats', 'Dogs']]
);
$sink = $this->redirectEvents();
mod_glossary_delete_entry(fullclone($entry2), $glossary2, $cm, $context, $course);
$events = $sink->get_events();
$event = array_pop($events);
// Check events.
$this->assertEquals('\mod_glossary\event\entry_deleted', $event->eventname);
$this->assertEquals($entry2->id, $event->objectid);
$sink->close();
// Check source.
$this->assertEquals(0, $DB->get_field('glossary_entries', 'sourceglossaryid', ['id' => $entry2->id]));
$this->assertEquals($glossary1->id, $DB->get_field('glossary_entries', 'glossaryid', ['id' => $entry2->id]));
// Tags.
$this->assertEmpty(\core_tag_tag::get_by_name(0, 'Cats'));
}
public function test_mod_glossary_can_update_entry_users(): void {
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
// Test student can update.
$this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test teacher can update.
$this->setUser($teacher);
$this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test admin can update.
$this->setAdminUser();
$this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test a different student is not able to update.
$this->setUser($anotherstudent);
$this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test exception.
$this->expectExceptionMessage(get_string('errcannoteditothers', 'glossary'));
mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
}
public function test_mod_glossary_can_update_entry_edit_period(): void {
global $CFG;
$this->resetAfterTest();
// Create required data.
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id, 'editalways' => 1));
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setUser($student);
$entry = $gg->create_content($glossary);
$context = \context_module::instance($glossary->cmid);
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
// Test student can always update when edit always is set to 1.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test student cannot update old entries when edit always is set to 0.
$glossary->editalways = 0;
$this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Test student can update recent entries when edit always is set to 0.
$entry->timecreated = time();
$this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
// Check exception.
$entry->timecreated = time() - 2 * $CFG->maxeditingtime;
$this->expectExceptionMessage(get_string('erredittimeexpired', 'glossary'));
mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
}
public function test_prepare_entry_for_edition(): void {
global $USER;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
$gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$this->setAdminUser();
$aliases = ['alias1', 'alias2'];
$entry = $gg->create_content(
$glossary,
['approved' => 1, 'userid' => $USER->id],
$aliases
);
$cat1 = $gg->create_category($glossary, [], [$entry]);
$gg->create_category($glossary);
$entry = mod_glossary_prepare_entry_for_edition($entry);
$this->assertCount(1, $entry->categories);
$this->assertEquals($cat1->id, $entry->categories[0]);
$returnedaliases = array_values(explode("\n", trim($entry->aliases)));
sort($returnedaliases);
$this->assertEquals($aliases, $returnedaliases);
}
}
@@ -0,0 +1,466 @@
<?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_glossary
* @copyright 2018 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_glossary\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\deletion_criteria;
use mod_glossary\privacy\provider;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
require_once($CFG->dirroot . '/rating/lib.php');
/**
* Privacy provider tests class.
*
* @package mod_glossary
* @copyright 2018 Simey Lameze <simey@moodle.com>
* @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 teacher object. */
protected $teacher;
/** @var stdClass The glossary object. */
protected $glossary;
/** @var stdClass The course object. */
protected $course;
/** @var stdClass The plugin generator object. */
protected $plugingenerator;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->resetAfterTest();
global $DB;
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$this->course = $course;
$this->plugingenerator = $generator->get_plugin_generator('mod_glossary');
// The glossary activity the user will answer.
$glossary = $this->plugingenerator->create_instance(['course' => $course->id]);
$this->glossary = $glossary;
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
$context = \context_module::instance($cm->id);
// Create a student which will add an entry to a glossary.
$student = $generator->create_user();
$generator->enrol_user($student->id, $course->id, 'student');
$this->student = $student;
$teacher = $generator->create_user();
$generator->enrol_user($teacher->id, $course->id, 'editingteacher');
$this->teacher = $teacher;
$this->setUser($student->id);
$ge1 = $this->plugingenerator->create_content($glossary, ['concept' => 'first', 'approved' => 1], ['one']);
// Student create a comment on a glossary entry.
$this->setUser($student);
$comment = $this->get_comment_object($context, $ge1->id);
$comment->add('Hello, it\'s me!');
// Attach tags.
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge1->id, $context, ['Beer', 'Golf']);
}
/**
* Test for provider::get_metadata().
*/
public function test_get_metadata(): void {
$collection = new collection('mod_glossary');
$newcollection = provider::get_metadata($collection);
$itemcollection = $newcollection->get_collection();
$this->assertCount(5, $itemcollection);
$table = reset($itemcollection);
$this->assertEquals('glossary_entries', $table->get_name());
$privacyfields = $table->get_privacy_fields();
$this->assertArrayHasKey('glossaryid', $privacyfields);
$this->assertArrayHasKey('concept', $privacyfields);
$this->assertArrayHasKey('definition', $privacyfields);
$this->assertArrayHasKey('attachment', $privacyfields);
$this->assertArrayHasKey('userid', $privacyfields);
$this->assertArrayHasKey('timemodified', $privacyfields);
$this->assertEquals('privacy:metadata:glossary_entries', $table->get_summary());
}
/**
* Test for provider::get_contexts_for_userid().
*/
public function test_get_contexts_for_userid(): void {
$cm = get_coursemodule_from_instance('glossary', $this->glossary->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_glossary';
$cm = get_coursemodule_from_instance('glossary', $this->glossary->id);
$cmcontext = \context_module::instance($cm->id);
$userlist = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$this->student->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context(): void {
$cm = get_coursemodule_from_instance('glossary', $this->glossary->id);
$cmcontext = \context_module::instance($cm->id);
// Export all of the data for the context.
$writer = \core_privacy\local\request\writer::with_context($cmcontext);
$contextlist = new \core_privacy\local\request\approved_contextlist($this->student, 'mod_glossary' , [$cmcontext->id]);
\mod_glossary\privacy\provider::export_user_data($contextlist);
$this->assertTrue($writer->has_any_data());
$data = $writer->get_data([]);
$this->assertEquals('Glossary 1', $data->name);
$this->assertEquals('first', $data->entries[0]['concept']);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$generator = $this->getDataGenerator();
$cm = get_coursemodule_from_instance('glossary', $this->glossary->id);
$context = \context_module::instance($cm->id);
// Create another student who will add an entry the glossary activity.
$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $this->course->id, 'student');
$this->setUser($student2);
$ge3 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'first', 'approved' => 1], ['three']);
$comment = $this->get_comment_object($context, $ge3->id);
$comment->add('User 2 comment');
$this->plugingenerator->create_category($this->glossary, ['cat1'], [$ge3]);
$count = $DB->count_records('glossary_entries_categories', ['entryid' => $ge3->id]);
$this->assertEquals(1, $count);
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge3->id, $context, ['Pizza', 'Noodles']);
// As a teacher, rate student 2 entry.
$this->setUser($this->teacher);
$rating = $this->get_rating_object($context, $ge3->id);
$rating->update_rating(2);
// Before deletion, we should have 2 entries.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(2, $count);
$aliascount = $DB->count_records('glossary_alias');
$this->assertEquals(2, $aliascount);
// Delete data based on context.
provider::delete_data_for_all_users_in_context($context);
// After deletion, the glossary entries and aliases for that glossary activity should have been deleted.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(0, $count);
$this->assertEquals(0, $DB->count_records('glossary_alias'));
$count = $DB->count_records('glossary_entries_categories', ['entryid' => $ge3->id]);
$this->assertEquals(0, $count);
$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge3->id]);
$this->assertEquals(0, $tagcount);
$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'itemid' => $ge3->id, 'userid' => $student2->id]);
$this->assertEquals(0, $commentcount);
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(0, $ratingcount);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
$generator = $this->getDataGenerator();
// Create another student who will add an entry to the first glossary.
$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $this->course->id, 'student');
$cm1 = get_coursemodule_from_instance('glossary', $this->glossary->id);
$glossary2 = $this->plugingenerator->create_instance(['course' => $this->course->id]);
$cm2 = get_coursemodule_from_instance('glossary', $glossary2->id);
$ge1 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'first user glossary entry', 'approved' => 1]);
$this->plugingenerator->create_content($glossary2, ['concept' => 'first user second glossary entry', 'approved' => 1]);
$context1 = \context_module::instance($cm1->id);
$context2 = \context_module::instance($cm2->id);
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge1->id, $context1, ['Parmi', 'Sushi']);
$this->setUser($student2);
$ge3 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'second user glossary entry',
'approved' => 1], ['three']);
$comment = $this->get_comment_object($context1, $ge3->id);
$comment->add('User 2 comment');
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge3->id, $context1, ['Pizza', 'Noodles']);
// As a teacher, rate student 2's entry.
$this->setUser($this->teacher);
$rating = $this->get_rating_object($context1, $ge3->id);
$rating->update_rating(2);
// Before deletion, we should have 3 entries, one rating and 2 tag instances.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(3, $count);
$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge3->id]);
$this->assertEquals(2, $tagcount);
$aliascount = $DB->count_records('glossary_alias', ['entryid' => $ge3->id]);
$this->assertEquals(1, $aliascount);
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(1, $ratingcount);
$contextlist = new \core_privacy\local\request\approved_contextlist($student2, 'glossary',
[$context1->id, $context2->id]);
provider::delete_data_for_user($contextlist);
// After deletion, the glossary entry and tags for the second student should have been deleted.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id, 'userid' => $student2->id]);
$this->assertEquals(0, $count);
$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge3->id]);
$this->assertEquals(0, $tagcount);
$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'itemid' => $ge3->id, 'userid' => $student2->id]);
$this->assertEquals(0, $commentcount);
$aliascount = $DB->count_records('glossary_alias', ['entryid' => $ge3->id]);
$this->assertEquals(0, $aliascount);
// Student's 1 entries, comments and tags should not be removed.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id,
'userid' => $this->student->id]);
$this->assertEquals(2, $count);
$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge1->id]);
$this->assertEquals(2, $tagcount);
$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'userid' => $this->student->id]);
$this->assertEquals(1, $commentcount);
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(0, $ratingcount);
}
/**
* Test for provider::delete_data_for_users().
*/
public function test_delete_data_for_users(): void {
global $DB;
$generator = $this->getDataGenerator();
$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $this->course->id, 'student');
$cm1 = get_coursemodule_from_instance('glossary', $this->glossary->id);
$glossary2 = $this->plugingenerator->create_instance(['course' => $this->course->id]);
$cm2 = get_coursemodule_from_instance('glossary', $glossary2->id);
$ge1 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'first user glossary entry', 'approved' => 1]);
$ge2 = $this->plugingenerator->create_content($glossary2, ['concept' => 'first user second glossary entry',
'approved' => 1], ['two']);
$context1 = \context_module::instance($cm1->id);
$context2 = \context_module::instance($cm2->id);
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge1->id, $context1, ['Parmi', 'Sushi']);
$this->setUser($student2);
$ge3 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'second user glossary entry',
'approved' => 1], ['three']);
$comment = $this->get_comment_object($context1, $ge3->id);
$comment->add('User 2 comment 1');
$comment = $this->get_comment_object($context2, $ge2->id);
$comment->add('User 2 comment 2');
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge3->id, $context1, ['Pizza', 'Noodles']);
\core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge2->id, $context2, ['Potato', 'Kumara']);
// As a teacher, rate student 2's entry.
$this->setUser($this->teacher);
$rating = $this->get_rating_object($context1, $ge3->id);
$rating->update_rating(2);
// Check correct glossary 1 record counts before deletion.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
// Note: There is an additional student entry from setUp().
$this->assertEquals(3, $count);
list($context1itemsql, $context1itemparams) = $DB->get_in_or_equal([$ge1->id, $ge3->id], SQL_PARAMS_NAMED);
$geparams = [
'component' => 'mod_glossary',
'itemtype' => 'glossary_entries',
];
$geparams += $context1itemparams;
$wheresql = "component = :component AND itemtype = :itemtype AND itemid {$context1itemsql}";
$tagcount = $DB->count_records_select('tag_instance', $wheresql, $geparams);
$this->assertEquals(4, $tagcount);
$aliascount = $DB->count_records_select('glossary_alias', "entryid {$context1itemsql}", $context1itemparams);
$this->assertEquals(1, $aliascount);
$commentparams = [
'component' => 'mod_glossary',
'commentarea' => 'glossary_entry',
];
$commentparams += $context1itemparams;
$commentwhere = "component = :component AND commentarea = :commentarea AND itemid {$context1itemsql}";
$commentcount = $DB->count_records_select('comments', $commentwhere, $commentparams);
$this->assertEquals(1, $commentcount);
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(1, $ratingcount);
// Perform deletion within context 1 for both students.
$approveduserlist = new approved_userlist($context1, 'mod_glossary',
[$this->student->id, $student2->id]);
provider::delete_data_for_users($approveduserlist);
// After deletion, all context 1 entries, tags and comment should be deleted.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(0, $count);
$tagcount = $DB->count_records_select('tag_instance', $wheresql, $geparams);
$this->assertEquals(0, $tagcount);
$aliascount = $DB->count_records_select('glossary_alias', "entryid {$context1itemsql}", $context1itemparams);
$this->assertEquals(0, $aliascount);
$commentcount = $DB->count_records_select('comments', $commentwhere, $commentparams);
$this->assertEquals(0, $commentcount);
// Context 2 entries should remain intact.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $glossary2->id]);
$this->assertEquals(1, $count);
$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge2->id]);
$this->assertEquals(2, $tagcount);
$aliascount = $DB->count_records('glossary_alias', ['entryid' => $ge2->id]);
$this->assertEquals(1, $aliascount);
$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'itemid' => $ge2->id]);
$this->assertEquals(1, $commentcount);
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(0, $ratingcount);
}
/**
* Get the comment area for glossary module.
*
* @param context $context The context.
* @param int $itemid The item ID.
* @return comment
*/
protected function get_comment_object(\context $context, $itemid) {
$args = new \stdClass();
$args->context = $context;
$args->course = get_course(SITEID);
$args->area = 'glossary_entry';
$args->itemid = $itemid;
$args->component = 'mod_glossary';
$comment = new \comment($args);
$comment->set_post_permission(true);
return $comment;
}
/**
* Get the rating area for glossary module.
*
* @param context $context The context.
* @param int $itemid The item ID.
* @return rating object
*/
protected function get_rating_object(\context $context, $itemid) {
global $USER;
$ratingoptions = new \stdClass;
$ratingoptions->context = $context;
$ratingoptions->ratingarea = 'entry';
$ratingoptions->component = 'mod_glossary';
$ratingoptions->itemid = $itemid;
$ratingoptions->scaleid = 2;
$ratingoptions->userid = $USER->id;
return new \rating($ratingoptions);
}
}
+233
View File
@@ -0,0 +1,233 @@
<?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/>.
/**
* Glossary search unit tests.
*
* @package mod_glossary
* @category test
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_glossary\search;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
require_once($CFG->dirroot . '/mod/glossary/tests/generator/lib.php');
/**
* Provides the unit tests for glossary search.
*
* @package mod_glossary
* @category test
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_test extends \advanced_testcase {
/**
* @var string Area id
*/
protected $entryareaid = null;
public function setUp(): void {
$this->resetAfterTest(true);
set_config('enableglobalsearch', true);
// Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
$search = \testable_core_search::instance();
$this->entryareaid = \core_search\manager::generate_areaid('mod_glossary', 'entry');
}
/**
* Availability.
*
* @return void
*/
public function test_search_enabled(): void {
$searcharea = \core_search\manager::get_search_area($this->entryareaid);
list($componentname, $varname) = $searcharea->get_config_var_name();
// Enabled by default once global search is enabled.
$this->assertTrue($searcharea->is_enabled());
set_config($varname . '_enabled', 0, $componentname);
$this->assertFalse($searcharea->is_enabled());
set_config($varname . '_enabled', 1, $componentname);
$this->assertTrue($searcharea->is_enabled());
}
/**
* Indexing contents.
*
* @return void
*/
public function test_entries_indexing(): void {
global $DB;
$searcharea = \core_search\manager::get_search_area($this->entryareaid);
$this->assertInstanceOf('\mod_glossary\search\entry', $searcharea);
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$course1 = self::getDataGenerator()->create_course();
$course2 = self::getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
$this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
$record = new \stdClass();
$record->course = $course1->id;
$this->setUser($user1);
// Approved entries by default glossary.
$glossary1 = self::getDataGenerator()->create_module('glossary', $record);
$entry1 = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary1);
$entry2 = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary1);
// All records.
$recordset = $searcharea->get_recordset_by_timestamp(0);
$this->assertTrue($recordset->valid());
$nrecords = 0;
foreach ($recordset as $record) {
$this->assertInstanceOf('stdClass', $record);
$doc = $searcharea->get_document($record);
$this->assertInstanceOf('\core_search\document', $doc);
// Static caches are working.
$dbreads = $DB->perf_get_reads();
$doc = $searcharea->get_document($record);
// The +1 is because we are not caching glossary alias (keywords) as they depend on a single entry.
$this->assertEquals($dbreads + 1, $DB->perf_get_reads());
$this->assertInstanceOf('\core_search\document', $doc);
$nrecords++;
}
// If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
$recordset->close();
$this->assertEquals(2, $nrecords);
// The +2 is to prevent race conditions.
$recordset = $searcharea->get_recordset_by_timestamp(time() + 2);
// No new records.
$this->assertFalse($recordset->valid());
$recordset->close();
// Create a second glossary with one entry.
$glossary2 = self::getDataGenerator()->create_module('glossary', ['course' => $course1->id]);
self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary2);
// Test indexing with each activity then combined course context.
$rs = $searcharea->get_document_recordset(0, \context_module::instance($glossary1->cmid));
$this->assertEquals(2, iterator_count($rs));
$rs->close();
$rs = $searcharea->get_document_recordset(0, \context_module::instance($glossary2->cmid));
$this->assertEquals(1, iterator_count($rs));
$rs->close();
$rs = $searcharea->get_document_recordset(0, \context_course::instance($course1->id));
$this->assertEquals(3, iterator_count($rs));
$rs->close();
}
/**
* Document contents.
*
* @return void
*/
public function test_entries_document(): void {
global $DB;
$searcharea = \core_search\manager::get_search_area($this->entryareaid);
$user = self::getDataGenerator()->create_user();
$course1 = self::getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user->id, $course1->id, 'teacher');
$record = new \stdClass();
$record->course = $course1->id;
$this->setUser($user);
$glossary = self::getDataGenerator()->create_module('glossary', $record);
$entry = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary);
$entry->course = $glossary->course;
$doc = $searcharea->get_document($entry);
$this->assertInstanceOf('\core_search\document', $doc);
$this->assertEquals($entry->id, $doc->get('itemid'));
$this->assertEquals($course1->id, $doc->get('courseid'));
$this->assertEquals($user->id, $doc->get('userid'));
$this->assertEquals($entry->concept, $doc->get('title'));
$this->assertEquals($entry->definition, $doc->get('content'));
}
/**
* Document accesses.
*
* @return void
*/
public function test_entries_access(): void {
global $DB;
// Returns the instance as long as the component is supported.
$searcharea = \core_search\manager::get_search_area($this->entryareaid);
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$course1 = self::getDataGenerator()->create_course();
$course2 = self::getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher');
$this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
$record = new \stdClass();
$record->course = $course1->id;
// Approved entries by default glossary, created by teacher.
$this->setUser($user1);
$glossary1 = self::getDataGenerator()->create_module('glossary', $record);
$teacherapproved = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary1);
$teachernotapproved = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary1, array('approved' => false));
// Entries need to be approved and created by student.
$glossary2 = self::getDataGenerator()->create_module('glossary', $record);
$this->setUser($user2);
$studentapproved = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary2);
$studentnotapproved = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary2, array('approved' => false));
// Activity hidden to students.
$this->setUser($user1);
$glossary3 = self::getDataGenerator()->create_module('glossary', $record);
$hidden = self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary3);
set_coursemodule_visible($glossary3->cmid, 0);
$this->setUser($user2);
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($teacherapproved->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($teachernotapproved->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($studentapproved->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($studentnotapproved->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($hidden->id));
}
}