Merge branch 'MDL-82298-404' of https://github.com/andrewnicols/moodle into MOODLE_404_STABLE

This commit is contained in:
Andrew Nicols 2024-09-25 22:55:25 +08:00
commit 24dadc7a7f
No known key found for this signature in database
GPG Key ID: 6D1E3157C8CFBF14
7 changed files with 231 additions and 34 deletions

View File

@ -7,6 +7,6 @@ define("core/menu_navigation",["exports"],(function(_exports){Object.definePrope
* @author Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS_menuitem='[role="menuitem"]',SELECTORS_tab='[role="tab"]',SELECTORS_dropdowntoggle='[data-toggle="dropdown"]',SELECTORS_primarymenuitemactive='.primary-navigation .dropdown-item[aria-current="true"]';let openDropdownNode=null;const clickErrorHandler=(item,fallback)=>null!==item?item:fallback,menuItemHelper=src=>{let parent;if(!src.dataset.disableactive){if(src.classList.contains("dropdown-item")){parent=src.closest(".dropdown-menu");const dropDownToggle=document.getElementById(parent.getAttribute("aria-labelledby"));dropDownToggle.classList.add("active"),dropDownToggle.setAttribute("tabindex",0)}else{if(!src.matches("".concat(SELECTORS_tab,",").concat(SELECTORS_menuitem))||src.matches(SELECTORS_dropdowntoggle))return;parent=src.parentElement.parentElement.querySelector(".dropdown-menu")}Array.prototype.forEach.call(parent.children,(node=>{const menuItem=node.querySelector(SELECTORS_menuitem);null!==menuItem&&(menuItem.classList.remove("active"),menuItem.removeAttribute("aria-current"))})),"menuitem"===src.getAttribute("role")&&src.setAttribute("aria-current","true")}},keyboardListenerEvents=e=>{const src=e.srcElement,firstNode=e.currentTarget.firstElementChild,lastNode=findUsableLastNode(e.currentTarget);if(src.classList.contains("dropdown-item"))"ArrowRight"!=e.key&&"ArrowLeft"!=e.key||(e.preventDefault(),null!==openDropdownNode&&openDropdownNode.parentElement.click())," "!=e.key&&"Enter"!=e.key||(e.preventDefault(),menuItemHelper(src),src.parentElement.classList.contains("dropdown")||src.click());else{const rtl=window.right_to_left(),arrowNext=rtl?"ArrowLeft":"ArrowRight",arrowPrevious=rtl?"ArrowRight":"ArrowLeft";"menuitem"===src.getAttribute("role")&&(e.key==arrowNext&&(e.preventDefault(),setFocusNext(src,firstNode)),e.key==arrowPrevious&&(e.preventDefault(),setFocusPrev(src,lastNode)),"ArrowUp"!=e.key&&"ArrowDown"!=e.key||(openDropdownNode=src,e.preventDefault()),"Home"==e.key&&(e.preventDefault(),setFocusHomeEnd(firstNode)),"End"==e.key&&(e.preventDefault(),setFocusHomeEnd(lastNode)))," "!=e.key&&"Enter"!=e.key||(e.preventDefault(),src.parentElement.classList.contains("dropdown")||src.click())}},clickListenerEvents=e=>{const src=e.srcElement;menuItemHelper(src)};_exports.default=elementRoot=>{elementRoot.removeEventListener("keydown",keyboardListenerEvents),elementRoot.removeEventListener("click",clickListenerEvents),elementRoot.addEventListener("keydown",keyboardListenerEvents),elementRoot.addEventListener("click",clickListenerEvents)},window.addEventListener("pageshow",(function(){const items=document.querySelectorAll(SELECTORS_primarymenuitemactive);null!==items&&items.length>1&&items.forEach((function(e){const href=e.getAttribute("href"),windowHref=window.location.href||"",windowPath=window.location.pathname||"";href!==windowHref&&href!==windowPath&&href!==windowHref+"/index.php"&&href!==windowPath+"index.php"&&(e.classList.remove("active"),e.removeAttribute("aria-current"))}))}));const setFocusNext=(currentNode,firstNode)=>{const listElement=currentNode.parentElement,nextListItem=(el=>{do{el=el.nextElementSibling}while(el&&!el.offsetHeight);return el})(listElement),nodeToSelect=clickErrorHandler(nextListItem,firstNode),itemSelector="tablist"===listElement.parentElement.getAttribute("role")?SELECTORS_tab:SELECTORS_menuitem;nodeToSelect.querySelector(itemSelector).focus()},setFocusPrev=(currentNode,lastNode)=>{const listElement=currentNode.parentElement,nextListItem=(el=>{do{el=el.previousElementSibling}while(el&&!el.offsetHeight);return el})(listElement),nodeToSelect=clickErrorHandler(nextListItem,lastNode),itemSelector="tablist"===listElement.parentElement.getAttribute("role")?SELECTORS_tab:SELECTORS_menuitem;nodeToSelect.querySelector(itemSelector).focus()},setFocusHomeEnd=node=>{node.querySelector(SELECTORS_menuitem).focus()},findUsableLastNode=elementRoot=>{if(elementRoot.lastElementChild.classList.contains("d-none")){const nodesToUse=Array.prototype.map.call(elementRoot.children,(node=>node)).reverse().filter((node=>{if(!node.classList.contains("d-none"))return node}));return 0!==nodesToUse.length?nodesToUse[0]:elementRoot.firstElementChild}return elementRoot.lastElementChild};return _exports.default}));
const SELECTORS_menuitem='[role="menuitem"]',SELECTORS_tab='[role="tab"]',SELECTORS_dropdowntoggle='[data-toggle="dropdown"]';let openDropdownNode=null;const clickErrorHandler=(item,fallback)=>null!==item?item:fallback,menuItemHelper=src=>{let parent;if(!src.dataset.disableactive){if(src.classList.contains("dropdown-item")){parent=src.closest(".dropdown-menu");const dropDownToggle=document.getElementById(parent.getAttribute("aria-labelledby"));dropDownToggle.classList.add("active"),dropDownToggle.setAttribute("tabindex",0)}else{if(!src.matches("".concat(SELECTORS_tab,",").concat(SELECTORS_menuitem))||src.matches(SELECTORS_dropdowntoggle))return;parent=src.parentElement.parentElement.querySelector(".dropdown-menu")}Array.prototype.forEach.call(parent.children,(node=>{const menuItem=node.querySelector(SELECTORS_menuitem);null!==menuItem&&(menuItem.classList.remove("active"),menuItem.removeAttribute("aria-current"))})),"menuitem"===src.getAttribute("role")&&src.setAttribute("aria-current","true")}},keyboardListenerEvents=e=>{const src=e.srcElement,firstNode=e.currentTarget.firstElementChild,lastNode=findUsableLastNode(e.currentTarget);if(src.classList.contains("dropdown-item"))"ArrowRight"!=e.key&&"ArrowLeft"!=e.key||(e.preventDefault(),null!==openDropdownNode&&openDropdownNode.parentElement.click())," "!=e.key&&"Enter"!=e.key||(e.preventDefault(),menuItemHelper(src),src.parentElement.classList.contains("dropdown")||src.click());else{const rtl=window.right_to_left(),arrowNext=rtl?"ArrowLeft":"ArrowRight",arrowPrevious=rtl?"ArrowRight":"ArrowLeft";"menuitem"===src.getAttribute("role")&&(e.key==arrowNext&&(e.preventDefault(),setFocusNext(src,firstNode)),e.key==arrowPrevious&&(e.preventDefault(),setFocusPrev(src,lastNode)),"ArrowUp"!=e.key&&"ArrowDown"!=e.key||(openDropdownNode=src,e.preventDefault()),"Home"==e.key&&(e.preventDefault(),setFocusHomeEnd(firstNode)),"End"==e.key&&(e.preventDefault(),setFocusHomeEnd(lastNode)))," "!=e.key&&"Enter"!=e.key||(e.preventDefault(),src.parentElement.classList.contains("dropdown")||src.click())}},clickListenerEvents=e=>{const src=e.srcElement;menuItemHelper(src)};_exports.default=elementRoot=>{elementRoot.removeEventListener("keydown",keyboardListenerEvents),elementRoot.removeEventListener("click",clickListenerEvents),elementRoot.addEventListener("keydown",keyboardListenerEvents),elementRoot.addEventListener("click",clickListenerEvents)};const setFocusNext=(currentNode,firstNode)=>{const listElement=currentNode.parentElement,nextListItem=(el=>{do{el=el.nextElementSibling}while(el&&!el.offsetHeight);return el})(listElement),nodeToSelect=clickErrorHandler(nextListItem,firstNode),itemSelector="tablist"===listElement.parentElement.getAttribute("role")?SELECTORS_tab:SELECTORS_menuitem;nodeToSelect.querySelector(itemSelector).focus()},setFocusPrev=(currentNode,lastNode)=>{const listElement=currentNode.parentElement,nextListItem=(el=>{do{el=el.previousElementSibling}while(el&&!el.offsetHeight);return el})(listElement),nodeToSelect=clickErrorHandler(nextListItem,lastNode),itemSelector="tablist"===listElement.parentElement.getAttribute("role")?SELECTORS_tab:SELECTORS_menuitem;nodeToSelect.querySelector(itemSelector).focus()},setFocusHomeEnd=node=>{node.querySelector(SELECTORS_menuitem).focus()},findUsableLastNode=elementRoot=>{if(elementRoot.lastElementChild.classList.contains("d-none")){const nodesToUse=Array.prototype.map.call(elementRoot.children,(node=>node)).reverse().filter((node=>{if(!node.classList.contains("d-none"))return node}));return 0!==nodesToUse.length?nodesToUse[0]:elementRoot.firstElementChild}return elementRoot.lastElementChild};return _exports.default}));
//# sourceMappingURL=menu_navigation.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,6 @@ const SELECTORS = {
'menuitem': '[role="menuitem"]',
'tab': '[role="tab"]',
'dropdowntoggle': '[data-toggle="dropdown"]',
'primarymenuitemactive': '.primary-navigation .dropdown-item[aria-current="true"]',
};
let openDropdownNode = null;
@ -85,33 +84,6 @@ const menuItemHelper = src => {
}
};
/**
* Check if there are sub items in a dropdown menu. There can be one element active only. That is usually controlled
* by the server. However, when you click, the newly clicked item gets the active state as well. This is no problem
* because the user leaves the page and a new page load happens. When the user hits the back button, the old page dom
* is restored from the cache, with both menu items active. If there is such a case, we need to uncheck the item that
* was clicked when leaving this page.
* Make sure that this function is applied in the main menu only. The gradebook may contain drop down menus as well
* were more than one item can be flagged as active.
*/
const dropDownMenuActiveCheck = function() {
const items = document.querySelectorAll(SELECTORS.primarymenuitemactive);
// Do the check only, if there is more than one subitem active.
if (items !== null && items.length > 1) {
items.forEach(function(e) {
// Get the link target from the href attribute and compare it with the current url in the browser.
const href = e.getAttribute('href');
const windowHref = window.location.href || '';
const windowPath = window.location.pathname || '';
if (href !== windowHref && href !== windowPath
&& href !== windowHref + '/index.php' && href !== windowPath + 'index.php') {
e.classList.remove('active');
e.removeAttribute('aria-current');
}
});
}
};
/**
* Defined keyboard event handling so we can remove listeners on nodes on resize etc.
*
@ -208,9 +180,6 @@ export default elementRoot => {
elementRoot.addEventListener('click', clickListenerEvents);
};
// We need this triggered only when the user hits the back button.
window.addEventListener('pageshow', dropDownMenuActiveCheck);
/**
* Handle the focusing to the next element in the dropdown.
*

View File

@ -55,7 +55,7 @@
{{/action_link_actions}}
{{/is_action_link}}
{{^is_action_link}}
<a class="dropdown-item" role="menuitem" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1"
<a class="dropdown-item" role="menuitem" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} data-disableactive="true" tabindex="-1"
{{#title}}title="{{.}}"{{/title}}
>
{{{text}}}
@ -87,6 +87,7 @@
href="{{tab}}" data-toggle="tab" data-text="{{{text}}}"
{{#title}}title="{{.}}"{{/title}}
{{#isactive}}aria-selected="true"{{/isactive}}
data-disableactive="true"
{{^isactive}}tabindex="-1"{{/isactive}}
>
{{{text}}}
@ -110,6 +111,7 @@
href="{{{url}}}{{{action}}}"
{{#title}}title="{{.}}"{{/title}}
{{#isactive}}aria-current="true"{{/isactive}}
data-disableactive="true"
{{^isactive}}tabindex="-1"{{/isactive}}
>
{{{text}}}

View File

@ -1628,4 +1628,60 @@ class behat_navigation extends behat_base {
set_user_preference('behat_keep_drawer_closed', 1);
$this->i_close_block_drawer_if_open();
}
/**
* Checks if a navigation menu item is active.
*
* @Then menu item :navigationmenuitem should be active
* @param string $navigationmenuitem The navigation menu item name.
*/
public function menu_item_should_be_active(string $navigationmenuitem): void {
$elementselector = "//*//a/following-sibling::*//a[contains(text(), '$navigationmenuitem') and @aria-current='true']";
$params = [$elementselector, "xpath_element"];
$this->execute("behat_general::should_exist", $params);
}
/**
* Checks if a navigation menu item is not active
*
* @Then menu item :navigationmenuitem should not be active
* @param string $navigationmenuitem The navigation menu item name.
*/
public function menu_item_should_not_be_active(string $navigationmenuitem): void {
$elementselector = "//*//a/following-sibling::*//a[contains(text(), '$navigationmenuitem') and @aria-current='true']";
$params = [$elementselector, "xpath_element"];
$this->execute("behat_general::should_not_exist", $params);
}
/**
* Sets a link to no longer navigate when selected.
*
* @When /^I update the href of the "(?P<locator_string>[^"]*)" "(?P<selector_string>[^"]*)" link to "(?P<href_string>[^"]*)"$/
* @param string $locator The locator to use
* @param string $selector selector type
* @param string $href The value
*/
public function i_update_the_link_to_go_nowhere(
string $locator,
string $selector,
string $href,
): void {
$this->require_javascript();
$xpath = $this->find(
selector: $selector,
locator: $locator,
)->getXpath();
$script = <<<JS
var result = document.evaluate("{$xpath}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var link = result.singleNodeValue;
if (link) {
link.setAttribute('href', '{$href}');
} else {
throw new Error('No element found with the XPath: ' + "$selector");
}
JS;
$this->getSession()->executeScript($script);
}
}

View File

@ -0,0 +1,78 @@
@core
Feature: Menu navigation has accurate checkmarks in single activity course format
In order to correctly navigate the menu items
As an admin
I need to see accurate checkmarks besides the menu items I am currently on while in a single activity format course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | T1 | Teacher1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format | activitytype |
| Course 1 | C1 | singleactivity | quiz |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| quiz | Quiz 1 | Quiz 1 for testing the Add menu | C1 | quiz1 |
Scenario: Admin can see checkmark beside menu item they are currently on in a single activity format course
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Backup" in current page administration
Then menu item "Backup" should be active
When I navigate to "Permissions" in current page administration
Then menu item "Permissions" should be active
And menu item "Backup" should not be active
When I navigate to "Participants" in current page administration
Then menu item "Participants" should be active
And menu item "Backup" should not be active
And menu item "Permissions" should not be active
When I navigate to "Grades" in current page administration
Then menu item "Grades" should be active
And menu item "Backup" should not be active
And menu item "Permissions" should not be active
And menu item "Participants" should not be active
Scenario: Admin can see checkmark beside menu item they are currently on after pressing browser back button in a single
activity format course
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Backup" in current page administration
Then menu item "Backup" should be active
When I navigate to "Permissions" in current page administration
Then menu item "Permissions" should be active
And menu item "Backup" should not be active
When I press the "back" button in the browser
Then menu item "Backup" should be active
And menu item "Permissions" should not be active
Scenario: Admin can see checkmark beside menu item they are currently on after pressing browser back button when
jumping between course and activity menu in a single activity format course
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Backup" in current page administration
Then menu item "Backup" should be active
When I navigate to "Participants" in current page administration
Then menu item "Participants" should be active
And menu item "Backup" should not be active
When I press the "back" button in the browser
Then menu item "Backup" should be active
And menu item "Participants" should not be active
@javascript
Scenario: Admin should not see checkmark if link is not navigated to in current browser for single activity format quiz
Given I log in as "admin"
And I am on "Course 1" course homepage
And I update the href of the "//*//a/following-sibling::*//a[contains(text(), 'Participants')]" "xpath" link to "#"
When I navigate to "Participants" in current page administration
Then menu item "Participants" should not be active
And I update the href of the "//*//a/following-sibling::*//a[contains(text(), 'Backup')]" "xpath" link to "#"
When I click on "//*//a[contains(text(),'Activity')]" "xpath"
And I click on "//*//a/following-sibling::*//a[contains(text(), 'Backup')]" "xpath"
Then menu item "Backup" should not be active

View File

@ -0,0 +1,92 @@
@core
Feature: Menu navigation has accurate checkmarks in topic course format
In order to correctly navigate the menu items
As an admin
I need to see accurate checkmarks besides the menu items I am currently on while in topics format
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | T1 | Teacher1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| quiz | Quiz 1 | Quiz 1 for testing the Add menu | C1 | quiz1 |
@javascript
Scenario: Admin can see checkmark beside menu item they are currently on in the quiz page of a topics format course
Given I log in as "admin"
And I am on "Course 1" course homepage
And I follow "Quiz 1"
When I navigate to "Filters" in current page administration
Then menu item "Filters" should be active
When I navigate to "Permissions" in current page administration
Then menu item "Permissions" should be active
And menu item "Filters" should not be active
When I navigate to "Backup" in current page administration
Then menu item "Backup" should be active
And menu item "Filters" should not be active
And menu item "Permissions" should not be active
@javascript
Scenario: Admin can see checkmark beside menu item they are currently on in the course page of a topics format course
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Filters" in current page administration
Then menu item "Filters" should be active
When I navigate to "Course reuse" in current page administration
Then menu item "Course reuse" should be active
And menu item "Filters" should not be active
@javascript
Scenario: Admin can see checkmark beside menu item they are currently on after pressing browser back button in the
quiz page of a topics format course
Given I log in as "admin"
And I am on "Course 1" course homepage
And I follow "Quiz 1"
When I navigate to "Filters" in current page administration
Then menu item "Filters" should be active
When I navigate to "Permissions" in current page administration
Then menu item "Permissions" should be active
And menu item "Filters" should not be active
When I press the "back" button in the browser
Then menu item "Filters" should be active
And menu item "Permissions" should not be active
@javascript
Scenario: Admin can see checkmark beside menu item they are currently on after pressing browser back button in the
course page of a topics format course
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Filters" in current page administration
Then menu item "Filters" should be active
When I navigate to "Course reuse" in current page administration
Then menu item "Course reuse" should be active
And menu item "Filters" should not be active
When I press the "back" button in the browser
Then menu item "Filters" should be active
And menu item "Course reuse" should not be active
@javascript
Scenario: Admin should not see checkmark if link is not navigated to in current browser in course view for topics format
Given I log in as "admin"
And I am on "Course 1" course homepage
And I update the href of the "//*//a/following-sibling::*//a[contains(text(), 'Filters')]" "xpath" link to "#"
And I navigate to "Question bank" in current page administration
Then menu item "Filters" should not be active
@javascript
Scenario: Admin should not see checkmark if link is not navigated to in current browser in quiz view for topics format
Given I log in as "admin"
And I am on "Course 1" course homepage
And I follow "Quiz 1"
And I update the href of the "//*//a/following-sibling::*//a[contains(text(), 'Backup')]" "xpath" link to "#"
And I navigate to "Backup" in current page administration
Then menu item "Backup" should not be active