Compare commits

..

25 Commits

Author SHA1 Message Date
Jun Pataleta e591ddc4e8 Moodle release 4.1.8 2023-12-22 10:01:56 +08:00
Sara Arjona e1767fa80f weekly release 4.1.7+ 2023-12-21 13:34:33 +01:00
Huong Nguyen e25033e42d Merge branch 'MDL-80151-401' of https://github.com/lameze/moodle into MOODLE_401_STABLE 2023-12-20 15:40:30 +07:00
Huong Nguyen dd2c19eb09 MDL-80151 behat: Improve viewing site calendar step 2023-12-20 16:32:55 +08:00
Angelia Dela Cruz 3b8b8d7d84 MDL-80151 mod_chat: Behat for chat repeat times and past session view 2023-12-20 16:32:55 +08:00
Simey Lameze 5378285a9a MDL-80151 behat: add calendar step to navigate to specific view 2023-12-20 16:32:55 +08:00
Jun Pataleta 3a098250f1 Merge branch 'MDL-68124-401' of https://github.com/sarjona/moodle into MOODLE_401_STABLE 2023-12-20 15:01:28 +08:00
Jun Pataleta 148ddd26e7 Merge branch 'MDL-80393-401' of https://github.com/paulholden/moodle into MOODLE_401_STABLE 2023-12-19 17:24:56 +08:00
Paul Holden cdf432ee17 MDL-80393 lang: expose current page language to JS modules.
Since 4be39296 the root HTML element contains the iso6391 language
code of the current language pack. However various JS modules need
access to the original Moodle language pack code, so expose that
in the `M.cfg` structure.
2023-12-19 09:08:12 +00:00
Sara Arjona b1e918edd8 MDL-68124 lesson: Fix random behat error with Oracle
This random error was caused because when reviewing an attempt,
the first pageid to display is calculated based on the timeseen
field in the lesson_attempst table.
In Oracle, sometimes it returned the second pageid (because both
had the same timeseen), so which led to these random failures.

Apart from adding 1-second wait when the attempt is created (to
guarantee the first page is returned properly), a few more
improvements have been also made, like removing manual steps for
setting the lesson activity or adding some extra checks to
confirm the information displayed is expected.
2023-12-18 09:49:05 +01:00
Huong Nguyen f58df6fa88 weekly release 4.1.7+ 2023-12-15 08:44:56 +07:00
Huong Nguyen eae562817d Merge branch 'install_401_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_401_STABLE 2023-12-15 08:44:52 +07:00
Huong Nguyen 033a59f9c5 Merge branch 'MDL-79828-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-14 10:16:22 +07:00
Huong Nguyen a34a8636be Merge branch 'MDL-79050-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-14 09:57:28 +07:00
Huong Nguyen 08d2e7c136 Merge branch 'MDL-80394-401' of https://github.com/ilyatregubov/moodle into MOODLE_401_STABLE 2023-12-14 09:23:23 +07:00
Ilya Tregubov 886bbcbdb8 Merge branch 'MDL-79938-m401' of https://github.com/sammarshallou/moodle into MOODLE_401_STABLE 2023-12-14 08:51:51 +08:00
AMOS bot 0c3e759d98 Automatically generated installer lang files 2023-12-14 00:11:04 +00:00
sam marshall 607a452a74 MDL-79938 Behat: Add steps to support multiple tabs 2023-12-13 11:45:51 +00:00
Angelia Dela Cruz 3ef6e04f3c MDL-79050 Behat: Add behat coverage to retake lesson activity 2023-12-13 16:27:02 +08:00
Ilya Tregubov dfc7dcd062 MDL-80394 core_grades: Revert changes to graded_users_iterator
Third party plugins are using it and change was breaking it
2023-12-13 09:38:29 +08:00
Sara Arjona 80d773868c Merge branch 'MDL-80315-401' of https://github.com/andelacruz/moodle into MOODLE_401_STABLE 2023-12-12 14:20:03 +01:00
Sara Arjona eb127cc93b Merge branch 'MDL-79835_401' of https://github.com/timhunt/moodle into MOODLE_401_STABLE 2023-12-12 14:11:51 +01:00
Tim Hunt 67f869a081 MDL-79835 behat_core_generator: remove misleading error obfuscation 2023-12-06 18:14:09 +00:00
Angelia Dela Cruz a11a9ca5aa MDL-80315 block_comments: Removed duplicate Behat test
Deleted the duplicated course comments block behat test. The file
blocks/comments/tests/behat/add_comment.feature is deleted as a
result.
2023-12-06 18:26:23 +08:00
Angelia Dela Cruz 6aeecbdf46 MDL-79828 core_badges: Delete already awarded course badges 2023-11-21 17:45:47 +08:00
26 changed files with 409 additions and 91 deletions
+34
View File
@@ -0,0 +1,34 @@
@tool_behat
Feature: Confirm that we can open multiple browser tabs
In order to use multiple browser tabs
As a test writer
I need the relevant Behat steps to work
@javascript @_switch_window
Scenario: Open multiple browser tabs
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
And I am on the "C1" "Course" page logged in as "admin"
# Open a new tab on the same page.
When I open a tab named "CourseViewer1" on the current page
And I should see "Course 1" in the "h1" "css_element"
And I am on the "C2" "Course" page
# Open new tab for specified page with identifier.
And I open a tab named "CourseViewer2" on the "C3" "Course" page
# And for a specified page without identifier.
And I open a tab named "CourseViewer4" on the "My courses" page
# Switch between all the tabs and confirm their different contents.
Then I should see "No courses"
And I switch to "CourseViewer2" tab
And I should see "Course 3" in the "h1" "css_element"
And I switch to "CourseViewer1" tab
And I should see "Course 2" in the "h1" "css_element"
And I switch to the main tab
And I should see "Course 1" in the "h1" "css_element"
@@ -0,0 +1,52 @@
@core @core_badges
Feature: Delete course badge already awarded
As a teacher
I can delete awarded course badge
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario Outline: Delete course badge already awarded to student
Given the following "core_badges > Badges" exist:
| name | course | description | image | status | type |
| <badgename> | C1 | <badgename> description | badges/tests/behat/badge.png | active | 2 |
And the following "core_badges > Criterias" exist:
| badge | role |
| <badgename> | editingteacher |
And the following "core_badges > Issued badges" exist:
| badge | user |
| <badgename> | student1 |
When I am on the "Course 1" "enrolled users" page logged in as "teacher1"
And I click on "Student 1" "link"
Then I should see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Manage Badges page in order to delete the badge
And I navigate to "Badges > Manage badges" in current page administration
# Delete the badge
And I click on "Delete" "icon" in the "<badgename>" "table_row"
And I press "<deleteoption>"
And I am on the "Course 1" "enrolled users" page
And I click on "Student 1" "link"
# Confirm that Badge 1 is retained as awarded badge but Badge 2 is not
And I <visibility> see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Badges page to confirm that no badges exist, hence, Manage badges would not exist
And I navigate to "Badges" in current page administration
# Confirm that badges are sucessfully deleted
And I should see "There are currently no badges available for users to earn."
Examples:
| badgename | deleteoption | visibility |
| Badge 1 | Delete and keep existing issued badges | should |
| Badge 2 | Delete and remove existing issued badges | should not |
@@ -25,11 +25,16 @@ Feature: Add a comment to the comments block
Scenario: Add a comment with Javascript enabled
When I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I should see "I'm a comment from student1"
Scenario: Add a comment with Javascript disabled
When I follow "Show comments"
And I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I should see "I'm a comment from student1"
@javascript
Scenario: Test comment block pagination
@@ -1,27 +0,0 @@
@block @block_comments
Feature: Enable Block comments on a course page and view comments
In order to enable the comments block on a course page
As a teacher
I can add the comments block to the course page
Scenario: Add the comments block on the course page and add comments
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| comments | Course | C1 | course-view-* | side-pre |
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I add "I'm a comment from the teacher" comment to comments block
When I am on the "Course 1" course page logged in as student1
And I follow "Show comments"
Then I should see "I'm a comment from the teacher"
+17 -1
View File
@@ -176,7 +176,23 @@ class behat_calendar extends behat_base {
* @return void
*/
public function i_am_viewing_site_calendar() {
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
$this->i_am_viewing_calendar_in_view('month');
}
/**
* Navigate to a specific view in the calendar.
*
* @Given /^I am viewing calendar in "([^"]+)" view$/
* @param string $view The calendar view ('month', 'day' and 'upcoming') to navigate to.
* @return void
*/
public function i_am_viewing_calendar_in_view(string $view): void {
if (!in_array($view, ['month', 'day', 'upcoming'])) {
throw new Exception("Invalid calendar view. Allowed values are: 'month', 'day' and 'upcoming'");
}
$url = new moodle_url('/calendar/view.php', ['view' => $view]);
$this->execute('behat_general::i_visit', [$url]);
}
}
+3 -7
View File
@@ -183,12 +183,7 @@ class graded_users_iterator {
}
}
$userfieldsapi = \core_user\fields::for_identity($coursecontext, false)->with_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
// This need to be fixed - webservices in grade/report/user/classes/external/user.php don't check permission properly.
$userfields .= ', u.idnumber, u.institution, u.department';
$userfields = 'u.*';
$customfieldssql = '';
if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) {
$customfieldscount = 0;
@@ -222,7 +217,8 @@ class graded_users_iterator {
$this->users_rs = $DB->get_recordset_sql($users_sql, $params);
if (!$this->onlyactive) {
$this->suspendedusers = get_suspended_userids($coursecontext);
$context = context_course::instance($this->course->id);
$this->suspendedusers = get_suspended_userids($context);
} else {
$this->suspendedusers = array();
}
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'Lingua';
$string['moodlelogo'] = 'Mercu Moodle';
+2 -2
View File
@@ -1,4 +1,4 @@
define("core/str",["exports","jquery","core/ajax","core/localstorage"],(function(_exports,_jquery,_ajax,_localstorage){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
define("core/str",["exports","jquery","core/ajax","core/config","core/localstorage"],(function(_exports,_jquery,_ajax,_config,_localstorage){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Fetch and return language strings.
*
@@ -7,6 +7,6 @@ define("core/str",["exports","jquery","core/ajax","core/localstorage"],(function
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.get_strings=_exports.get_string=_exports.cache_strings=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),_localstorage=_interopRequireDefault(_localstorage);let promiseCache=[];_exports.get_string=(key,component,param,lang)=>get_strings([{key:key,component:component,param:param,lang:lang}]).then((results=>results[0]));const get_strings=requests=>{let requestData=[];const pageLang=(0,_jquery.default)("html").attr("lang").replace(/-/g,"_"),stringPromises=requests.map((request=>{let{component:component,key:key,param:param,lang:lang=pageLang}=request;component||(component="core");const cacheKey=(_ref=>{let{key:key,component:component,lang:lang=pageLang}=_ref;return"core_str/".concat(key,"/").concat(component,"/").concat(lang)})({key:key,component:component,lang:lang}),buildReturn=promise=>(promiseCache[cacheKey]=promise,promise);if(component in M.str&&key in M.str[component])return buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})));const cached=_localstorage.default.get(cacheKey);return cached?(M.str[component]={...M.str[component],[key]:cached},buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})))):cacheKey in promiseCache?buildReturn(promiseCache[cacheKey]).then((()=>M.util.get_string(key,component,param,lang))):buildReturn(new Promise(((resolve,reject)=>{requestData.push({methodname:"core_get_string",args:{stringid:key,stringparams:[],component:component,lang:lang},done:str=>{M.str[component]={...M.str[component],[key]:str},_localstorage.default.set(cacheKey,str),resolve(M.util.get_string(key,component,param,lang))},fail:reject})})))}));return requestData.length&&_ajax.default.call(requestData,!0,!1,!1,0,M.cfg.langrev),_jquery.default.when.apply(_jquery.default,stringPromises).then((function(){for(var _len=arguments.length,strings=new Array(_len),_key=0;_key<_len;_key++)strings[_key]=arguments[_key];return strings}))};_exports.get_strings=get_strings;_exports.cache_strings=strings=>{const defaultLang=(0,_jquery.default)("html").attr("lang").replace(/-/g,"_");strings.forEach((_ref2=>{let{key:key,component:component,value:value,lang:lang=defaultLang}=_ref2;const cacheKey=["core_str",key,component,lang].join("/");component in M.str&&key in M.str[component]||(component in M.str||(M.str[component]={}),M.str[component][key]=value),_localstorage.default.get(cacheKey)||_localstorage.default.set(cacheKey,value),cacheKey in promiseCache||(promiseCache[cacheKey]=_jquery.default.Deferred().resolve(value).promise())}))}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.get_strings=_exports.get_string=_exports.cache_strings=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),_config=_interopRequireDefault(_config),_localstorage=_interopRequireDefault(_localstorage);let promiseCache=[];_exports.get_string=(key,component,param,lang)=>get_strings([{key:key,component:component,param:param,lang:lang}]).then((results=>results[0]));const get_strings=requests=>{let requestData=[];const pageLang=_config.default.language,stringPromises=requests.map((request=>{let{component:component,key:key,param:param,lang:lang=pageLang}=request;component||(component="core");const cacheKey=(_ref=>{let{key:key,component:component,lang:lang=pageLang}=_ref;return"core_str/".concat(key,"/").concat(component,"/").concat(lang)})({key:key,component:component,lang:lang}),buildReturn=promise=>(promiseCache[cacheKey]=promise,promise);if(component in M.str&&key in M.str[component])return buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})));const cached=_localstorage.default.get(cacheKey);return cached?(M.str[component]={...M.str[component],[key]:cached},buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})))):cacheKey in promiseCache?buildReturn(promiseCache[cacheKey]).then((()=>M.util.get_string(key,component,param,lang))):buildReturn(new Promise(((resolve,reject)=>{requestData.push({methodname:"core_get_string",args:{stringid:key,stringparams:[],component:component,lang:lang},done:str=>{M.str[component]={...M.str[component],[key]:str},_localstorage.default.set(cacheKey,str),resolve(M.util.get_string(key,component,param,lang))},fail:reject})})))}));return requestData.length&&_ajax.default.call(requestData,!0,!1,!1,0,M.cfg.langrev),_jquery.default.when.apply(_jquery.default,stringPromises).then((function(){for(var _len=arguments.length,strings=new Array(_len),_key=0;_key<_len;_key++)strings[_key]=arguments[_key];return strings}))};_exports.get_strings=get_strings;_exports.cache_strings=strings=>{strings.forEach((_ref2=>{let{key:key,component:component,value:value,lang:lang=_config.default.language}=_ref2;const cacheKey=["core_str",key,component,lang].join("/");component in M.str&&key in M.str[component]||(component in M.str||(M.str[component]={}),M.str[component][key]=value),_localstorage.default.get(cacheKey)||_localstorage.default.set(cacheKey,value),cacheKey in promiseCache||(promiseCache[cacheKey]=_jquery.default.Deferred().resolve(value).promise())}))}}));
//# sourceMappingURL=str.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,6 +5,6 @@
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core/user_date",["jquery","core/ajax","core/sessionstorage","core/config"],(function($,Ajax,Storage,Config){var promisesCache={},getKey=function(request){return"core_user_date/"+$("html").attr("lang").replace(/-/g,"_")+"/"+Config.usertimezone+"/"+request.timestamp+"/"+request.format},loadDatesFromServer=function(dates){var args=dates.map((function(data){var fixDay=data.hasOwnProperty("fixday")?data.fixday:1,fixHour=data.hasOwnProperty("fixhour")?data.fixhour:1;return{timestamp:data.timestamp,format:data.format,type:data.type||null,fixday:fixDay,fixhour:fixHour}})),request={methodname:"core_get_user_dates",args:{contextid:Config.contextid,timestamps:args}};return Ajax.call([request],!0,!0)[0].then((function(results){results.dates.forEach((function(value,index){var date=dates[index];!function(key,value){Storage.set(key,value)}(getKey(date),value),date.deferred.resolve(value)}))})).catch((function(ex){dates.forEach((function(date){date.deferred.reject(ex)}))}))};return{get:function(requests){var ajaxRequests=[],promises=[];return requests.forEach((function(request){var key=getKey(request);if(function(key){return void 0!==promisesCache[key]}(key))promises.push(function(key){return promisesCache[key]}(key));else{var deferred=$.Deferred(),cached=function(key){return Storage.get(key)}(key);cached?deferred.resolve(cached):(request.deferred=deferred,ajaxRequests.push(request)),function(key,promise){promisesCache[key]=promise}(key,deferred.promise()),promises.push(deferred.promise())}})),ajaxRequests.length&&loadDatesFromServer(ajaxRequests),$.when.apply($,promises).then((function(){return 1===arguments.length?[arguments[0]]:Array.apply(null,arguments)}))},getUserMidnightForTimestamp:function(timestamp,todayMidnight){var future=timestamp>todayMidnight,diffSeconds=Math.abs(timestamp-todayMidnight),diffDaysInSeconds=86400*(future?Math.floor(diffSeconds/86400):Math.ceil(diffSeconds/86400));return future?todayMidnight+diffDaysInSeconds:todayMidnight-diffDaysInSeconds}}}));
define("core/user_date",["jquery","core/ajax","core/sessionstorage","core/config"],(function($,Ajax,Storage,Config){var promisesCache={},getKey=function(request){return"core_user_date/"+Config.language+"/"+Config.usertimezone+"/"+request.timestamp+"/"+request.format},loadDatesFromServer=function(dates){var args=dates.map((function(data){var fixDay=data.hasOwnProperty("fixday")?data.fixday:1,fixHour=data.hasOwnProperty("fixhour")?data.fixhour:1;return{timestamp:data.timestamp,format:data.format,type:data.type||null,fixday:fixDay,fixhour:fixHour}})),request={methodname:"core_get_user_dates",args:{contextid:Config.contextid,timestamps:args}};return Ajax.call([request],!0,!0)[0].then((function(results){results.dates.forEach((function(value,index){var date=dates[index];!function(key,value){Storage.set(key,value)}(getKey(date),value),date.deferred.resolve(value)}))})).catch((function(ex){dates.forEach((function(date){date.deferred.reject(ex)}))}))};return{get:function(requests){var ajaxRequests=[],promises=[];return requests.forEach((function(request){var key=getKey(request);if(function(key){return void 0!==promisesCache[key]}(key))promises.push(function(key){return promisesCache[key]}(key));else{var deferred=$.Deferred(),cached=function(key){return Storage.get(key)}(key);cached?deferred.resolve(cached):(request.deferred=deferred,ajaxRequests.push(request)),function(key,promise){promisesCache[key]=promise}(key,deferred.promise()),promises.push(deferred.promise())}})),ajaxRequests.length&&loadDatesFromServer(ajaxRequests),$.when.apply($,promises).then((function(){return 1===arguments.length?[arguments[0]]:Array.apply(null,arguments)}))},getUserMidnightForTimestamp:function(timestamp,todayMidnight){var future=timestamp>todayMidnight,diffSeconds=Math.abs(timestamp-todayMidnight),diffDaysInSeconds=86400*(future?Math.floor(diffSeconds/86400):Math.ceil(diffSeconds/86400));return future?todayMidnight+diffDaysInSeconds:todayMidnight-diffDaysInSeconds}}}));
//# sourceMappingURL=user_date.min.js.map
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -24,6 +24,7 @@
*/
import $ from 'jquery';
import Ajax from 'core/ajax';
import Config from 'core/config';
import LocalStorage from 'core/localstorage';
// Module cache for the promises so that we don't make multiple
@@ -101,7 +102,7 @@ export const get_string = (key, component, param, lang) => {
// eslint-disable-next-line camelcase
export const get_strings = (requests) => {
let requestData = [];
const pageLang = $('html').attr('lang').replace(/-/g, '_');
const pageLang = Config.language;
// Helper function to construct the cache key.
const getCacheKey = ({key, component, lang = pageLang}) => `core_str/${key}/${component}/${lang}`;
@@ -191,13 +192,11 @@ export const get_strings = (requests) => {
* @param {string} strings.key The string identifer to fetch
* @param {string} strings.value The string value
* @param {string} [strings.component='core'] The componet to fetch from
* @param {string} [strings.lang] The language to fetch a string for. Defaults to current page language.
* @param {string} [strings.lang=Config.language] The language to fetch a string for. Defaults to current page language.
*/
// eslint-disable-next-line camelcase
export const cache_strings = (strings) => {
const defaultLang = $('html').attr('lang').replace(/-/g, '_');
strings.forEach(({key, component, value, lang = defaultLang}) => {
strings.forEach(({key, component, value, lang = Config.language}) => {
const cacheKey = ['core_str', key, component, lang].join('/');
// Check M.str caching.
+1 -1
View File
@@ -184,7 +184,7 @@ function(
component: component,
template: name,
themename: theme,
lang: $('html').attr('lang').replace(/-/g, '_')
lang: config.language,
}
});
// Remember the index in the requests list for this template so that
+1 -2
View File
@@ -36,9 +36,8 @@ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
* @return {string}
*/
var getKey = function(request) {
var language = $('html').attr('lang').replace(/-/g, '_');
return 'core_user_date/' +
language + '/' +
Config.language + '/' +
Config.usertimezone + '/' +
request.timestamp + '/' +
request.format;
+1 -7
View File
@@ -470,13 +470,7 @@ class behat_core_generator extends behat_generator_base {
}
}
// Custom exception.
try {
$this->datagenerator->create_module($activityname, $data, $cmoptions);
} catch (coding_exception $e) {
throw new Exception('\'' . $activityname . '\' activity can not be added using this step,' .
' use the step \'I add a "ACTIVITY_OR_RESOURCE_NAME_STRING" to section "SECTION_NUMBER"\' instead');
}
$this->datagenerator->create_module($activityname, $data, $cmoptions);
}
/**
+1
View File
@@ -335,6 +335,7 @@ class page_requirements_manager {
'admin' => $CFG->admin,
'svgicons' => $page->theme->use_svg_icons(),
'usertimezone' => usertimezone(),
'language' => current_language(),
'courseId' => isset($courseid) ? (int) $courseid : 0,
'courseContextId' => isset($coursecontext) ? $coursecontext->id : 0,
'contextid' => $contextid,
+2 -2
View File
@@ -200,7 +200,7 @@ class behat_general extends behat_base {
/**
* Switches to the specified window. Useful when interacting with popup windows.
*
* @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/
* @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" (window|tab)$/
* @param string $windowname
*/
public function switch_to_window($windowname) {
@@ -232,7 +232,7 @@ class behat_general extends behat_base {
/**
* Switches to the main Moodle window. Useful when you finish interacting with popup windows.
*
* @Given /^I switch to the main window$/
* @Given /^I switch to the main (window|tab)$/
*/
public function switch_to_the_main_window() {
$this->switch_to_window(self::MAIN_WINDOW_NAME);
+57
View File
@@ -909,6 +909,63 @@ class behat_navigation extends behat_base {
throw new Exception('Unrecognised core page type "' . $type . '."');
}
/**
* Opens a new tab with given name on the same URL as current page and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @When /^I open a tab named "(?<name>[^"]*)" on the current page$/
*/
public function i_open_a_tab_on_the_current_page(string $name): void {
$this->open_tab($name, 'location.href');
}
/**
* Opens a new tab with given name on specified page, and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @param string $page Page name
* @When /^I open a tab named "(?<name>[^"]*)" on the "(?<page>[^"]*)" page$/
*/
public function i_open_a_tab_on_the_page(string $name, string $page): void {
if ($page === 'current') {
$jstarget = 'location.href';
} else {
$jstarget = '"' . addslashes_js($this->resolve_page_helper($page)->out(false)) . '"';
}
$this->open_tab($name, $jstarget);
}
/**
* Opens a new tab with given name (on specified page), and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @param string $identifier Page identifier
* @param string $page Page type
* @When /^I open a tab named "(?<name>[^"]*)" on the "(?<identifier>[^"]*)" "(?<page>[^"]*)" page$/
*/
public function i_open_a_tab_on_the_page_instance(string $name, string $identifier, string $page): void {
$this->open_tab($name, '"' . addslashes_js(
$this->resolve_page_instance_helper($identifier, $page)->out(false)) . '"');
}
/**
* Opens a new tab at the given target URL.
*
* @param string $name Name for tab
* @param string $jstarget Target in JavaScript syntax, i.e. if a string, must be quoted
*/
protected function open_tab(string $name, string $jstarget): void {
// Tab names aren't allowed spaces, and our JavaScript below doesn't do any escaping.
if (clean_param($name, PARAM_ALPHANUMEXT) !== $name) {
throw new Exception('Tab name may not contain whitespace or special characters: "' . $name . '"');
}
// Normally you can't open a tab unless in response to a user action, but presumably Behat
// is exempt from this restriction, because it works to just open it directly.
$this->execute_script('window.open(' . $jstarget . ', "' . $name . '");');
$this->execute('behat_general::switch_to_window', [$name]);
}
/**
* Opens the course homepage. (Consider using 'I am on the "shortname" "Course" page' step instead.)
*
+4
View File
@@ -1,6 +1,10 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.1.8 ===
* The current page language is available in new `core/config` language property for Javascript modules
=== 4.1.7 ===
* Add a new parameter to the debounce (core/utils) function allow it to create its own own Pending promise.
(via options.pending). This is a backport of patch MDL-78779.
@@ -17,18 +17,19 @@ Feature: Chat calendar entries
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Scenario: Create a chat activity and do not publish the start date to the calendar
Scenario Outline: Create a chat activity with repeated chat times set
# Create an activity with repeated chat times
Given the following "activities" exist:
| activity | name | intro | course | idnumber | schedule |
| chat | Test chat name | Test chat description | C1 | chat1 | 0 |
| activity | course | name | schedule |
| chat | C1 | Chat 1 | <scheduleset> |
And I log in as "teacher1"
When I follow "Calendar" in the user menu
Then I should not see "Test chat name"
# Confirm Chat activity visibility based on schedule
When I am viewing calendar in "upcoming" view
Then I <chatvisibility> see "Chat 1"
Scenario: Create a chat activity and publish the start date to the calendar
Given the following "activities" exist:
| activity | name | intro | course | idnumber | schedule |
| chat | Test chat name | Test chat description | C1 | chat1 | 1 |
And I log in as "teacher1"
When I follow "Calendar" in the user menu
Then I should see "Test chat name"
Examples:
| scheduleset | chatvisibility |
| 0 | should not |
| 1 | should |
| 2 | should |
| 3 | should |
@@ -0,0 +1,47 @@
@mod @mod_chat @javascript
Feature: View past chat sessions
In order for students to view past chat sessions
As a teacher
I need to be able to change the mod/chat:readlog permission for students
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
And the following "course" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Scenario Outline: View past chat sessions
# Display or hide past chat session based on example data
Given the following "activities" exist:
| activity | course | name | studentlogs |
| chat | C1 | Chat 1 | <studentlogvalue> |
And I am on the "Chat 1" "chat activity" page logged in as teacher1
# Display or hide past chat session by default based on mod/chat:readlog setting
And the following "role capability" exists:
| role | student |
| mod/chat:readlog | <readpermission> |
# Enter chat activity to create a session
And I click on "Enter the chat" "link"
# Close chat window
When I close all opened windows
And I reload the page
# Confirm that past chat sessions is always visible for teacher
Then I should see "Past sessions"
# Confirm past chat sessions visibility for student based on settings
And I am on the "Chat 1" "chat activity" page logged in as student1
And I <sessionvisibility> see "Past sessions"
# Regardless of studentlogvalue value if readpermission is allowed, then Past sessions will be visible for students
Examples:
| studentlogvalue | readpermission | sessionvisibility |
| 0 | allow | should |
| 1 | allow | should |
| 0 | prohibit | should not |
| 1 | prohibit | should |
@@ -0,0 +1,82 @@
@mod @mod_lesson
Feature: Retake lesson activity
In order for student to retake a lesson activity
As a teacher
I should be able to allow retakes
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
# Generate lesson with retakes enabled, maxgrade = 100 and handling of re-takes = use mean
And the following "activities" exist:
| activity | name | course | retake | grade[modgrade_point] | usemaxgrade |
| lesson | Test lesson name | C1 | 1 | 100 | 0 |
# Generate question pages
And the following "mod_lesson > page" exist:
| lesson | qtype | title | content |
| Test lesson name | multichoice | Question 1 | Which is not a plant? |
| Test lesson name | multichoice | Question 2 | Which is a plant? |
| Test lesson name | multichoice | Question 3 | Which is a plant and a colour? |
# Generate question answers
And the following "mod_lesson > answers" exist:
| page | answer | jumpto | score |
| Question 1 | Brown | Next page | 1 |
| Question 1 | Lavender | Next page | 0 |
| Question 2 | Brown | Next page | 0 |
| Question 2 | Lavender | Next page | 1 |
| Question 3 | Brown | Next page | 0 |
| Question 3 | Lavender | Next page | 1 |
Scenario: A student can retake a lesson
# First attempt - all correct
Given I am on the "Test lesson name" "lesson activity" page logged in as student1
And I set the following fields to these values:
| Brown | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
# Confirm that lesson can be retaken
When I am on the "Test lesson name" "lesson activity" page
Then I should see "Which is not a plant?"
# Second attempt - only 1 correct
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
And I set the following fields to these values:
| Brown | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
# Check that grade is 66.67 (mean of the 2 attempts)
And I click on "View grades" "link"
And "Test lesson name" row "Grade" column of "generaltable" table should contain "66.67"
# Change handling of re-takes = use maximum
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Handling of re-takes | Use maximum |
And I press "Save and display"
# Confirm that lesson grade is the maximum of the 2 attempts (100)
And I am on the "Course 1" "grades > user > View" page logged in as student1
And "Test lesson name" row "Grade" column of "generaltable" table should contain "100.00"
# Disable lesson retake
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Re-takes allowed | No |
And I press "Save and display"
# Confirm lesson cannot be retaken
And I am on the "Test lesson name" "lesson activity" page logged in as student1
And I should see "You are not allowed to retake this lesson."
+43 -18
View File
@@ -16,51 +16,76 @@ Feature: In a lesson activity, students can review the answers they gave to ques
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exist:
| activity | name | course | idnumber |
| lesson | Test lesson name | C1 | lesson1 |
And the following "activity" exists:
| activity | lesson |
| name | Test lesson name |
| course | C1 |
| idnumber | lesson1 |
# Display ongoing score = Yes
| ongoing | 1 |
# Slideshow = Yes
| slideshow | 1 |
# Maximum number of answers = 10
| maxanswers | 10 |
# Allow student review = Yes
| modattempts | 1 |
# Maximum number of attempts per question
| maxattempts | 3 |
# Custom scoring = No
| custom | 0 |
# Re-takes allowed = Yes
| retake | 1 |
And the following "mod_lesson > pages" exist:
| lesson | qtype | title | content |
| Test lesson name | numeric | Hardest question ever | 1 + 1? |
| Test lesson name | truefalse | Next question | Paper is made from trees. |
And the following "mod_lesson > answers" exist:
| page | answer | response | jumpto | score |
| Hardest question ever | 2 | Correct answer | Next page | 1 |
| Hardest question ever | 1 | Incorrect answer | This page | 0 |
| Next question | True | Correct | Next page | 1 |
| Next question | False | Wrong | This page | 0 |
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Display ongoing score | Yes |
| Slideshow | Yes |
| Maximum number of answers | 10 |
| Allow student review | Yes |
| Maximum number of attempts per question | 3 |
| Custom scoring | No |
| Re-takes allowed | Yes |
And I press "Save and display"
| page | answer | response | jumpto | score |
| Hardest question ever | 2 | Correct answer 1 | Next page | 1 |
| Hardest question ever | 1 | Incorrect answer 1 | This page | 0 |
| Next question | True | Correct answer 2 | Next page | 1 |
| Next question | False | Incorrect answer 2 | This page | 0 |
And I am on the "Test lesson name" "lesson activity" page logged in as student1
And I should see "You have answered 0 correctly out of 0 attempts."
And I should see "1 + 1?"
And I wait "1" seconds
And I set the following fields to these values:
| Your answer | 1 |
And I press "Submit"
And I should see "You have answered 0 correctly out of 1 attempts."
And I press "Continue"
And I should see "1 + 1?"
And I wait "1" seconds
And I set the following fields to these values:
| Your answer | 2 |
And I press "Submit"
And I should see "You have answered 1 correctly out of 2 attempts."
And I press "Continue"
And I should see "Paper is made from trees."
And I wait "1" seconds
And I set the following fields to these values:
| True | 1 |
And I press "Submit"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I press "Continue"
And I should see "Congratulations - end of lesson reached"
And I should see "Number of questions answered: 2"
And I should see "Number of correct answers: 2"
And I should see "Your score is 2 (out of 3)."
When I follow "Review lesson"
Then I should see "You have answered 2 correctly out of 3 attempts."
And I should see "1 + 1?"
And I press "Next page"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "1 + 1?"
And I should see "Correct answer 1"
And I press "Continue"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I press "Next page"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I should see "Correct answer 2"
And I press "Continue"
And I should see "Congratulations - end of lesson reached"
+2 -2
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2022112807.00; // 20221128 = branching date YYYYMMDD - do not modify!
$version = 2022112808.00; // 20221128 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.1.7 (Build: 20231211)'; // Human-friendly version name
$release = '4.1.8 (Build: 20231222)'; // Human-friendly version name
$branch = '401'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.