This commit is contained in:
Ilya Tregubov 2021-11-29 10:37:08 +02:00
commit d29c09344e
150 changed files with 944 additions and 445 deletions

View file

@ -1,2 +1,2 @@
define ("core/addblockmodal",["exports","core/modal_factory","core/templates","core/str","core/ajax"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=f(b);c=f(c);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function h(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){g(i,d,e,f,h,"next",a)}function h(a){g(i,d,e,f,h,"throw",a)}f(void 0)})}}var i={ADD_BLOCK:"[data-key=\"addblock\"]"},j=null,k=function(a,b,c){document.addEventListener("click",function(d){if(d.target.closest(i.ADD_BLOCK)){d.preventDefault();if(j){j.show()}else{l().then(function(d){j=d;var e=m(c,a,b);d.setBody(e);d.show();return e}).catch(function(){j.destroy();j=null})}}})},l=function(){return b.default.create({type:b.default.types.CANCEL,title:(0,d.get_string)("addblock")})},m=function(){var a=h(regeneratorRuntime.mark(function a(b,d,e){var f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return n(d,e);case 2:f=a.sent;return a.abrupt("return",c.default.render("core/add_block_body",{blocks:f,url:b}));case 4:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),n=function(){var a=h(regeneratorRuntime.mark(function a(b,c){var d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d={methodname:"core_block_fetch_addable_blocks",args:{pagecontextid:M.cfg.contextid,pagetype:b,pagelayout:c}};return a.abrupt("return",e.default.call([d])[0]);case 2:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.init=function init(a,b,c){k(a,b,c)}});
define ("core/addblockmodal",["exports","core/modal_factory","core/templates","core/str","core/ajax"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=f(b);c=f(c);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function h(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){g(i,d,e,f,h,"next",a)}function h(a){g(i,d,e,f,h,"throw",a)}f(void 0)})}}var i={ADD_BLOCK:"[data-key=\"addblock\"]"},j=null,k=function(a,b,c){document.addEventListener("click",function(d){if(d.target.closest(i.ADD_BLOCK)){d.preventDefault();if(null===c){c=d.target.closest(i.ADD_BLOCK).dataset.url}if(j){j.show()}else{l().then(function(d){j=d;var e=m(c,a,b);d.setBody(e);d.show();return e}).catch(function(){j.destroy();j=null})}}})},l=function(){return b.default.create({type:b.default.types.CANCEL,title:(0,d.get_string)("addblock")})},m=function(){var a=h(regeneratorRuntime.mark(function a(b,d,e){var f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return n(d,e);case 2:f=a.sent;return a.abrupt("return",c.default.render("core/add_block_body",{blocks:f,url:b}));case 4:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),n=function(){var a=h(regeneratorRuntime.mark(function a(b,c){var d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d={methodname:"core_block_fetch_addable_blocks",args:{pagecontextid:M.cfg.contextid,pagetype:b,pagelayout:c}};return a.abrupt("return",e.default.call([d])[0]);case 2:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.init=function init(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;k(a,b,c)}});
//# sourceMappingURL=addblockmodal.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -38,7 +38,7 @@ let addBlockModal = null;
* @method registerListenerEvents
* @param {String} pageType The type of the page
* @param {String} pageLayout The layout of the page
* @param {String} addBlockUrl The add block URL
* @param {String|null} addBlockUrl The add block URL
*/
const registerListenerEvents = (pageType, pageLayout, addBlockUrl) => {
document.addEventListener('click', e => {
@ -46,6 +46,10 @@ const registerListenerEvents = (pageType, pageLayout, addBlockUrl) => {
if (e.target.closest(SELECTORS.ADD_BLOCK)) {
e.preventDefault();
if (addBlockUrl === null) {
addBlockUrl = e.target.closest(SELECTORS.ADD_BLOCK).dataset.url;
}
if (addBlockModal) { // The 'add block' modal has been already created.
// Display the 'add block' modal.
addBlockModal.show();
@ -128,8 +132,8 @@ const getAddableBlocks = async(pageType, pageLayout) => {
* @method init
* @param {String} pageType The type of the page
* @param {String} pageLayout The layout of the page
* @param {String} addBlockUrl The add block URL
* @param {String|null} addBlockUrl The add block URL
*/
export const init = (pageType, pageLayout, addBlockUrl) => {
export const init = (pageType, pageLayout, addBlockUrl = null) => {
registerListenerEvents(pageType, pageLayout, addBlockUrl);
};

View file

@ -837,12 +837,23 @@ class block_manager {
}
}
public function add_block_at_end_of_default_region($blockname) {
/**
* When passed a block name create a new instance of the block in the specified region.
*
* @param string $blockname Name of the block to add.
* @param null|string $blockregion If defined add the new block to the specified region.
*/
public function add_block_at_end_of_default_region($blockname, $blockregion = null) {
if (empty($this->birecordsbyregion)) {
// No blocks or block regions exist yet.
return;
}
$defaulregion = $this->get_default_region();
if ($blockregion === null) {
$defaulregion = $this->get_default_region();
} else {
$defaulregion = $blockregion;
}
$lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
if ($lastcurrentblock) {
@ -1458,6 +1469,8 @@ class block_manager {
global $CFG, $PAGE, $OUTPUT;
$blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN);
$blockregion = optional_param('bui_blockregion', null, PARAM_TEXT);
if ($blocktype === null) {
return false;
}
@ -1519,7 +1532,7 @@ class block_manager {
throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
}
$this->add_block_at_end_of_default_region($blocktype);
$this->add_block_at_end_of_default_region($blocktype, $blockregion);
// If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
$this->page->ensure_param_not_in_url('bui_addblock');

View file

@ -58,6 +58,10 @@ class secondary extends view {
'outcomes' => 2.2,
'coursecompletion' => 6,
'filtermanagement' => 9,
'unenrolself' => 10,
'coursetags' => 11,
'download' => 12,
'contextlocking' => 13,
],
];
$nodes['navigation'] = [

View file

@ -515,6 +515,7 @@ function enrol_add_course_navigation(navigation_node $coursenode, $course) {
if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
$shortname = format_string($course->shortname, true, array('context' => $coursecontext));
$coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
$coursenode->get('unenrolself')->set_force_into_more_menu();
break;
//TODO. deal with multiple unenrol links - not likely case, but still...
}

View file

@ -844,6 +844,24 @@ class navigation_node implements renderable {
return $this->action;
}
/**
* Return an array consisting of the additional attributes for the action url.
*
* @return array Formatted array to parse in a template
*/
public function actionattributes() {
if ($this->action instanceof action_link) {
return array_map(function($key, $value) {
return [
'name' => $key,
'value' => $value
];
}, array_keys($this->action->attributes), $this->action->attributes);
}
return [];
}
/**
* Sets whether the node and its children should be added into a "more" menu whenever possible.
*
@ -4100,27 +4118,8 @@ class flat_navigation extends navigation_node_collection {
$flat->icon = new pix_icon('t/preferences', '');
$this->add($flat);
}
// Add-a-block in editing mode.
if (isset($this->page->theme->addblockposition) &&
$this->page->theme->addblockposition == BLOCK_ADDBLOCK_POSITION_FLATNAV &&
$PAGE->user_is_editing() && $PAGE->user_can_edit_blocks()) {
$url = new moodle_url($PAGE->url, ['bui_addblock' => '', 'sesskey' => sesskey()]);
$addablock = navigation_node::create(get_string('addblock'), $url);
$flat = new flat_navigation_node($addablock, 0);
$flat->set_showdivider(true, get_string('blocksaddedit'));
$flat->key = 'addblock';
$flat->icon = new pix_icon('i/addblock', '');
$this->add($flat);
$addblockurl = "?{$url->get_query_string(false)}";
$PAGE->requires->js_call_amd('core/addblockmodal', 'init',
[$PAGE->pagetype, $PAGE->pagelayout, $addblockurl]);
}
}
/**
* Override the parent so we can set a label for this collection if it has not been set yet.
*
@ -4518,6 +4517,7 @@ class settings_navigation extends navigation_node {
if (!$adminoptions->update && $adminoptions->tags) {
$url = new moodle_url('/course/tags.php', array('id' => $course->id));
$coursenode->add(get_string('coursetags', 'tag'), $url, self::TYPE_SETTING, null, 'coursetags', new pix_icon('i/settings', ''));
$coursenode->get('coursetags')->set_force_into_more_menu();
}
// add enrol nodes
@ -4654,6 +4654,7 @@ class settings_navigation extends navigation_node {
$coursenode->add($linkattr->displaystring, $actionlink, self::TYPE_SETTING, null, 'download',
new pix_icon('t/download', ''));
$coursenode->get('download')->set_force_into_more_menu();
}
// Return we are done

View file

@ -0,0 +1,41 @@
{{!
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/>.
}}
{{!
@template core/add_block_button
Template for the body of the fake 'add block' block.
Example context (json):
{
"link" : "/my/index.php?bui_addblock&bui_blockregion=content&sesskey=M3mes",
"escapedlink" : "?bui_addblock&bui_blockregion=content&sesskey=M3mes",
"pageType" : "my-index",
"pageLayout" : "mydashboard"
}
}}
<a href="{{link}}" id="addblock-{{uniqid}}" class="btn btn-link block-add text-left" data-key="addblock" data-url="{{escapedlink}}">
{{#str}}addblock{{/str}}
<i class="fa fa-plus-square pull-right py-1" aria-hidden="true"></i>
</a>
{{#js}}
// Initialise the JS for the modal window which displays the blocks available to add.
require(['core/addblockmodal'], function(addBlockModal) {
addBlockModal.init('{{pageType}}', '{{pageLayout}}');
});
{{/js}}

View file

@ -48,16 +48,10 @@
</div>
<div class="d-sm-flex align-items-center">
{{#contextheader}}
<div class="mr-auto">
{{{contextheader}}}
</div>
<div class="mr-auto">
{{{contextheader}}}
</div>
{{/contextheader}}
{{#settingsmenu}}
<div class="context-header-settings-menu">
{{{settingsmenu}}}
</div>
{{/settingsmenu}}
<div class="header-actions-container flex-shrink-0" data-region="header-actions-container">
{{#headeractions}}
<div class="header-action ml-2">{{{.}}}</div>

View file

@ -50,12 +50,12 @@
{{^haschildren}}
<li class="nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
{{#tab}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" data-text="{{{text}}}" aria-current="false" tabindex="-1">
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{tab}}" data-toggle="tab" data-text="{{{text}}}" aria-current="false" tabindex="-1">
{{{text}}}
</a>
{{/tab}}
{{^tab}}
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" {{#actionattributes}}{{name}}="{{value}}"{{/actionattributes}} href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
{{{text}}}
</a>
{{/tab}}

View file

@ -36,11 +36,6 @@
}
}
}}
{{#secondarynavigation}}
<div class="secondary-navigation">
{{> core/moremenu}}
</div>
{{/secondarynavigation}}
{{^secondarynavigation}}
<ul class="nav nav-tabs" role="tablist">
<!-- First the top most node and immediate children -->

View file

@ -250,7 +250,8 @@ Feature: Initials bar
And I click on "Save changes" "button"
And I log out
And I am on the "C1" "Course" page logged in as "teacher"
And I navigate to "Reports > Activity completion" in current page administration
And I navigate to "Reports" in current page administration
And I select "Activity completion" from the "Report type" singleselect
And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element"
And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element"
And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element"
@ -275,7 +276,8 @@ Feature: Initials bar
And I should see "Bstudent Astudent"
And I should not see "Cstudent Cstudent"
And I am on "Course 1" course homepage
And I navigate to "Reports > Activity completion" in current page administration
And I navigate to "Reports" in current page administration
And I select "Activity completion" from the "Report type" singleselect
And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element"
And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element"
And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element"

View file

@ -81,4 +81,18 @@ class behat_deprecated extends behat_base {
throw new Exception($message);
}
/**
* Clicks link with specified id|title|alt|text in the flat navigation drawer.
*
* @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from flat navigation drawer$/
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $link
* @deprecated Since Moodle 4.0
*/
public function i_select_from_flat_navigation_drawer(string $link) {
self::deprecated_message(['i_select_from_primary_navigation', 'i_select_from_secondary_navigation'], true);
$this->i_open_flat_navigation_drawer();
$this->execute('behat_general::i_click_on_in_the', [$link, 'link', '#nav-drawer', 'css_element']);
}
}

View file

@ -444,7 +444,9 @@ class behat_navigation extends behat_base {
$nodetext = end($nodes);
// Find administration menu.
$menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true);
if (!$menuxpath = $this->find_page_action_menu()) {
$menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true);
}
$this->toggle_page_administration_menu($menuxpath);
$this->execute('behat_general::should_exist_in_the', [$nodetext, $selectortype, $menuxpath, 'xpath_element']);
@ -489,7 +491,7 @@ class behat_navigation extends behat_base {
*/
public function i_navigate_to_in_site_administration($nodetext) {
$nodelist = array_map('trim', explode('>', $nodetext));
$this->i_select_from_flat_navigation_drawer(get_string('administrationsite'));
$this->i_select_from_primary_navigation(get_string('administrationsite'));
$this->select_on_administration_page($nodelist);
}
@ -911,15 +913,28 @@ class behat_navigation extends behat_base {
}
/**
* Clicks link with specified id|title|alt|text in the flat navigation drawer.
* Clicks link with specified id|title|alt|text in the primary navigation
*
* @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from flat navigation drawer$/
* @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from primary navigation$/
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $link
*/
public function i_select_from_flat_navigation_drawer($link) {
$this->i_open_flat_navigation_drawer();
$this->execute('behat_general::i_click_on_in_the', [$link, 'link', '#nav-drawer', 'css_element']);
public function i_select_from_primary_navigation(string $link) {
$this->execute('behat_general::i_click_on_in_the',
[$link, 'link', '.primary-navigation .moremenu.navigation', 'css_element']
);
}
/**
* Clicks link with specified id|title|alt|text in the secondary navigation
*
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $link
*/
public function i_select_from_secondary_navigation(string $link) {
$this->execute('behat_general::i_click_on_in_the',
[$link, 'link', '.secondary-navigation .moremenu.navigation', 'css_element']
);
}
/**
@ -928,7 +943,9 @@ class behat_navigation extends behat_base {
protected function go_to_main_course_page() {
$url = $this->getSession()->getCurrentUrl();
if (!preg_match('|/course/view.php\?id=[\d]+$|', $url)) {
$node = $this->find('xpath', '//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]');
$node = $this->find('xpath',
'//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]'
);
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
}
}
@ -993,11 +1010,16 @@ class behat_navigation extends behat_base {
// Find a link and click on it.
$linkname = behat_context_helper::escape($lastnode);
$xpath .= '//a[contains(normalize-space(.), ' . $linkname . ')]';
if (!$node = $this->getSession()->getPage()->find('xpath', $xpath)) {
throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"');
$xpathlink = $xpathbutton = $xpath;
$xpathlink .= '//a[contains(normalize-space(.), ' . $linkname . ')]';
$xpathbutton .= '//button[contains(normalize-space(.), ' . $linkname . ')]';
if ($node = $this->getSession()->getPage()->find('xpath', $xpathbutton)) {
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
} else if (!$node = $this->getSession()->getPage()->find('xpath', $xpathlink)) {
throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"');
} else {
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
}
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
}
/**
@ -1007,7 +1029,8 @@ class behat_navigation extends behat_base {
* @return null|string
*/
protected function find_header_administration_menu($mustexist = false) {
$menuxpath = '//header[@id=\'page-header\']//div[contains(@class,\'moodle-actionmenu\')]';
$menuxpath = '//div[contains(@class,\'secondary-navigation\')]//nav[contains(@class,\'moremenu\')]';
if ($mustexist) {
$exception = new ElementNotFoundException($this->getSession(), 'Page header administration menu');
$this->find('xpath', $menuxpath, $exception);
@ -1034,6 +1057,24 @@ class behat_navigation extends behat_base {
return $menuxpath;
}
/**
* Locates the action menu on the page (but not in the header) and returns its xpath
*
* @param null|bool $mustexist if specified throws an exception if menu is not found
* @return null|string
*/
protected function find_page_action_menu($mustexist = false) {
$menuxpath = '//div[@id=\'action-menu-0-menubar\']';
if ($mustexist) {
$exception = new ElementNotFoundException($this->getSession(), 'Page check');
$this->find('xpath', $menuxpath, $exception);
} else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) {
return null;
}
return $menuxpath;
}
/**
* Toggles administration menu
*
@ -1045,7 +1086,9 @@ class behat_navigation extends behat_base {
}
if ($menuxpath && $this->running_javascript()) {
$node = $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']');
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
if ($node->isVisible()) {
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
}
}
}
@ -1062,6 +1105,8 @@ class behat_navigation extends behat_base {
// Find administration menu.
if ($menuxpath = $this->find_header_administration_menu()) {
$isheader = true;
} else if ($menuxpath = $this->find_page_action_menu(true)) {
$isheader = false;
} else {
$menuxpath = $this->find_page_administration_menu(true);
$isheader = false;
@ -1069,6 +1114,12 @@ class behat_navigation extends behat_base {
$this->execute('behat_navigation::toggle_page_administration_menu', [$menuxpath]);
$firstnode = $nodelist[0];
$firstlinkname = behat_context_helper::escape($firstnode);
$firstlink = $this->getSession()->getPage()->find('xpath',
$menuxpath . '//a[contains(normalize-space(.), ' . $firstlinkname . ')]'
);
if (!$isheader || count($nodelist) == 1) {
$lastnode = end($nodelist);
$linkname = behat_context_helper::escape($lastnode);
@ -1077,16 +1128,32 @@ class behat_navigation extends behat_base {
$this->execute('behat_general::i_click_on', [$link, 'NodeElement']);
return;
}
} else if ($firstlink) {
$this->execute('behat_general::i_click_on', [$firstlink, 'NodeElement']);
array_splice($nodelist, 0, 1);
$this->select_on_administration_page($nodelist);
return;
}
if ($isheader) {
// Course administration and Front page administration will have subnodes under "More...".
// Front page administration will have subnodes under "More...".
$linkname = behat_context_helper::escape(get_string('morenavigationlinks'));
$link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]');
$link = $this->getSession()->getPage()->find('xpath',
$menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]'
);
// Course administration will have subnodes under "Course administration".
$courselinkname = behat_context_helper::escape(get_string('courseadministration'));
$courselink = $this->getSession()->getPage()->find('xpath',
$menuxpath . '//a[contains(normalize-space(.), ' . $courselinkname . ')]'
);
if ($link) {
$this->execute('behat_general::i_click_on', [$link, 'NodeElement']);
$this->select_on_administration_page($nodelist);
return;
} else if ($courselink) {
$this->execute('behat_general::i_click_on', [$courselink, 'NodeElement']);
$this->select_on_administration_page($nodelist);
return;
}
}
@ -1115,6 +1182,7 @@ class behat_navigation extends behat_base {
* First checks to see if we are on this page via the breadcrumb. If not we then attempt to follow the link name given.
*
* @param string $pagename Name of the breadcrumb item to check and follow.
* @Given /^I follow the breadcrumb "(?P<url_string>(?:[^"]|\\")*)"$/
*/
public function go_to_breadcrumb_location(string $pagename): void {
$link = $this->getSession()->getPage()->find(