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,130 @@
<?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 tool_usertours;
use tool_usertours\local\filter\accessdate;
/**
* Tests for time filter.
*
* @package tool_usertours
* @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\local\filter\accessdate
*/
class accessdate_filter_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest(true);
}
/**
* Data Provider for filter_matches method.
*
* @return array
*/
public static function filter_matches_provider(): array {
return [
'No config set; Matches' => [
[],
[],
true,
],
'Filter is not enabled; Match' => [
['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 0, ],
['timecreated' => time() - (89 * DAYSECS)],
true,
],
'Filter is not enabled (tour would not be displayed if it was); Match' => [
['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 0, ],
['timecreated' => time() - (91 * DAYSECS)],
true,
],
'Inside range of account creation date; Match' => [
['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['timecreated' => time() - (89 * DAYSECS)],
true,
],
'Outside range of account creation date; No match' => [
['filter_accessdate' => accessdate::FILTER_ACCOUNT_CREATION, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['timecreated' => time() - (91 * DAYSECS)],
false,
],
'Inside range of first login date; Match' => [
['filter_accessdate' => accessdate::FILTER_FIRST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['firstaccess' => time() - (89 * DAYSECS)],
true,
],
'Outside range of first login date; No match' => [
['filter_accessdate' => accessdate::FILTER_FIRST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['firstaccess' => time() - (91 * DAYSECS)],
false,
],
'Inside range of last login date; Match' => [
['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['lastlogin' => time() - (89 * DAYSECS)],
true,
],
'Outside range of last login date; No match' => [
['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['lastlogin' => time() - (91 * DAYSECS)],
false,
],
'User has never logged in, but tour should be visible; Match' => [
['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['lastlogin' => 0, 'timecreated' => time() - (89 * DAYSECS)],
true,
],
'User has never logged in, and tour should not be visible; No match' => [
['filter_accessdate' => accessdate::FILTER_LAST_LOGIN, 'filter_accessdate_range' => 90 * DAYSECS,
'filter_accessdate_enabled' => 1, ],
['lastlogin' => 0, 'timecreated' => time() - (91 * DAYSECS)],
false,
],
];
}
/**
* Test filter matches.
*
* @dataProvider filter_matches_provider
*
* @param array $filtervalues the filter values set.
* @param array $userstate any user state required for test.
* @param bool $expected result expected.
*/
public function test_filter_matches($filtervalues, $userstate, $expected): void {
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$user = $this->getDataGenerator()->create_user($userstate);
$this->setUser($user);
$tour = new tour();
$tour->set_filter_values('accessdate', $filtervalues);
$this->assertEquals($expected, accessdate::filter_matches($tour, $context));
}
}
@@ -0,0 +1,98 @@
<?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/>.
/**
* User tour related steps definitions.
*
* @package tool_usertours
* @category test
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode;
/**
* User tour related steps definitions.
*
* @package tool_usertours
* @category test
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_tool_usertours extends behat_base {
/**
* Add a new user tour.
*
* @Given /^I add a new user tour with:$/
* @param TableNode $table
*/
public function i_add_a_new_user_tour_with(TableNode $table) {
$this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page');
$this->execute('behat_general::click_link', get_string('newtour', 'tool_usertours'));
// Fill form and post.
$this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table);
$this->execute('behat_forms::press_button', get_string('savechanges', 'moodle'));
$this->execute('behat_general::i_wait_to_be_redirected');
}
/**
* Add new steps to a user tour.
*
* @Given /^I add steps to the "(?P<tour_name_string>(?:[^"]|\\")*)" tour:$/
* @param string $tourname The name of the tour to add steps to.
* @param TableNode $table
*/
public function i_add_steps_to_the_named_tour($tourname, TableNode $table) {
$this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page');
$this->execute('behat_general::click_link', $this->escape($tourname));
$this->execute('behat_tool_usertours::i_add_steps_to_the_tour', $table);
}
/**
* Add new steps to the current user tour.
*
* @Given /^I add steps to the tour:$/
* @param TableNode $table
*/
public function i_add_steps_to_the_tour(TableNode $table) {
foreach ($table->getHash() as $step) {
$this->execute('behat_general::click_link', get_string('newstep', 'tool_usertours'));
foreach ($step as $locator => $value) {
$this->execute('behat_forms::i_set_the_field_to', [$this->escape($locator), $this->escape($value)]);
}
$this->execute('behat_forms::press_button', get_string('savechanges', 'moodle'));
$this->execute('behat_general::i_wait_to_be_redirected');
}
}
/**
* Navigate to the user tour settings page.
*
* @Given /^I open the User tour settings page$/
*/
public function i_open_the_user_tour_settings_page() {
$this->execute(
'behat_navigation::i_navigate_to_in_site_administration',
get_string('appearance', 'admin') . ' > ' .
get_string('usertours', 'tool_usertours')
);
}
}
@@ -0,0 +1,160 @@
@tool @tool_usertours
Feature: Add a new user tour
In order to help users learn of new features
As an administrator
I need to create a user tour
@javascript
Scenario: Add a new tour
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual |
And I add steps to the "First tour" tour:
| targettype | targetvalue_block | Title | id_content | Content type |
| Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | Manual |
| Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | Manual |
And I add steps to the "First tour" tour:
| targettype | targetvalue_selector | Title | id_content | Content type |
| Selector | .usermenu | User menu | This is your personal user menu. You'll find your personal preferences and your user profile here. | Manual |
When I am on homepage
Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element"
And I should see "This is the Timeline. All of your upcoming activities can be found here"
And I should not see "This is the Calendar. All of your assignments and due dates can be found here"
And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element"
And I should see "This is the Calendar. All of your assignments and due dates can be found here"
And I should not see "This area shows you what's happening in some of your courses"
And I click on "Skip tour" "button" in the "[data-role='flexitour-step']" "css_element"
And I should not see "This area shows you what's happening in some of your courses"
And I am on homepage
And I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
And I should not see "This area shows you what's happening in some of your courses"
And I follow "Reset user tour on this page"
And I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: A hidden tour should not be visible
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual |
When I am on homepage
Then I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: Tour visibility can be toggled
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual |
And I open the User tour settings page
When I click on "Enable" "link" in the "My first tour" "table_row"
And I am on homepage
Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: Display step numbers was enabled
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | Steps tour |
| Description | My steps tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| Display step numbers | 1 |
| End tour button's label | Sample end label |
And I add steps to the "Steps tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | First step of the Tour | Manual |
And I add steps to the "Steps tour" tour:
| targettype | targetvalue_block | Title | id_content | Content type |
| Block | Timeline | Timeline | Second step of the Tour | Manual |
| Block | Calendar | Calendar | Third step of the Tour | Manual |
When I am on homepage
Then I should see "First step of the Tour"
And I should see "Next (1/3)"
And I should not see "End tour"
And I should not see "Sample end label"
And "Skip tour" "button" should exist in the "[data-role='flexitour-step']" "css_element"
And I click on "Next (1/3)" "button" in the "[data-role='flexitour-step']" "css_element"
And I should see "Second step of the Tour"
And I should see "Next (2/3)"
And I click on "Next (2/3)" "button" in the "[data-role='flexitour-step']" "css_element"
And I should see "Third step of the Tour"
And I should not see "Next (3/3)"
And I should not see "Skip tour"
And "Sample end label" "button" should exist in the "[data-role='flexitour-step']" "css_element"
@javascript
Scenario: Display step numbers was disabled
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | Steps tour |
| Description | My steps tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| Display step numbers | 0 |
And I add steps to the "Steps tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | First step of the Tour | Manual |
And I add steps to the "Steps tour" tour:
| targettype | targetvalue_block | Title | id_content | Content type |
| Block | Timeline | Timeline | Second step of the Tour | Manual |
| Block | Calendar | Calendar | Third step of the Tour | Manual |
When I am on homepage
Then I should see "First step of the Tour"
And I should see "Next"
And I should not see "Next (1/3)"
And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element"
And I should see "Second step of the Tour"
And I should see "Next"
And I should not see "Next (2/3)"
@javascript
Scenario: Single step tour with display step numbers was enable
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | Steps tour |
| Description | My steps tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| Display step numbers | 1 |
And I add steps to the "Steps tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | This is a single step tour | Manual |
When I am on homepage
Then I should see "This is a single step tour"
And I should not see "Next (1/1)"
@@ -0,0 +1,24 @@
@tool @tool_usertours
Feature: Duplicate a user tour
As an administrator
I want to duplicate a user tour
@javascript
Scenario: Tour can be duplicated
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual |
And I open the User tour settings page
And I should see "1" occurrences of "First tour" in the "admintable" "table"
And I click on "Duplicate" "link" in the "My first tour" "table_row"
And I open the User tour settings page
Then I should see "1" occurrences of "First tour (copy)" in the "admintable" "table"
@@ -0,0 +1,28 @@
@tool @tool_usertours
Feature: Reset a tour
In order to test a tour
As an administrator
I can reset the tour to force it to display again
Background:
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | FRONTPAGE |
| Tour is enabled | 1 |
| Show with backdrop | 1 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome tour. | Manual |
@javascript
Scenario: Reset the tour with mobile view
# Changing the window viewport to mobile so we will have the footer section.
Given I change viewport size to "480x800"
And I am on site homepage
And I should see "Welcome"
And I press "Got it"
And I should not see "Welcome"
When I click on "Reset user tour on this page" "link" in the "#page-footer" "css_element"
Then I should see "Welcome"
@@ -0,0 +1,32 @@
@tool @tool_usertours @javascript
Feature: Verify the breadcrumbs in user tours site administration pages
Whenever I navigate to user tours page in site administration
As an admin
The breadcrumbs should be visible
Background:
Given I log in as "admin"
Scenario: Verify the breadcrumbs in user tours, expand to explore, next step, create and import pages as admin
Given I navigate to "Appearance > User tours" in site administration
And I click on "Block drawer" "link"
And "Block drawer" "text" should exist in the ".breadcrumb" "css_element"
And "User tours" "link" should exist in the ".breadcrumb" "css_element"
When I click on "Expand to explore" "link"
Then "Expand to explore" "text" should exist in the ".breadcrumb" "css_element"
And "Block drawer" "link" should exist in the ".breadcrumb" "css_element"
And "User tours" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
And I click on "New step" "link"
And "New step" "text" should exist in the ".breadcrumb" "css_element"
And "Block drawer" "link" should exist in the ".breadcrumb" "css_element"
And "User tours" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
And I navigate to "Appearance > User tours" in site administration
And I click on "Create a new tour" "link"
And "Create a new tour" "text" should exist in the ".breadcrumb" "css_element"
And "User tours" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
And I click on "Import tour" "link"
And "Import tour" "text" should exist in the ".breadcrumb" "css_element"
And "User tours" "link" should exist in the ".breadcrumb" "css_element"
@@ -0,0 +1,76 @@
@tool @tool_usertours
Feature: Apply accessibility to a tour
Background:
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | FRONTPAGE |
| Tour is enabled | 1 |
| Show with backdrop | 1 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome tour. | Manual |
And I add steps to the tour:
| targettype | targetvalue_selector | Title | id_content | Content type |
| Selector | .usermenu | User menu | Next page | Manual |
| Selector | .navbar-brand | Page 2 | Next page | Manual |
And I add steps to the tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Page 3 | Final page. | Manual |
@javascript
Scenario: Check tabbing working correctly.
Given I am on site homepage
And I wait "1" seconds
And I should see "Welcome"
# First dialogue of the tour, "Welcome". It has Next and End buttons.
# Nothing highlighted on the page. Initially whole dialogue focused.
When I press tab
Then the focused element is "Next" "button" in the "Welcome" "dialogue"
When I press tab
Then the focused element is "Skip tour" "button" in the "Welcome" "dialogue"
When I press tab
# Here the focus loops round to the whole dialogue again.
And I press tab
Then the focused element is "Next" "button" in the "Welcome" "dialogue"
# Check looping works properly going backwards too.
When I press shift tab
And I press shift tab
Then the focused element is "Skip tour" "button" in the "Welcome" "dialogue"
When I press "Next"
# Now we are on the "User menu" step, so Previous is also enabled.
# Also, the user menu section in the page is highlighted, and this
# section contain a hyperlink so the focus have to go though and back to the dialogue.
And I wait "1" seconds
And I press tab
Then the focused element is "Next" "button" in the "User menu" "dialogue"
When I press tab
Then the focused element is "Skip tour" "button" in the "User menu" "dialogue"
# We tab 3 times from "Skip Tour" button to header container, drop down then go to "Dashboard" link.
When I press tab
Then the focused element is ".usermenu" "css_element"
When I press tab
Then the focused element is "User menu" "button" in the ".usermenu" "css_element"
When I press tab
And I press tab
Then the focused element is "Next" "button" in the "User menu" "dialogue"
# Press shift-tab twice should lead us back to the user menu button.
When I press shift tab
And I press shift tab
Then the focused element is "User menu" "button" in the ".usermenu" "css_element"
@javascript
Scenario: Aria tags should not exist
And I am on site homepage
When I click on "Next" "button"
And I click on "Next" "button"
Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should exist
And ".navbar-brand[tabindex]" "css_element" should exist
When I click on "Next" "button"
Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist
And ".navbar-brand[tabindex]:not([tabindex='-1'])" "css_element" should not exist
When I click on "End tour" "button"
Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist
And ".navbar-brand[tabindex]:not([tabindex='0'])" "css_element" should not exist
@@ -0,0 +1,82 @@
@tool @tool_usertours
Feature: Apply content type to a tour
In order to give more content to a tour
As an administrator
I need to change the content type of the user tour
Background:
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
And I add a new user tour with:
| Name | tour_activityinfo_activity_student_title,tool_usertours |
| Description | tour_activityinfo_activity_student_content,tool_usertours |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
@javascript
Scenario: User can choose the the content type of the tour step
Given I open the User tour settings page
And I click on "View" "link" in the "My first tour" "table_row"
When I click on "New step" "link"
Then "Content type" "select" should exist
And the "Content type" select box should contain "Language string ID"
And the "Content type" select box should contain "Manual"
And I select "Language string ID" from the "Content type" singleselect
And I should see " Language string ID"
And I should not see "Content" in the "#fitem_id_content" "css_element"
And I select "Manual" from the "Content type" singleselect
And I should see "Content" in the "#fitem_id_content" "css_element"
And I should not see "Language string ID" in the "#fitem_id_contentlangstring" "css_element"
@javascript
Scenario: Create a new step with Moodle Language content type
Given I open the User tour settings page
And I click on "View" "link" in the "My first tour" "table_row"
And I click on "New step" "link"
And I set the field "Title" to "tour_activityinfo_course_teacher_title,tool_usertours"
And I select "Language string ID" from the "Content type" singleselect
And I set the field "Language string ID" to "tour_activityinfo_course_teacher_content_abc,tool_usertours"
When I press "Save changes"
Then I should see "Invalid language string ID"
And I set the field "Language string ID" to "tour_activityinfo_course_teacher_content,tool_usertours"
And I press "Save changes"
And I should see "New: Activity information"
And I should see "New course settings 'Show completion conditions' and 'Show activity dates' enable you to choose whether activity completion conditions (if set) and/or dates are displayed for students on the course page."
And I click on "Edit" "link" in the "New: Activity information" "table_row"
And I should see "Editing \"New: Activity information\""
And the field "Title" matches value "tour_activityinfo_course_teacher_title,tool_usertours"
And the field "Language string ID" matches value "tour_activityinfo_course_teacher_content,tool_usertours"
@javascript
Scenario: Create a new step with manual content type
Given I open the User tour settings page
And I click on "View" "link" in the "My first tour" "table_row"
And I click on "New step" "link"
And I set the field "Title" to "tour_activityinfo_course_teacher_title,tool_usertours"
And I select "Manual" from the "Content type" singleselect
And I set the field "id_content" to "<p><strong>Test content</strong></p>"
And I press "Save changes"
And I should see "New: Activity information"
And I should see "Test content"
And I click on "Edit" "link" in the "New: Activity information" "table_row"
And I should see "Editing \"New: Activity information\""
And I should not see "Language string ID" in the "#fitem_id_contentlangstring" "css_element"
And the field "Title" matches value "tour_activityinfo_course_teacher_title,tool_usertours"
And the field "id_content" matches value "<p><strong>Test content</strong></p>"
@javascript
Scenario: Tour name and description can be translatable
Given I open the User tour settings page
And I should see "New: Activity information"
And I should see "Activity dates plus what to do to complete the activity are shown on the activity page."
When I click on "View" "link" in the "New: Activity information" "table_row"
Then I should see "New: Activity information"
And I should see "This is the 'New: Activity information' tour. It applies to the path '/my/%'."
And I click on "edit the tour defaults" "link"
And I should see "New: Activity information"
And the field "Name" matches value "tour_activityinfo_activity_student_title,tool_usertours"
And the field "Description" matches value "tour_activityinfo_activity_student_content,tool_usertours"
@@ -0,0 +1,40 @@
@tool @tool_usertours
Feature: Apply Moodle filter to a tour
In order to give more content to a tour
As an administrator
I need to create a user tour with Moodle filters applied
Background:
Given I log in as "admin"
And the following "courses" exist:
| shortname | fullname |
| C1 | Course 1 |
And the following "activities" exist:
| activity | name | content | course |
| page | Page for Usertour | Content of page for Usertour | C1 |
And I add a new user tour with:
| Name | Activity names auto-linking tour |
| Description | Tour with activity names auto-linking filter |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
And I add steps to the "Activity names auto-linking tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Activity names auto-linking | Test Activity names auto-linking Filter Page for Usertour | Manual |
@javascript
Scenario: Add a new tour with Activity names auto-linking filter off
Given the "activitynames" filter is "off"
When I am on "Course 1" course homepage
Then I should see "Test Activity names auto-linking Filter" in the "Activity names auto-linking" "dialogue"
And I should see "Page for Usertour" in the "Activity names auto-linking" "dialogue"
And "Page for Usertour" "link" should not exist in the "Activity names auto-linking" "dialogue"
@javascript
Scenario: Add a new tour with Activity names auto-linking filter on
Given the "activitynames" filter is "on"
When I am on "Course 1" course homepage
Then I should see "Test Activity names auto-linking Filter" in the "Activity names auto-linking" "dialogue"
And I should see "Page for Usertour" in the "Activity names auto-linking" "dialogue"
And "Page for Usertour" "link" should exist in the "Activity names auto-linking" "dialogue"
And I click on "Page for Usertour" "link" in the "Activity names auto-linking" "dialogue"
And I should see "Content of page for Usertour"
@@ -0,0 +1,222 @@
@tool @tool_usertours
Feature: Apply tour filters to a tour
In order to give more directed tours
As an administrator
I need to create a user tour with filters applied
@javascript
Scenario: Add a tour for a specific role
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
And the following "users" exist:
| username |
| editor1 |
| teacher1 |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| editor1 | C1 | editingteacher |
| teacher1 | C1 | teacher |
| student1 | C1 | student |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| Role | Student,Non-editing teacher |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your course tour. | Manual |
And I log out
And I log in as "editor1"
When I am on "Course 1" course homepage
Then I should not see "Welcome to your course tour."
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I should see "Welcome to your course tour."
And I click on "Got it" "button"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I should see "Welcome to your course tour."
@javascript
Scenario: Add tour for a specific category and its subcategory
Given the following "categories" exist:
| name | category | idnumber |
| MainCat | 0 | CAT1 |
| SubCat | CAT1 | CAT2 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | CAT1 |
| Course 2 | C2 | CAT2 |
And the following "users" exist:
| username |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| Category | MainCat |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your course tour. | Manual |
And I log out
And I log in as "student1"
When I am on "Course 1" course homepage
And I wait until the page is ready
Then I should see "Welcome to your course tour."
When I am on "Course 2" course homepage
And I wait until the page is ready
Then I should see "Welcome to your course tour."
@javascript
Scenario: Add tour for a specific courseformat
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
| Course 2 | C2 | weeks |
And the following "users" exist:
| username |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| Course format | Weekly sections |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your course tour. | Manual |
And I log out
And I log in as "student1"
When I am on "Course 1" course homepage
And I wait until the page is ready
Then I should not see "Welcome to your course tour."
When I am on "Course 2" course homepage
And I wait until the page is ready
Then I should see "Welcome to your course tour."
@javascript
Scenario: Add tour for a specific course
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
| Course 2 | C2 | weeks |
And the following "users" exist:
| username |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| Courses | C1 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your course tour. | Manual |
And I log out
And I log in as "student1"
When I am on "Course 1" course homepage
And I wait until the page is ready
Then I should see "Welcome to your course tour."
When I am on "Course 2" course homepage
And I wait until the page is ready
Then I should not see "Welcome to your course tour."
@javascript
Scenario: Add tours with CSS selectors
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
| Course 2 | C2 | topics | 1 |
And the following "activities" exist:
| activity | course | name | firstpagetitle | wikimode | idnumber | intro | type |
| wiki | C1 | Test wiki name | First page | collaborative | | | |
| forum | C2 | Test forum name | | | 001 | Test forum description | general |
And I log in as "admin"
And I add a new user tour with:
| Name | Wiki tour |
| Description | A tour with both matches |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| CSS selector | .modtype_wiki |
And I add steps to the "Wiki tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to the Wiki tour | Manual |
And I add a new user tour with:
| Name | Forum tour |
| Description | A tour with both matches |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| CSS selector | .modtype_forum |
And I add steps to the "Forum tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to the Forum tour | Manual |
And I am on "Course 1" course homepage
Then I should see "Welcome to the Wiki tour"
And I am on "Course 2" course homepage
Then I should see "Welcome to the Forum tour"
@javascript
Scenario: Check filtering respects the sort order
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | The first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| CSS selector | #page-my-index |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to the First tour | Manual |
And I add a new user tour with:
| Name | Second tour |
| Description | The second tour |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
| CSS selector | #page-my-index |
And I add steps to the "Second tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to the Second tour | Manual |
And I add a new user tour with:
| Name | Third tour |
| Description | The third tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| CSS selector | #page-my-index |
And I add steps to the "Third tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to the Third tour | Manual |
And I am on homepage
Then I should see "Welcome to the First tour"
And I open the User tour settings page
And I click on "Move tour down" "link" in the "The first tour" "table_row"
And I click on "Move tour down" "link" in the "The first tour" "table_row"
And I am on homepage
Then I should see "Welcome to the Third tour"
@@ -0,0 +1,100 @@
@tool @tool_usertours
Feature: Steps can be navigated within a tour
In order to use a tour effectively
As a user
I can navigate its steps
@javascript
Scenario: Clicking on items in the page should not end the tour
Given I log in as "admin"
And I add a new user tour with:
| Name | Calendar tour |
| Description | Calendar tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
And I add steps to the "Calendar tour" tour:
| targettype | Block | Title | id_content | Content type |
| Block | Calendar | Calendar events | This is the calendar block | Manual |
And I change window size to "large"
And I follow "Dashboard"
And I wait until the page is ready
And I should see "This is the calendar block"
When I click on ".block_calendar_month .calendar-controls .next" "css_element"
And I wait until the page is ready
Then I should see "Calendar events"
@javascript
Scenario: End tour button text for one step tours
Given I log in as "admin"
And I add a new user tour with:
| Name | Calendar tour |
| Description | Calendar tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
And I add steps to the "Calendar tour" tour:
| targettype | Block | Title | id_content | Content type |
| Block | Calendar | Calendar events | This is the calendar block | Manual |
And I change window size to "large"
And I follow "Dashboard"
And I wait until the page is ready
And I should see "This is the calendar block"
Then I should see "Got it"
@javascript
Scenario: End tour button text for multiple step tours
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual |
And I add steps to the "First tour" tour:
| targettype | targetvalue_block | Title | id_content | Content type |
| Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | Manual |
| Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | Manual |
When I am on homepage
Then I should see "Skip tour"
And I should see "Next (1/3)"
And I click on "Next (1/3)" "button" in the "Welcome" "dialogue"
And I should see "Skip tour"
And I click on "Next (2/3)" "button" in the "Timeline" "dialogue"
And I should see "End tour"
@javascript
Scenario: Customised 'end tour' button text for one step tours
Given I log in as "admin"
And I add a new user tour with:
| Name | Calendar tour |
| Description | Calendar tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| End tour button's label | CustomText |
And I add steps to the "Calendar tour" tour:
| targettype | Block | Title | id_content | Content type |
| Block | Calendar | Calendar events | This is the calendar block | Manual |
And I change window size to "large"
And I follow "Dashboard"
And I wait until the page is ready
And I should see "This is the calendar block"
Then I should see "CustomText"
@javascript
Scenario: Customised 'end tour' button text for one step tours can be translatable
Given I log in as "admin"
And I add a new user tour with:
| Name | Calendar tour |
| Description | Calendar tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| End tour button's label | exporttour,tool_usertours |
And I add steps to the "Calendar tour" tour:
| targettype | Block | Title | id_content | Content type |
| Block | Calendar | Calendar events | This is the calendar block | Manual |
And I change window size to "large"
And I follow "Dashboard"
And I wait until the page is ready
And I should see "This is the calendar block"
Then I should see "Export tour"
@@ -0,0 +1,29 @@
@tool @tool_usertours
Feature: Prevent yours from being marked as complete
In order to impart key information
As an administrator
I can prevent a user tour from being marked as complete
Background:
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | FRONTPAGE |
| Tour is enabled | 1 |
| Show with backdrop | 1 |
# 2 = tour::SHOW_TOUR_ON_EACH_PAGE_VISIT
| Show tour | 2 |
And I add steps to the "First tour" tour:
| targettype | Title | id_content | Content type |
| Display in middle of page | Welcome | Welcome tour. | Manual |
@javascript
Scenario: Ending the tour should not mark it as complete
# Changing the window viewport to mobile so we will have the footer section.
Given I am on site homepage
And I should see "Welcome"
And I press "Got it"
And I should not see "Welcome"
When I am on site homepage
Then I should see "Welcome"
+352
View File
@@ -0,0 +1,352 @@
<?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 tool_usertours;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/helper_trait.php');
/**
* Tests for cache.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\cache
*/
class cache_test extends \advanced_testcase {
// There are shared helpers for these tests in the helper trait.
use \tool_usertours_helper_trait;
/**
* Test that get_enabled_tourdata does not return disabled tours.
*/
public function test_get_enabled_tourdata_disabled(): void {
$this->resetAfterTest();
$tour = $this->helper_create_tour((object)['enabled' => false]);
$this->helper_create_step((object) ['tourid' => $tour->get_id()]);
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEmpty($matches);
}
/**
* Test that get_enabled_tourdata does not return an enabled but empty tour.
*/
public function test_get_enabled_tourdata_enabled_no_steps(): void {
$this->resetAfterTest();
$this->helper_create_tour();
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEmpty($matches);
}
/**
* Test that get_enabled_tourdata returns a tour with steps.
*/
public function test_get_enabled_tourdata_enabled(): void {
$this->resetAfterTest();
// Create two tours. Only the second has steps.
$this->helper_create_tour();
$tour2 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertNotEmpty($matches);
$this->assertCount(1, $matches);
$match = array_shift($matches);
$this->assertEquals($tour2->get_id(), $match->id);
}
/**
* Test that get_enabled_tourdata returns tours in the correct sortorder
*/
public function test_get_enabled_tourdata_enabled_sortorder(): void {
$this->resetAfterTest();
$tour1 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$tour2 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertNotEmpty($matches);
$this->assertCount(2, $matches);
$match = array_shift($matches);
$this->assertEquals($tour1->get_id(), $match->id);
$match = array_shift($matches);
$this->assertEquals($tour2->get_id(), $match->id);
}
/**
* Test that caching prevents additional DB reads.
*/
public function test_get_enabled_tourdata_single_fetch(): void {
global $DB;
$this->resetAfterTest();
$tour1 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$tour2 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
// Only one read for the first call.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// No subsequent reads for any further calls.
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
}
/**
* Data provider for get_matching_tourdata.
*
* @return array
*/
public static function get_matching_tourdata_provider(): array {
$tourconfigs = [
(object) [
'name' => 'my_exact_1',
'pathmatch' => '/my/view.php',
],
(object) [
'name' => 'my_failed_regex',
'pathmatch' => '/my/*.php',
],
(object) [
'name' => 'my_glob_1',
'pathmatch' => '/my/%',
],
(object) [
'name' => 'my_glob_2',
'pathmatch' => '/my/%',
],
(object) [
'name' => 'frontpage_only',
'pathmatch' => 'FRONTPAGE',
],
(object) [
'name' => 'frontpage_match',
'pathmatch' => '/?%',
],
];
return [
'Matches expected glob' => [
$tourconfigs,
'/my/index.php',
['my_glob_1', 'my_glob_2'],
],
'Matches expected glob and exact' => [
$tourconfigs,
'/my/view.php',
['my_exact_1', 'my_glob_1', 'my_glob_2'],
],
'Special constant FRONTPAGE must match front page only' => [
$tourconfigs,
'/',
['frontpage_only'],
],
'Standard frontpage URL matches both the special constant, and a correctly formed pathmatch' => [
$tourconfigs,
'/?redirect=0',
['frontpage_only', 'frontpage_match'],
],
];
}
/**
* Tests for the get_matching_tourdata function.
*
* @dataProvider get_matching_tourdata_provider
* @param array $tourconfigs The configuration for the tours to create
* @param string $targetmatch The match to be tested
* @param array $expected An array containing the ordered names of the expected tours
*/
public function test_get_matching_tourdata($tourconfigs, $targetmatch, $expected): void {
$this->resetAfterTest();
foreach ($tourconfigs as $tourconfig) {
$tour = $this->helper_create_tour($tourconfig);
$this->helper_create_step((object) ['tourid' => $tour->get_id()]);
}
$matches = \tool_usertours\cache::get_matching_tourdata(new \moodle_url($targetmatch));
$this->assertCount(count($expected), $matches);
for ($i = 0; $i < count($matches); $i++) {
$match = array_shift($matches);
$this->assertEquals($expected[$i], $match->name);
}
}
/**
* Test that notify_tour_change clears the cache.
*/
public function test_notify_tour_change(): void {
global $DB;
$this->resetAfterTest();
$tour1 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$tour2 = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
// Only one read for the first call.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// No subsequent reads for any further calls.
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// Reset.
\tool_usertours\cache::notify_tour_change();
// An additional DB read now.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_enabled_tourdata();
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
}
/**
* Test that get_stepdata returns an empty array when no steps were found.
*/
public function test_get_stepdata_no_steps(): void {
$this->resetAfterTest();
$tour = $this->helper_create_tour((object)['enabled' => false]);
$data = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertIsArray($data);
$this->assertEmpty($data);
}
/**
* Test that get_stepdata returns an empty array when no steps were found.
*/
public function test_get_stepdata_correct_tour(): void {
$this->resetAfterTest();
$tour1 = $this->helper_create_tour((object)['enabled' => false]);
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
$tour2 = $this->helper_create_tour((object)['enabled' => false]);
$data = \tool_usertours\cache::get_stepdata($tour1->get_id());
$this->assertIsArray($data);
$this->assertCount(3, $data);
$data = \tool_usertours\cache::get_stepdata($tour2->get_id());
$this->assertIsArray($data);
$this->assertEmpty($data);
}
/**
* Test that get_stepdata returns an array containing multiple steps in
* the same order.
*
* This is very difficult to determine because the act of changing the
* order will likely change the DB natural sorting.
*/
public function test_get_stepdata_ordered_steps(): void {
$this->resetAfterTest();
$tour = $this->helper_create_tour((object)['enabled' => false]);
$steps = [];
$steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
$steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
$steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
$steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
$steps[0]->set_sortorder(10)->persist();
$data = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertIsArray($data);
$this->assertCount(4, $data);
// Re-order the steps.
usort($steps, function ($a, $b) {
return ($a->get_sortorder() < $b->get_sortorder()) ? -1 : 1;
});
for ($i = 0; $i < count($data); $i++) {
$step = array_shift($data);
$this->assertEquals($steps[$i]->get_id(), $step->id);
}
}
/**
* Test that caching prevents additional DB reads.
*/
public function test_get_stepdata_single_fetch(): void {
global $DB;
$this->resetAfterTest();
$tour = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour->get_id()]);
// Only one read for the first call.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// No subsequent reads for any further calls.
$matches = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
}
/**
* Test that notify_step_change clears the cache.
*/
public function test_notify_step_change(): void {
global $DB;
$this->resetAfterTest();
$tour = $this->helper_create_tour();
$this->helper_create_step((object) ['tourid' => $tour->get_id()]);
// Only one read for the first call.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// No subsequent reads for any further calls.
$matches = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
// Reset.
\tool_usertours\cache::notify_step_change($tour->get_id());
// An additional DB read now.
$startreads = $DB->perf_get_reads();
$matches = \tool_usertours\cache::get_stepdata($tour->get_id());
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
}
}
@@ -0,0 +1,45 @@
<?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/>.
/**
* Hook fixtures for testing of hooks.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\test\hook\clientside_filter_for_serverside_hook;
defined('MOODLE_INTERNAL') || die();
final class filter_class extends \tool_usertours\local\clientside_filter\clientside_filter {
}
final class callback {
public static function callme(
\tool_usertours\hook\before_serverside_filter_fetch $hook
): void {
$hook->add_filter_by_classname(filter_class::class);
}
}
$callbacks = [
[
'hook' => \tool_usertours\hook\before_serverside_filter_fetch::class,
'callback' => callback::class . '::callme',
],
];
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* Hook fixtures for testing of hooks.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\test\hook;
defined('MOODLE_INTERNAL') || die();
final class serverside_filter_fixture extends \tool_usertours\local\filter\base {
}
final class clientside_filter_fixture extends \tool_usertours\local\clientside_filter\clientside_filter {
}
final class hook_fixtures {
public static function example_serverside_hook(
\tool_usertours\hook\before_serverside_filter_fetch $hook
): void {
// Add a valid serverside and an invalid clientside filter.
$hook->add_filter_by_classname(\tool_usertours\test\hook\serverside_filter_fixture::class);
$hook->remove_filter_by_classname(\tool_usertours\local\filter\accessdate::class);
}
public static function example_clientside_hook(
\tool_usertours\hook\before_clientside_filter_fetch $hook
): void {
$hook->add_filter_by_classname(\tool_usertours\test\hook\clientside_filter_fixture::class);
$hook->remove_filter_by_classname(\tool_usertours\local\clientside_filter\cssselector::class);
}
}
$callbacks = [
[
'hook' => \tool_usertours\hook\before_serverside_filter_fetch::class,
'callback' => \tool_usertours\test\hook\hook_fixtures::class . '::example_serverside_hook',
],
[
'hook' => \tool_usertours\hook\before_clientside_filter_fetch::class,
'callback' => \tool_usertours\test\hook\hook_fixtures::class . '::example_clientside_hook',
],
];
@@ -0,0 +1,43 @@
<?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/>.
/**
* Hook fixtures for testing of hooks.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
final class nocomponent_clientside_filter_fixture extends \tool_usertours\local\clientside_filter\clientside_filter {
}
final class nocomponent_clientside_hook_fixtures {
public static function example_clientside_hook(
\tool_usertours\hook\before_clientside_filter_fetch $hook
): void {
$hook->add_filter_by_classname(\nocomponent_clientside_filter_fixture::class);
}
}
$callbacks = [
[
'hook' => \tool_usertours\hook\before_clientside_filter_fetch::class,
'callback' => \nocomponent_clientside_hook_fixtures::class . '::example_clientside_hook',
],
];
@@ -0,0 +1,43 @@
<?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/>.
/**
* Hook fixtures for testing of hooks.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
final class nocomponent_serverside_filter_fixture {
}
final class nocomponent_serverside_hook_fixtures {
public static function example_serverside_hook(
\tool_usertours\hook\before_serverside_filter_fetch $hook
): void {
$hook->add_filter_by_classname(\nocomponent_serverside_filter_fixture::class);
}
}
$callbacks = [
[
'hook' => \tool_usertours\hook\before_serverside_filter_fetch::class,
'callback' => \nocomponent_serverside_hook_fixtures::class . '::example_serverside_hook',
],
];
@@ -0,0 +1,45 @@
<?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/>.
/**
* Hook fixtures for testing of hooks.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\test\hook\serverside_filter_for_clientside_hook;
defined('MOODLE_INTERNAL') || die();
final class filter_class extends \tool_usertours\local\filter\base {
}
final class callback {
public static function callme(
\tool_usertours\hook\before_clientside_filter_fetch $hook
): void {
$hook->add_filter_by_classname(filter_class::class);
}
}
$callbacks = [
[
'hook' => \tool_usertours\hook\before_clientside_filter_fetch::class,
'callback' => callback::class . '::callme',
],
];
+188
View File
@@ -0,0 +1,188 @@
<?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 tool_usertours;
/**
* Tests for helper.
*
* @package tool_usertours
* @category test
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\helper
* @covers \tool_usertours\hook\before_serverside_filter_fetch
* @covers \tool_usertours\hook\before_clientside_filter_fetch
*/
final class helper_test extends \advanced_testcase {
/**
* Data Provider for get_string_from_input.
*
* @return array
*/
public static function get_string_from_input_provider(): array {
return [
'Text' => [
'example',
'example',
],
'Text which looks like a langstring' => [
'example,fakecomponent',
'example,fakecomponent',
],
'Text which is a langstring' => [
'administration,core',
'Administration',
],
'Text which is a langstring but uses "moodle" instead of "core"' => [
'administration,moodle',
'Administration',
],
'Text which is a langstring, but with extra whitespace' => [
' administration,moodle ',
'Administration',
],
'Looks like a langstring, but has incorrect space around comma' => [
'administration , moodle',
'administration , moodle',
],
];
}
/**
* Ensure that the get_string_from_input function returns langstring strings correctly.
*
* @dataProvider get_string_from_input_provider
* @param string $string The string to test
* @param string $expected The expected result
*/
public function test_get_string_from_input($string, $expected): void {
$this->assertEquals($expected, helper::get_string_from_input($string));
}
public function test_get_all_filters(): void {
$filters = helper::get_all_filters();
$this->assertIsArray($filters);
array_map(
function ($filter) {
$this->assertIsString($filter);
$this->assertTrue(class_exists($filter));
$this->assertTrue(is_a($filter, \tool_usertours\local\filter\base::class, true));
$rc = new \ReflectionClass($filter);
$this->assertTrue($rc->isInstantiable());
},
$filters,
);
$this->assertNotContains(\tool_usertours\test\hook\serverside_filter_fixture::class, $filters);
$this->assertNotContains(\tool_usertours\test\hook\clientside_filter_fixture::class, $filters);
$this->assertContains(\tool_usertours\local\filter\accessdate::class, $filters);
$this->assertContains(\tool_usertours\local\clientside_filter\cssselector::class, $filters);
$filters = helper::get_all_clientside_filters();
array_map(
function ($filter) {
$this->assertIsString($filter);
},
$filters,
);
}
public function test_get_invalid_server_filter(): void {
\core\di::set(
\core\hook\manager::class,
\core\hook\manager::phpunit_get_instance([
'test_plugin1' => __DIR__ . '/fixtures/invalid_serverside_hook_fixture.php',
]),
);
$this->expectException(\coding_exception::class);
helper::get_all_filters();
}
public function test_clientside_filter_for_serverside_hook(): void {
\core\di::set(
\core\hook\manager::class,
\core\hook\manager::phpunit_get_instance([
'test_plugin1' => __DIR__ . '/fixtures/clientside_filter_for_serverside_hook.php',
]),
);
$this->expectException(\coding_exception::class);
helper::get_all_filters();
}
public function test_serverside_filter_for_clientside_hook(): void {
\core\di::set(
\core\hook\manager::class,
\core\hook\manager::phpunit_get_instance([
'test_plugin1' => __DIR__ . '/fixtures/serverside_filter_for_clientside_hook.php',
]),
);
$this->expectException(\coding_exception::class);
helper::get_all_clientside_filters();
}
public function test_filter_hooks(): void {
\core\di::set(
\core\hook\manager::class,
\core\hook\manager::phpunit_get_instance([
'test_plugin1' => __DIR__ . '/fixtures/hook_fixtures.php',
]),
);
$filters = helper::get_all_filters();
$this->assertIsArray($filters);
// Check the modifications from the serverside hook.
$this->assertContains(\tool_usertours\test\hook\serverside_filter_fixture::class, $filters);
$this->assertNotContains(\tool_usertours\test\hook\another_clientside_filter_fixture::class, $filters);
$this->assertNotContains(\tool_usertours\local\filter\accessdate::class, $filters);
// Check the modifications from the clientside hook.
$this->assertContains(\tool_usertours\test\hook\clientside_filter_fixture::class, $filters);
$this->assertNotContains(\tool_usertours\test\hook\another_serverside_filter_fixture::class, $filters);
$this->assertNotContains(\tool_usertours\local\clientside_filter\cssselector::class, $filters);
array_map(
function ($filter) {
$this->assertIsString($filter);
$this->assertTrue(class_exists($filter));
$this->assertTrue(is_a($filter, \tool_usertours\local\filter\base::class, true));
$rc = new \ReflectionClass($filter);
$this->assertTrue($rc->isInstantiable());
},
$filters,
);
}
public function test_get_clientside_filter_module_names(): void {
\core\di::set(
\core\hook\manager::class,
\core\hook\manager::phpunit_get_instance([
'test_plugin1' => __DIR__ . '/fixtures/invalid_clientside_hook_fixture.php',
]),
);
$filters = helper::get_all_clientside_filters();
$this->expectException(\coding_exception::class);
$this->expectExceptionMessageMatches('/Could not determine component/');
helper::get_clientside_filter_module_names($filters);
}
}
@@ -0,0 +1,96 @@
<?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/>.
/**
* Helpers for unit tests.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait tool_usertours_helper_trait {
/**
* A helper to create an empty tour.
*
* @param stdClass $tourconfig The configuration for the new tour
* @param bool $persist Whether to persist the data
* @return \tool_usertours\tour
*/
public function helper_create_tour(\stdClass $tourconfig = null, $persist = true) {
$minvalues = [
'id' => null,
'pathmatch' => '/my/%',
'enabled' => true,
'name' => '',
'description' => '',
'configdata' => '',
'displaystepnumbers' => true,
];
if ($tourconfig === null) {
$tourconfig = new \stdClass();
}
foreach ($minvalues as $key => $value) {
if (!isset($tourconfig->$key)) {
$tourconfig->$key = $value;
}
}
$tour = \tool_usertours\tour::load_from_record($tourconfig, true);
if ($persist) {
$tour->persist(true);
}
return $tour;
}
/**
* A helper to create an empty step for the specified tour.
*
* @param stdClass $stepconfig The configuration for the new step
* @param bool $persist Whether to persist the data
* @return \tool_usertours\step
*/
public function helper_create_step(\stdClass $stepconfig = null, $persist = true) {
$minvalues = [
'id' => null,
'title' => '',
'content' => '',
'targettype' => \tool_usertours\target::TARGET_UNATTACHED,
'targetvalue' => '',
'sortorder' => 0,
'configdata' => '',
];
if ($stepconfig === null) {
$stepconfig = new \stdClass();
}
foreach ($minvalues as $key => $value) {
if (!isset($stepconfig->$key)) {
$stepconfig->$key = $value;
}
}
$step = \tool_usertours\step::load_from_record($stepconfig, true);
if ($persist) {
$step->persist(true);
}
return $step;
}
}
+365
View File
@@ -0,0 +1,365 @@
<?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 tool_usertours;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/formslib.php');
require_once(__DIR__ . '/helper_trait.php');
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\manager
*/
class manager_test extends \advanced_testcase {
// There are shared helpers for these tests in the helper trait.
use \tool_usertours_helper_trait;
/**
* @var moodle_database
*/
protected $db;
/**
* Setup to store the DB reference.
*/
public function setUp(): void {
global $DB;
$this->db = $DB;
}
/**
* Tear down to restore the original DB reference.
*/
public function tearDown(): void {
global $DB;
$DB = $this->db;
}
/**
* Helper to mock the database.
*
* @return moodle_database
*/
public function mock_database() {
global $DB;
$DB = $this->getMockBuilder('moodle_database')->getMock();
return $DB;
}
/**
* Data provider to ensure that all modification actions require the session key.
*
* @return array
*/
public static function sesskey_required_provider(): array {
$tourid = rand(1, 100);
$stepid = rand(1, 100);
return [
'Tour removal' => [
'delete_tour',
[$tourid],
],
'Step removal' => [
'delete_step',
[$stepid],
],
'Tour visibility' => [
'show_hide_tour',
[$tourid, true],
],
'Move step' => [
'move_step',
[$stepid],
],
];
}
/**
* Ensure that all modification actions require the session key.
*
* @dataProvider sesskey_required_provider
* @param string $function The function to test
* @param array $arguments The arguments to pass with it
*/
public function test_sesskey_required($function, $arguments): void {
$manager = new \tool_usertours\manager();
$rc = new \ReflectionClass('\tool_usertours\manager');
$rcm = $rc->getMethod($function);
$this->expectException('moodle_exception');
$rcm->invokeArgs($manager, $arguments);
}
/**
* Data provider for test_move_tour
*
* @return array
*/
public static function move_tour_provider(): array {
$alltours = [
['name' => 'Tour 1'],
['name' => 'Tour 2'],
['name' => 'Tour 3'],
];
return [
'Move up' => [
$alltours,
'Tour 2',
\tool_usertours\helper::MOVE_UP,
0,
],
'Move down' => [
$alltours,
'Tour 2',
\tool_usertours\helper::MOVE_DOWN,
2,
],
'Move up (first)' => [
$alltours,
'Tour 1',
\tool_usertours\helper::MOVE_UP,
0,
],
'Move down (last)' => [
$alltours,
'Tour 3',
\tool_usertours\helper::MOVE_DOWN,
2,
],
];
}
/**
* Test moving tours (changing sortorder)
*
* @dataProvider move_tour_provider
*
* @param array $alltours
* @param string $movetourname
* @param int $direction
* @param int $expectedsortorder
* @return void
*/
public function test_move_tour($alltours, $movetourname, $direction, $expectedsortorder): void {
global $DB;
$this->resetAfterTest();
// Clear out existing tours so ours are the only ones, otherwise we can't predict the sortorder.
$DB->delete_records('tool_usertours_tours');
foreach ($alltours as $tourconfig) {
$this->helper_create_tour((object) $tourconfig);
}
// Load our tour to move.
$record = $DB->get_record('tool_usertours_tours', ['name' => $movetourname]);
$tour = \tool_usertours\tour::load_from_record($record);
// Call protected method via reflection.
$class = new \ReflectionClass(\tool_usertours\manager::class);
$method = $class->getMethod('_move_tour');
$method->invokeArgs(null, [$tour, $direction]);
// Assert expected sortorder.
$this->assertEquals($expectedsortorder, $tour->get_sortorder());
}
/**
* Data Provider for get_matching_tours tests.
*
* @return array
*/
public static function get_matching_tours_provider(): array {
global $CFG;
$alltours = [
[
'pathmatch' => '/my/%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/my/%',
'enabled' => true,
'name' => 'My tour enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/my/%',
'enabled' => true,
'name' => 'My tour enabled 2',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/my/%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => true,
'name' => 'course tour with additional params enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => true,
'name' => 'course tour enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
];
return
[
'No matches found' => [
$alltours,
$CFG->wwwroot . '/some/invalid/value',
[],
],
'Never return a disabled tour' => [
$alltours,
$CFG->wwwroot . '/my/index.php',
['My tour enabled', 'My tour enabled 2'],
],
'My not course' => [
$alltours,
$CFG->wwwroot . '/my/index.php',
['My tour enabled', 'My tour enabled 2'],
],
'My with params' => [
$alltours,
$CFG->wwwroot . '/my/index.php?id=42',
['My tour enabled', 'My tour enabled 2'],
],
'Course with params' => [
$alltours,
$CFG->wwwroot . '/course/?id=42',
['course tour enabled'],
],
'Course with params and trailing content' => [
$alltours,
$CFG->wwwroot . '/course/?id=42&foo=bar',
['course tour with additional params enabled', 'course tour enabled'],
],
];
}
/**
* Tests for the get_matching_tours function.
*
* @dataProvider get_matching_tours_provider
* @param array $alltours The list of tours to insert.
* @param string $url The URL to test.
* @param array $expected List of names of the expected matching tours.
*/
public function test_get_matching_tours(array $alltours, string $url, array $expected): void {
$this->resetAfterTest();
$this->setGuestUser();
foreach ($alltours as $tourconfig) {
$tour = $this->helper_create_tour((object) $tourconfig);
$this->helper_create_step((object) ['tourid' => $tour->get_id()]);
}
$matches = \tool_usertours\manager::get_matching_tours(new \moodle_url($url));
$this->assertEquals(count($expected), count($matches));
for ($i = 0; $i < count($matches); $i++) {
$this->assertEquals($expected[$i], $matches[$i]->get_name());
}
}
/**
* Test that no matching tours are returned if there is pending site policy agreement.
*/
public function test_get_matching_tours_for_user_without_site_policy_agreed(): void {
global $CFG;
$this->resetAfterTest();
$this->setGuestUser();
$tour = $this->helper_create_tour((object) [
'pathmatch' => '/%',
'enabled' => true,
'name' => 'Test tour',
'description' => '',
'configdata' => '',
]);
$this->helper_create_step((object) [
'tourid' => $tour->get_id(),
]);
$matches = \tool_usertours\manager::get_matching_tours(new \moodle_url('/'));
$this->assertEquals(1, count($matches));
$CFG->sitepolicyguest = 'https://example.com';
$matches = \tool_usertours\manager::get_matching_tours(new \moodle_url('/'));
$this->assertEmpty($matches);
}
}
@@ -0,0 +1,183 @@
<?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 tool_usertours\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
use tool_usertours\tour;
use tool_usertours\privacy\provider;
/**
* Unit tests for the tool_usertours implementation of the privacy API.
*
* @package tool_usertours
* @category test
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\privacy\provider
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Helper method for creating a tour
*
* @return tour
*/
protected function create_test_tour(): tour {
return (new tour())
->set_name('test_tour')
->set_description('Test tour')
->set_enabled(true)
->set_pathmatch('/')
->persist();
}
/**
* Ensure that get_metadata exports valid content.
*/
public function test_get_metadata(): void {
$items = new collection('tool_usertours');
$result = provider::get_metadata($items);
$this->assertSame($items, $result);
$this->assertInstanceOf(collection::class, $result);
}
/**
* Ensure that export_user_preferences returns no data if the user has completed no tours.
*/
public function test_export_user_preferences_no_pref(): void {
$user = \core_user::get_user_by_username('admin');
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertFalse($writer->has_any_data());
}
/**
* Ensure that export_user_preferences returns request completion data.
*/
public function test_export_user_preferences_completed(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$tour = $this->create_test_tour();
$user = \core_user::get_user_by_username('admin');
$tour->mark_user_completed();
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$prefs = $writer->get_user_preferences('tool_usertours');
$this->assertCount(1, (array) $prefs);
}
/**
* Ensure that export_user_preferences returns request completion data.
*/
public function test_export_user_preferences_requested(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$tour = $this->create_test_tour();
$user = \core_user::get_user_by_username('admin');
$tour->mark_user_completed();
$tour->request_user_reset();
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$prefs = $writer->get_user_preferences('tool_usertours');
$this->assertCount(2, (array) $prefs);
}
/**
* Make sure we are exporting preferences for the correct user
*/
public function test_export_user_preferences_correct_user(): void {
$this->resetAfterTest();
$tour = $this->create_test_tour();
// Create test user, mark them as having completed the tour.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$tour->mark_user_completed();
// Switch to admin user, mark them as having reset the tour.
$this->setAdminUser();
$tour->request_user_reset();
// Export test users preferences.
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$prefs = (array)$writer->get_user_preferences('tool_usertours');
$this->assertCount(1, $prefs);
// We should have received back the "completed tour" preference of the test user.
$this->assertStringStartsWith(
'You last marked the "' . $tour->get_name() . '" user tour as completed on',
reset($prefs)->description
);
}
/**
* Ensure that export_user_preferences excludes deleted tours.
*/
public function test_export_user_preferences_deleted_tour(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$tour1 = $this->create_test_tour();
$tour2 = $this->create_test_tour();
$user = \core_user::get_user_by_username('admin');
$alltours = $DB->get_records('tool_usertours_tours');
$tour1->mark_user_completed();
$tour2->mark_user_completed();
$tour2->remove();
$writer = writer::with_context(\context_system::instance());
provider::export_user_preferences($user->id);
$this->assertTrue($writer->has_any_data());
// We should have one preference.
$prefs = (array)$writer->get_user_preferences('tool_usertours');
$this->assertCount(1, $prefs);
// The preference should be related to the first tour.
$this->assertStringContainsString($tour1->get_name(), reset($prefs)->description);
}
}
@@ -0,0 +1,284 @@
<?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 tool_usertours;
/**
* Tests for role filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\local\filter\role
*/
class role_filter_test extends \advanced_testcase {
/**
* @var $course Test course
*/
protected $course;
/**
* @var $student Test student
*/
protected $student;
/**
* @var $teacher Test teacher
*/
protected $teacher;
/**
* @var $editingteacher Test editor
*/
protected $editingteacher;
/**
* @var $roles List of all roles
*/
protected $roles;
/** @var array Roles. */
protected array $testroles = [];
public function setUp(): void {
global $DB;
$this->resetAfterTest(true);
$generator = $this->getDataGenerator();
$this->course = $generator->create_course();
$this->roles = $DB->get_records_menu('role', [], null, 'shortname, id');
$this->testroles = ['student', 'teacher', 'editingteacher'];
foreach ($this->testroles as $role) {
$user = $this->$role = $generator->create_user();
$generator->enrol_user($user->id, $this->course->id, $this->roles[$role]);
}
}
/**
* Test the filter_matches function when any is set.
*/
public function test_filter_matches_any(): void {
$context = \context_course::instance($this->course->id);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', []);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
// The admin should always be able to view too.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when one role is set.
*/
public function test_filter_matches_single_role(): void {
$context = \context_course::instance($this->course->id);
$roles = [
'student',
];
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin can't view this one either.
$this->setAdminUser();
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when multiple roles are set.
*/
public function test_filter_matches_multiple_role(): void {
$context = \context_course::instance($this->course->id);
$roles = [
'teacher',
'editingteacher',
];
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin can't view this one either.
$this->setAdminUser();
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when one user has multiple roles.
*/
public function test_filter_matches_multiple_role_one_user(): void {
$context = \context_course::instance($this->course->id);
$roles = [
'student',
];
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->roles['teacher']);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin can't view this one either.
$this->setAdminUser();
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when it is targetted at an admin.
*/
public function test_filter_matches_multiple_role_only_admin(): void {
$context = \context_course::instance($this->course->id);
$roles = [
\tool_usertours\local\filter\role::ROLE_SITEADMIN,
];
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->roles['teacher']);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
// The admin can view this one because it's only aimed at them.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when multiple roles are set, including an admin user.
*/
public function test_filter_matches_multiple_role_including_admin(): void {
$context = \context_course::instance($this->course->id);
$roles = [
\tool_usertours\local\filter\role::ROLE_SITEADMIN,
'teacher',
'editingteacher',
];
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin can view this one because it's only aimed at them.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when an admin user has multiple roles.
*/
public function test_filter_matches_multiple_role_admin_user(): void {
global $USER;
$context = \context_course::instance($this->course->id);
$roles = [
\tool_usertours\local\filter\role::ROLE_SITEADMIN,
];
$this->setAdminUser();
$this->getDataGenerator()->enrol_user($USER->id, $this->course->id, $this->roles['student']);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// The admin can view this one because it's only aimed at them.
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test that the get_filter_options function does not include the guest roles.
*/
public function test_get_filter_options_no_guest_roles(): void {
create_role('Test Role', 'testrole', 'This is a test role', 'guest');
$allroles = role_get_names(null, ROLENAME_ALIAS);
$options = \tool_usertours\local\filter\role::get_filter_options();
foreach ($allroles as $role) {
$hasrole = isset($options[$role->shortname]);
if ($role->archetype === 'guest') {
$this->assertFalse($hasrole);
} else {
$this->assertTrue($hasrole);
}
}
}
}
+792
View File
@@ -0,0 +1,792 @@
<?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 tool_usertours;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\step
*/
class step_test extends \advanced_testcase {
/**
* @var moodle_database
*/
protected $db;
/**
* Setup to store the DB reference.
*/
public function setUp(): void {
global $DB;
$this->db = $DB;
}
/**
* Tear down to restore the original DB reference.
*/
public function tearDown(): void {
global $DB;
$DB = $this->db;
}
/**
* Helper to mock the database.
*
* @return moodle_database
*/
public function mock_database() {
global $DB;
$DB = $this->getMockBuilder('moodle_database')
->getMock();
return $DB;
}
/**
* Data provider for the dirty value tester.
*
* @return array
*/
public static function dirty_value_provider(): array {
return
[
'tourid' => [
'tourid',
[1],
],
'title' => [
'title',
['Lorem'],
],
'content' => [
'content',
['Lorem'],
],
'targettype' => [
'targettype',
['Lorem'],
],
'targetvalue' => [
'targetvalue',
['Lorem'],
],
'sortorder' => [
'sortorder',
[1],
],
'config' => [
'config',
['key', 'value'],
],
];
}
/**
* Test the fetch function.
*/
public function test_fetch(): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods(['reload_from_record'])
->getMock();
$idretval = rand(1, 100);
$DB = $this->mock_database();
$DB->method('get_record')
->willReturn($idretval);
$retval = rand(1, 100);
$step->expects($this->once())
->method('reload_from_record')
->with($this->equalTo($idretval))
->wilLReturn($retval);
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcm = $rc->getMethod('fetch');
$id = rand(1, 100);
$this->assertEquals($retval, $rcm->invoke($step, 'fetch', $id));
}
/**
* Test that setters mark things as dirty.
*
* @dataProvider dirty_value_provider
* @param string $name The key to update
* @param string $value The value to set
*/
public function test_dirty_values($name, $value): void {
$step = new \tool_usertours\step();
$method = 'set_' . $name;
call_user_func_array([$step, $method], $value);
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('dirty');
$this->assertTrue($rcp->getValue($step));
}
/**
* Provider for is_first_step.
*
* @return array
*/
public static function step_sortorder_provider(): array {
return [
[0, 5, true, false],
[1, 5, false, false],
[4, 5, false, true],
];
}
/**
* Test is_first_step.
*
* @dataProvider step_sortorder_provider
* @param int $sortorder The sortorder to check
* @param int $count Unused in this function
* @param bool $isfirst Whether this is the first step
* @param bool $islast Whether this is the last step
*/
public function test_is_first_step($sortorder, $count, $isfirst, $islast): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods(['get_sortorder'])
->getMock();
$step->expects($this->once())
->method('get_sortorder')
->willReturn($sortorder);
$this->assertEquals($isfirst, $step->is_first_step());
}
/**
* Test is_last_step.
*
* @dataProvider step_sortorder_provider
* @param int $sortorder The sortorder to check
* @param int $count Total number of steps for this test
* @param bool $isfirst Whether this is the first step
* @param bool $islast Whether this is the last step
*/
public function test_is_last_step($sortorder, $count, $isfirst, $islast): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods(['get_sortorder', 'get_tour'])
->getMock();
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->onlyMethods(['count_steps'])
->getMock();
$step->expects($this->once())
->method('get_tour')
->willReturn($tour);
$tour->expects($this->once())
->method('count_steps')
->willReturn($count);
$step->expects($this->once())
->method('get_sortorder')
->willReturn($sortorder);
$this->assertEquals($islast, $step->is_last_step());
}
/**
* Test get_config with no keys provided.
*/
public function test_get_config_no_keys(): void {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$rcp->setValue($step, $allvalues);
$this->assertEquals($allvalues, $step->get_config());
}
/**
* Data provider for get_config.
*
* @return array
*/
public static function get_config_provider(): array {
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$tourconfig = rand(1, 100);
$forcedconfig = rand(1, 100);
return [
'No initial config' => [
null,
null,
null,
$tourconfig,
false,
$forcedconfig,
(object) [],
],
'All values' => [
$allvalues,
null,
null,
$tourconfig,
false,
$forcedconfig,
$allvalues,
],
'Valid string value' => [
$allvalues,
'some',
null,
$tourconfig,
false,
$forcedconfig,
'value',
],
'Valid array value' => [
$allvalues,
'key',
null,
$tourconfig,
false,
$forcedconfig,
['somethingelse'],
],
'Invalid value' => [
$allvalues,
'notavalue',
null,
$tourconfig,
false,
$forcedconfig,
$tourconfig,
],
'Configuration value' => [
$allvalues,
'placement',
null,
$tourconfig,
false,
$forcedconfig,
$tourconfig,
],
'Invalid value with default' => [
$allvalues,
'notavalue',
'somedefault',
$tourconfig,
false,
$forcedconfig,
'somedefault',
],
'Value forced at target' => [
$allvalues,
'somevalue',
'somedefault',
$tourconfig,
true,
$forcedconfig,
$forcedconfig,
],
];
}
/**
* Test get_config with valid keys provided.
*
* @dataProvider get_config_provider
* @param object $values The config values
* @param string $key The key
* @param mixed $default The default value
* @param mixed $tourconfig The tour config
* @param bool $isforced Whether the setting is forced
* @param mixed $forcedvalue The example value
* @param mixed $expected The expected value
*/
public function test_get_config_valid_keys($values, $key, $default, $tourconfig, $isforced, $forcedvalue, $expected): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods(['get_target', 'get_targettype', 'get_tour'])
->getMock();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$rcp->setValue($step, $values);
$target = $this->getMockBuilder(\tool_usertours\local\target\base::class)
->disableOriginalConstructor()
->getMock();
$target->expects($this->any())
->method('is_setting_forced')
->willReturn($isforced);
$target->expects($this->any())
->method('get_forced_setting_value')
->with($this->equalTo($key))
->willReturn($forcedvalue);
$step->expects($this->any())
->method('get_targettype')
->willReturn('type');
$step->expects($this->any())
->method('get_target')
->willReturn($target);
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->getMock();
$tour->expects($this->any())
->method('get_config')
->willReturn($tourconfig);
$step->expects($this->any())
->method('get_tour')
->willReturn($tour);
$this->assertEquals($expected, $step->get_config($key, $default));
}
/**
* Data provider for set_config.
*/
public static function set_config_provider(): array {
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$randvalue = rand(1, 100);
$provider = [];
$newvalues = $allvalues;
$newvalues->some = 'unset';
$provider['Unset an existing value'] = [
$allvalues,
'some',
null,
$newvalues,
];
$newvalues = $allvalues;
$newvalues->some = $randvalue;
$provider['Set an existing value'] = [
$allvalues,
'some',
$randvalue,
$newvalues,
];
$provider['Set a new value'] = [
$allvalues,
'newkey',
$randvalue,
(object) array_merge((array) $allvalues, ['newkey' => $randvalue]),
];
return $provider;
}
/**
* Test that set_config works in the anticipated fashion.
*
* @dataProvider set_config_provider
* @param mixed $initialvalues The inital value to set
* @param string $key The key to test
* @param mixed $newvalue The new value to set
* @param mixed $expected The expected value
*/
public function test_set_config($initialvalues, $key, $newvalue, $expected): void {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$rcp->setValue($step, $initialvalues);
$target = $this->getMockBuilder(\tool_usertours\local\target\base::class)
->disableOriginalConstructor()
->getMock();
$target->expects($this->any())
->method('is_setting_forced')
->willReturn(false);
$step->set_config($key, $newvalue);
$this->assertEquals($expected, $rcp->getValue($step));
}
/**
* Ensure that non-dirty tours are not persisted.
*/
public function test_persist_non_dirty(): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'to_record',
'reload',
])
->getMock();
$step->expects($this->never())
->method('to_record');
$step->expects($this->never())
->method('reload');
$this->assertSame($step, $step->persist());
}
/**
* Ensure that new dirty steps are persisted.
*/
public function test_persist_dirty_new(): void {
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('insert_record')
->willReturn(42);
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock();
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
$step->expects($this->once())
->method('calculate_sortorder');
$step->expects($this->once())
->method('reload');
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('dirty');
$rcp->setValue($step, true);
$tour = $this->createMock(\tool_usertours\tour::class);
$rcp = $rc->getProperty('tour');
$rcp->setValue($step, $tour);
$this->assertSame($step, $step->persist());
}
/**
* Ensure that new non-dirty, forced steps are persisted.
*/
public function test_persist_force_new(): void {
global $DB;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('insert_record')
->willReturn(42);
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock();
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
$step->expects($this->once())
->method('calculate_sortorder');
$step->expects($this->once())
->method('reload');
$tour = $this->createMock(\tool_usertours\tour::class);
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('tour');
$rcp->setValue($step, $tour);
$this->assertSame($step, $step->persist(true));
}
/**
* Ensure that existing dirty steps are persisted.
*/
public function test_persist_dirty_existing(): void {
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('update_record');
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock();
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
$step->expects($this->never())
->method('calculate_sortorder');
$step->expects($this->once())
->method('reload');
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setValue($step, 42);
$rcp = $rc->getProperty('dirty');
$rcp->setValue($step, true);
$tour = $this->createMock(\tool_usertours\tour::class);
$rcp = $rc->getProperty('tour');
$rcp->setValue($step, $tour);
$this->assertSame($step, $step->persist());
}
/**
* Ensure that existing non-dirty, forced steps are persisted.
*/
public function test_persist_force_existing(): void {
global $DB;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('update_record');
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock();
$step->expects($this->once())
->method('to_record')
->willReturn((object) ['id' => 42]);
$step->expects($this->never())
->method('calculate_sortorder');
$step->expects($this->once())
->method('reload');
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setValue($step, 42);
$tour = $this->createMock(\tool_usertours\tour::class);
$rcp = $rc->getProperty('tour');
$rcp->setValue($step, $tour);
$this->assertSame($step, $step->persist(true));
}
/**
* Check that a tour which has never been persisted is removed correctly.
*/
public function test_remove_non_persisted(): void {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([])
->getMock();
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->never())
->method('delete_records');
$this->assertNull($step->remove());
}
/**
* Check that a tour which has been persisted is removed correctly.
*/
public function test_remove_persisted(): void {
$id = rand(1, 100);
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->onlyMethods([
'reset_step_sortorder',
])
->getMock();
$tour->expects($this->once())
->method('reset_step_sortorder');
$step = $this->getMockBuilder(\tool_usertours\step::class)
->onlyMethods([
'get_tour',
])
->getMock();
$step->expects($this->once())
->method('get_tour')
->willReturn($tour);
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('delete_records')
->with($this->equalTo('tool_usertours_steps'), $this->equalTo(['id' => $id]));
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setValue($step, $id);
$this->assertEquals($id, $step->get_id());
$this->assertNull($step->remove());
}
/**
* Data provider for the get_ tests.
*
* @return array
*/
public static function getter_provider(): array {
return [
'id' => [
'id',
rand(1, 100),
],
'tourid' => [
'tourid',
rand(1, 100),
],
'title' => [
'title',
'Lorem',
],
'content' => [
'content',
'Lorem',
],
'targettype' => [
'targettype',
'Lorem',
],
'targetvalue' => [
'targetvalue',
'Lorem',
],
'sortorder' => [
'sortorder',
rand(1, 100),
],
];
}
/**
* Test that getters return the configured value.
*
* @dataProvider getter_provider
* @param string $key The key to test
* @param mixed $value The expected value
*/
public function test_getters($key, $value): void {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty($key);
$rcp->setValue($step, $value);
$getter = 'get_' . $key;
$this->assertEquals($value, $step->$getter());
}
/**
* Ensure that the get_step_image_from_input function replace PIXICON placeholder with the correct images correctly.
*/
public function test_get_step_image_from_input(): void {
// Test step content with single image.
$stepcontent = '@@PIXICON::tour/tour_mycourses::tool_usertours@@<br>Test';
$stepcontent = \tool_usertours\step::get_step_image_from_input($stepcontent);
// If the format is correct, PIXICON placeholder will be replaced with the img tag.
$this->assertStringStartsWith('<img', $stepcontent);
$this->assertStringEndsWith('Test', $stepcontent);
$this->assertStringNotContainsString('PIXICON', $stepcontent);
// Test step content with multiple images.
$stepcontent =
'@@PIXICON::tour/tour_mycourses::tool_usertours@@<br>Test<br>@@PIXICON::tour/tour_myhomepage::tool_usertours@@';
$stepcontent = \tool_usertours\step::get_step_image_from_input($stepcontent);
// If the format is correct, PIXICON placeholder will be replaced with the img tag.
$this->assertStringStartsWith('<img', $stepcontent);
// We should have 2 img tags here.
$this->assertEquals(2, substr_count($stepcontent, '<img'));
$this->assertStringNotContainsString('PIXICON', $stepcontent);
// Test step content with incorrect format.
$stepcontent = '@@PIXICON::tour/tour_mycourses<br>Test';
$stepcontent = \tool_usertours\step::get_step_image_from_input($stepcontent);
// If the format is not correct, PIXICON placeholder will not be replaced with the img tag.
$this->assertStringStartsNotWith('<img', $stepcontent);
$this->assertStringStartsWith('@@PIXICON', $stepcontent);
$this->assertStringEndsWith('Test', $stepcontent);
$this->assertStringContainsString('PIXICON', $stepcontent);
}
}
@@ -0,0 +1,92 @@
<?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 tool_usertours;
/**
* Tests for theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \tool_usertours\local\filter\theme
*/
class theme_filter_test extends \advanced_testcase {
/**
* Data Provider for filter_matches function.
*
* @return array
*/
public static function filter_matches_provider(): array {
return [
'No config set; Matches' => [
null,
'boost',
true,
],
'Empty config set; Matches' => [
[],
'boost',
true,
],
'Single matching value set; Matches' => [
['boost'],
'boost',
true,
],
'Multiple values set including matching; Matches' => [
['boost', 'classic'],
'boost',
true,
],
'Single value set; No match' => [
['classic'],
'boost',
false,
],
'Multiple values set; No match' => [
['classic', 'artificial'],
'boost',
false,
],
];
}
/**
* Test the filter_matches function.
*
* @dataProvider filter_matches_provider
* @param array $filtervalues The filter values
* @param string $currenttheme The name of the current theme
* @param boolean $expected Whether the tour is expected to match
*/
public function test_filter_matches($filtervalues, $currenttheme, $expected): void {
global $PAGE;
$filtername = \tool_usertours\local\filter\theme::class;
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
if ($filtervalues !== null) {
$tour->set_filter_values('theme', $filtervalues);
}
$PAGE->theme->name = $currenttheme;
// Note: The theme filter does not use the context.
$this->assertEquals($expected, $filtername::filter_matches($tour, \context_system::instance()));
}
}
File diff suppressed because it is too large Load Diff