MDL-64819 course: fix behat errors with the move icon
[moodle.git] / course / tests / behat / behat_course.php
CommitLineData
a1990e50
DM
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Behat course-related steps definitions.
19 *
20 * @package core_course
21 * @category test
22 * @copyright 2012 David Monllaó
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
27
28require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
29
eb9ca848 30use Behat\Gherkin\Node\TableNode as TableNode,
18c84063 31 Behat\Mink\Exception\ExpectationException as ExpectationException,
0e575f01 32 Behat\Mink\Exception\DriverException as DriverException,
18c84063 33 Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
a1990e50
DM
34
35/**
36 * Course-related steps definitions.
37 *
38 * @package core_course
39 * @category test
40 * @copyright 2012 David Monllaó
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 */
43class behat_course extends behat_base {
44
45 /**
46 * Turns editing mode on.
47 * @Given /^I turn editing mode on$/
48 */
49 public function i_turn_editing_mode_on() {
eb9ca848 50
c99dffb6
RT
51 try {
52 $this->execute("behat_forms::press_button", get_string('turneditingon'));
53 } catch (Exception $e) {
ebcff7e2 54 $this->execute("behat_navigation::i_navigate_to_in_current_page_administration", [get_string('turneditingon')]);
c99dffb6 55 }
a1990e50
DM
56 }
57
58 /**
59 * Turns editing mode off.
60 * @Given /^I turn editing mode off$/
61 */
62 public function i_turn_editing_mode_off() {
eb9ca848 63
c99dffb6
RT
64 try {
65 $this->execute("behat_forms::press_button", get_string('turneditingoff'));
66 } catch (Exception $e) {
ebcff7e2 67 $this->execute("behat_navigation::i_navigate_to_in_current_page_administration", [get_string('turneditingoff')]);
c99dffb6 68 }
a1990e50
DM
69 }
70
df1ff55d
DM
71 /**
72 * Creates a new course with the provided table data matching course settings names with the desired values.
73 *
74 * @Given /^I create a course with:$/
75 * @param TableNode $table The course data
76 */
77 public function i_create_a_course_with(TableNode $table) {
d1e55a47 78
eb9ca848
RT
79 // Go to course management page.
80 $this->i_go_to_the_courses_management_page();
81 // Ensure you are on course management page.
82 $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categories'));
83
84 // Select Miscellaneous category.
85 $this->i_click_on_category_in_the_management_interface(get_string('miscellaneous'));
86 $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcoures'));
87
88 // Click create new course.
89 $this->execute('behat_general::i_click_on_in_the',
90 array(get_string('createnewcourse'), "link", "#course-listing", "css_element")
df1ff55d 91 );
d1e55a47
DM
92
93 // If the course format is one of the fields we change how we
94 // fill the form as we need to wait for the form to be set.
95 $rowshash = $table->getRowsHash();
96 $formatfieldrefs = array(get_string('format'), 'format', 'id_format');
97 foreach ($formatfieldrefs as $fieldref) {
98 if (!empty($rowshash[$fieldref])) {
99 $formatfield = $fieldref;
100 }
101 }
102
103 // Setting the format separately.
104 if (!empty($formatfield)) {
105
106 // Removing the format field from the TableNode.
107 $rows = $table->getRows();
108 $formatvalue = $rowshash[$formatfield];
109 foreach ($rows as $key => $row) {
110 if ($row[0] == $formatfield) {
111 unset($rows[$key]);
112 }
113 }
42ad096f 114 $table = new TableNode($rows);
d1e55a47 115
c1faf86b
DM
116 // Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
117 // format field when the editor is being rendered and the click misses the field coordinates.
eb9ca848
RT
118 $this->execute("behat_forms::i_expand_all_fieldsets");
119
120 $this->execute("behat_forms::i_set_the_field_to", array($formatfield, $formatvalue));
d1e55a47
DM
121 }
122
eb9ca848
RT
123 // Set form fields.
124 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $table);
125
126 // Save course settings.
127 $this->execute("behat_forms::press_button", get_string('savechangesanddisplay'));
d1e55a47 128
df1ff55d
DM
129 }
130
131 /**
132 * Goes to the system courses/categories management page.
133 *
134 * @Given /^I go to the courses management page$/
135 */
136 public function i_go_to_the_courses_management_page() {
eb9ca848 137
02fda279 138 $parentnodes = get_string('courses', 'admin');
eb9ca848
RT
139
140 // Go to home page.
141 $this->execute("behat_general::i_am_on_homepage");
142
02fda279
VDF
143 // Navigate to course management via system administration.
144 $this->execute("behat_navigation::i_navigate_to_in_site_administration",
145 array($parentnodes . ' > ' . get_string('coursemgmt', 'admin'))
6dfd8325 146 );
eb9ca848 147
df1ff55d
DM
148 }
149
a1990e50 150 /**
9842e231 151 * Adds the selected activity/resource filling the form data with the specified field/value pairs. Sections 0 and 1 are also allowed on frontpage.
a1990e50 152 *
44d5af38 153 * @When /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)" and I fill the form with:$/
a1990e50 154 * @param string $activity The activity name
0e575f01 155 * @param int $section The section number
a1990e50
DM
156 * @param TableNode $data The activity field/value data
157 */
158 public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) {
159
eb9ca848
RT
160 // Add activity to section.
161 $this->execute("behat_course::i_add_to_section",
162 array($this->escape($activity), $this->escape($section))
a1990e50 163 );
eb9ca848
RT
164
165 // Wait to be redirected.
166 $this->execute('behat_general::wait_until_the_page_is_ready');
167
168 // Set form fields.
169 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
170
171 // Save course settings.
172 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
a1990e50
DM
173 }
174
175 /**
9842e231 176 * Opens the activity chooser and opens the activity/resource form page. Sections 0 and 1 are also allowed on frontpage.
a1990e50 177 *
44d5af38 178 * @Given /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)"$/
1f9ffbdb 179 * @throws ElementNotFoundException Thrown by behat_base::find
a1990e50 180 * @param string $activity
0e575f01 181 * @param int $section
a1990e50
DM
182 */
183 public function i_add_to_section($activity, $section) {
184
9842e231
MG
185 if ($this->getSession()->getPage()->find('css', 'body#page-site-index') && (int)$section <= 1) {
186 // We are on the frontpage.
187 if ($section) {
188 // Section 1 represents the contents on the frontpage.
e3652936
MM
189 $sectionxpath = "//body[@id='page-site-index']" .
190 "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
9842e231
MG
191 } else {
192 // Section 0 represents "Site main menu" block.
e3652936 193 $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
9842e231
MG
194 }
195 } else {
196 // We are inside the course.
197 $sectionxpath = "//li[@id='section-" . $section . "']";
198 }
38976081 199
921faad9 200 $activityliteral = behat_context_helper::escape(ucfirst($activity));
1c00d6f6
DM
201
202 if ($this->running_javascript()) {
203
204 // Clicks add activity or resource section link.
e3652936
MM
205 $sectionxpath = $sectionxpath . "/descendant::div" .
206 "[contains(concat(' ', normalize-space(@class) , ' '), ' section-modchooser ')]/span/a";
1c00d6f6
DM
207 $sectionnode = $this->find('xpath', $sectionxpath);
208 $sectionnode->click();
209
210 // Clicks the selected activity if it exists.
00ea74cb 211 $activityxpath = "//div[@id='chooseform']/descendant::label" .
e3652936
MM
212 "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
213 "[normalize-space(.)=$activityliteral]" .
214 "/parent::label/child::input";
1c00d6f6
DM
215 $activitynode = $this->find('xpath', $activityxpath);
216 $activitynode->doubleClick();
217
218 } else {
219 // Without Javascript.
220
221 // Selecting the option from the select box which contains the option.
e3652936
MM
222 $selectxpath = $sectionxpath . "/descendant::div" .
223 "[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
224 "/descendant::select[option[normalize-space(.)=$activityliteral]]";
1c00d6f6
DM
225 $selectnode = $this->find('xpath', $selectxpath);
226 $selectnode->selectOption($activity);
227
228 // Go button.
229 $gobuttonxpath = $selectxpath . "/ancestor::form/descendant::input[@type='submit']";
230 $gobutton = $this->find('xpath', $gobuttonxpath);
231 $gobutton->click();
232 }
233
a1990e50
DM
234 }
235
60cf0742
S
236 /**
237 * Opens a section edit menu if it is not already opened.
238 *
239 * @Given /^I open section "(?P<section_number>\d+)" edit menu$/
240 * @throws DriverException The step is not available when Javascript is disabled
241 * @param string $sectionnumber
242 */
243 public function i_open_section_edit_menu($sectionnumber) {
244 if (!$this->running_javascript()) {
245 throw new DriverException('Section edit menu not available when Javascript is disabled');
246 }
247
380ab95f
RT
248 // Wait for section to be available, before clicking on the menu.
249 $this->i_wait_until_section_is_available($sectionnumber);
250
60cf0742
S
251 // If it is already opened we do nothing.
252 $xpath = $this->section_exists($sectionnumber);
e3652936 253 $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]";
60cf0742
S
254
255 $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
256 $menu = $this->find('xpath', $xpath, $exception);
257 $menu->click();
258 $this->i_wait_until_section_is_available($sectionnumber);
259 }
260
261 /**
262 * Deletes course section.
263 *
264 * @Given /^I delete section "(?P<section_number>\d+)"$/
265 * @param int $sectionnumber The section number
60cf0742
S
266 */
267 public function i_delete_section($sectionnumber) {
268 // Ensures the section exists.
269 $xpath = $this->section_exists($sectionnumber);
270
271 // We need to know the course format as the text strings depends on them.
272 $courseformat = $this->get_course_format();
273 if (get_string_manager()->string_exists('deletesection', $courseformat)) {
274 $strdelete = get_string('deletesection', $courseformat);
275 } else {
276 $strdelete = get_string('deletesection');
277 }
278
279 // If javascript is on, link is inside a menu.
280 if ($this->running_javascript()) {
281 $this->i_open_section_edit_menu($sectionnumber);
282 }
283
eb9ca848
RT
284 // Click on delete link.
285 $this->execute('behat_general::i_click_on_in_the',
286 array($strdelete, "link", $this->escape($xpath), "xpath_element")
287 );
288
60cf0742
S
289 }
290
18c84063
DM
291 /**
292 * Turns course section highlighting on.
293 *
294 * @Given /^I turn section "(?P<section_number>\d+)" highlighting on$/
295 * @param int $sectionnumber The section number
296 */
297 public function i_turn_section_highlighting_on($sectionnumber) {
298
299 // Ensures the section exists.
300 $xpath = $this->section_exists($sectionnumber);
301
60cf0742
S
302 // If javascript is on, link is inside a menu.
303 if ($this->running_javascript()) {
304 $this->i_open_section_edit_menu($sectionnumber);
305 }
306
eb9ca848
RT
307 // Click on highlight topic link.
308 $this->execute('behat_general::i_click_on_in_the',
cce54c47 309 array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element")
eb9ca848 310 );
18c84063
DM
311 }
312
313 /**
314 * Turns course section highlighting off.
315 *
316 * @Given /^I turn section "(?P<section_number>\d+)" highlighting off$/
317 * @param int $sectionnumber The section number
318 */
319 public function i_turn_section_highlighting_off($sectionnumber) {
320
321 // Ensures the section exists.
322 $xpath = $this->section_exists($sectionnumber);
323
60cf0742
S
324 // If javascript is on, link is inside a menu.
325 if ($this->running_javascript()) {
326 $this->i_open_section_edit_menu($sectionnumber);
327 }
328
eb9ca848
RT
329 // Click on un-highlight topic link.
330 $this->execute('behat_general::i_click_on_in_the',
cce54c47 331 array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element")
eb9ca848 332 );
18c84063
DM
333 }
334
335 /**
b918f9f7
DM
336 * Shows the specified hidden section. You need to be in the course page and on editing mode.
337 *
338 * @Given /^I show section "(?P<section_number>\d+)"$/
339 * @param int $sectionnumber
340 */
341 public function i_show_section($sectionnumber) {
8857c715 342 $showlink = $this->show_section_link_exists($sectionnumber);
380ab95f
RT
343
344 // Ensure section edit menu is open before interacting with it.
345 if ($this->running_javascript()) {
346 $this->i_open_section_edit_menu($sectionnumber);
347 }
0e575f01 348 $showlink->click();
b918f9f7 349
0e575f01 350 if ($this->running_javascript()) {
05a5d547 351 $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
d1e55a47 352 $this->i_wait_until_section_is_available($sectionnumber);
0e575f01 353 }
b918f9f7
DM
354 }
355
356 /**
357 * Hides the specified visible section. You need to be in the course page and on editing mode.
358 *
359 * @Given /^I hide section "(?P<section_number>\d+)"$/
360 * @param int $sectionnumber
361 */
362 public function i_hide_section($sectionnumber) {
8857c715
DW
363 // Ensures the section exists.
364 $xpath = $this->section_exists($sectionnumber);
380ab95f 365
8857c715
DW
366 // We need to know the course format as the text strings depends on them.
367 $courseformat = $this->get_course_format();
368 if (get_string_manager()->string_exists('hidefromothers', $courseformat)) {
369 $strhide = get_string('hidefromothers', $courseformat);
370 } else {
371 $strhide = get_string('hidesection');
372 }
373
374 // If javascript is on, link is inside a menu.
380ab95f
RT
375 if ($this->running_javascript()) {
376 $this->i_open_section_edit_menu($sectionnumber);
377 }
8857c715
DW
378
379 // Click on delete link.
380 $this->execute('behat_general::i_click_on_in_the',
381 array($strhide, "link", $this->escape($xpath), "xpath_element")
382 );
b918f9f7 383
0e575f01 384 if ($this->running_javascript()) {
05a5d547 385 $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
d1e55a47 386 $this->i_wait_until_section_is_available($sectionnumber);
0e575f01 387 }
b918f9f7
DM
388 }
389
3f038d6d
RT
390 /**
391 * Go to editing section page for specified section number. You need to be in the course page and on editing mode.
392 *
393 * @Given /^I edit the section "(?P<section_number>\d+)"$/
394 * @param int $sectionnumber
395 */
396 public function i_edit_the_section($sectionnumber) {
60cf0742
S
397 // If javascript is on, link is inside a menu.
398 if ($this->running_javascript()) {
399 $this->i_open_section_edit_menu($sectionnumber);
400 }
401
402 // We need to know the course format as the text strings depends on them.
403 $courseformat = $this->get_course_format();
0f84b17e 404 if ($sectionnumber > 0 && get_string_manager()->string_exists('editsection', $courseformat)) {
60cf0742
S
405 $stredit = get_string('editsection', $courseformat);
406 } else {
407 $stredit = get_string('editsection');
408 }
409
eb9ca848
RT
410 // Click on un-highlight topic link.
411 $this->execute('behat_general::i_click_on_in_the',
412 array($stredit, "link", "#section-" . $sectionnumber, "css_element")
413 );
414
3f038d6d
RT
415 }
416
417 /**
418 * Edit specified section and fill the form data with the specified field/value pairs.
419 *
420 * @When /^I edit the section "(?P<section_number>\d+)" and I fill the form with:$/
421 * @param int $sectionnumber The section number
422 * @param TableNode $data The activity field/value data
3f038d6d
RT
423 */
424 public function i_edit_the_section_and_i_fill_the_form_with($sectionnumber, TableNode $data) {
425
eb9ca848 426 // Edit given section.
3ceaea26 427 $this->execute("behat_course::i_edit_the_section", $sectionnumber);
eb9ca848
RT
428
429 // Set form fields.
430 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
431
432 // Save section settings.
433 $this->execute("behat_forms::press_button", get_string('savechanges'));
3f038d6d
RT
434 }
435
b918f9f7
DM
436 /**
437 * Checks if the specified course section hightlighting is turned on. You need to be in the course page on editing mode.
18c84063 438 *
18c84063 439 * @Then /^section "(?P<section_number>\d+)" should be highlighted$/
b918f9f7 440 * @throws ExpectationException
18c84063
DM
441 * @param int $sectionnumber The section number
442 */
443 public function section_should_be_highlighted($sectionnumber) {
444
445 // Ensures the section exists.
446 $xpath = $this->section_exists($sectionnumber);
447
448 // The important checking, we can not check the img.
cce54c47 449 $this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']);
18c84063
DM
450 }
451
452 /**
b918f9f7 453 * Checks if the specified course section highlighting is turned off. You need to be in the course page on editing mode.
18c84063
DM
454 *
455 * @Then /^section "(?P<section_number>\d+)" should not be highlighted$/
b918f9f7 456 * @throws ExpectationException
18c84063
DM
457 * @param int $sectionnumber The section number
458 */
459 public function section_should_not_be_highlighted($sectionnumber) {
460
461 // We only catch ExpectationException, ElementNotFoundException should be thrown if the specified section does not exist.
462 try {
463 $this->section_should_be_highlighted($sectionnumber);
464 } catch (ExpectationException $e) {
465 // ExpectedException means that it is not highlighted.
466 return;
467 }
468
469 throw new ExpectationException('The "' . $sectionnumber . '" section is highlighted', $this->getSession());
470 }
471
b918f9f7
DM
472 /**
473 * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
474 *
475 * @Then /^section "(?P<section_number>\d+)" should be hidden$/
476 * @throws ExpectationException
477 * @throws ElementNotFoundException Thrown by behat_base::find
478 * @param int $sectionnumber
479 */
480 public function section_should_be_hidden($sectionnumber) {
481
482 $sectionxpath = $this->section_exists($sectionnumber);
483
d1e55a47
DM
484 // Preventive in case there is any action in progress.
485 // Adding it here because we are interacting (click) with
486 // the elements, not necessary when we just find().
487 $this->i_wait_until_section_is_available($sectionnumber);
488
b918f9f7
DM
489 // Section should be hidden.
490 $exception = new ExpectationException('The section is not hidden', $this->getSession());
38976081 491 $this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception);
26df91ca
AN
492 }
493
494 /**
495 * Checks that all actiities in the specified section are hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
496 *
497 * @Then /^all activities in section "(?P<section_number>\d+)" should be hidden$/
498 * @throws ExpectationException
499 * @throws ElementNotFoundException Thrown by behat_base::find
500 * @param int $sectionnumber
501 */
502 public function section_activities_should_be_hidden($sectionnumber) {
503 $sectionxpath = $this->section_exists($sectionnumber);
504
505 // Preventive in case there is any action in progress.
506 // Adding it here because we are interacting (click) with
507 // the elements, not necessary when we just find().
508 $this->i_wait_until_section_is_available($sectionnumber);
b918f9f7
DM
509
510 // The checking are different depending on user permissions.
511 if ($this->is_course_editor()) {
512
513 // The section must be hidden.
8857c715 514 $this->show_section_link_exists($sectionnumber);
b918f9f7
DM
515
516 // If there are activities they should be hidden and the visibility icon should not be available.
517 if ($activities = $this->get_section_activities($sectionxpath)) {
518
519 $dimmedexception = new ExpectationException('There are activities that are not dimmed', $this->getSession());
b918f9f7 520 foreach ($activities as $activity) {
b918f9f7 521 // Dimmed.
38976081 522 $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
f59f89b4 523 "//a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
b918f9f7
DM
524 }
525 }
b918f9f7
DM
526 } else {
527 // There shouldn't be activities.
528 if ($this->get_section_activities($sectionxpath)) {
529 throw new ExpectationException('There are activities in the section and they should be hidden', $this->getSession());
530 }
531 }
26df91ca 532
b918f9f7
DM
533 }
534
535 /**
536 * Checks that the specified section is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
537 *
538 * @Then /^section "(?P<section_number>\d+)" should be visible$/
539 * @throws ExpectationException
540 * @param int $sectionnumber
541 */
542 public function section_should_be_visible($sectionnumber) {
543
544 $sectionxpath = $this->section_exists($sectionnumber);
545
546 // Section should not be hidden.
38976081
DM
547 $xpath = $sectionxpath . "[not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]";
548 if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
b918f9f7
DM
549 throw new ExpectationException('The section is hidden', $this->getSession());
550 }
551
60cf0742 552 // Edit menu should be visible.
b918f9f7 553 if ($this->is_course_editor()) {
60cf0742 554 $xpath = $sectionxpath .
e3652936
MM
555 "/descendant::div[contains(@class, 'section-actions')]" .
556 "/descendant::a[contains(@data-toggle, 'dropdown')]";
60cf0742
S
557 if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
558 throw new ExpectationException('The section edit menu is not available', $this->getSession());
559 }
b918f9f7
DM
560 }
561 }
562
0e575f01
DM
563 /**
564 * Moves up the specified section, this step only works with Javascript disabled. Editing mode should be on.
565 *
566 * @Given /^I move up section "(?P<section_number>\d+)"$/
567 * @throws DriverException Step not available when Javascript is enabled
568 * @param int $sectionnumber
569 */
570 public function i_move_up_section($sectionnumber) {
571
572 if ($this->running_javascript()) {
573 throw new DriverException('Move a section up step is not available with Javascript enabled');
574 }
575
576 // Ensures the section exists.
577 $sectionxpath = $this->section_exists($sectionnumber);
578
60cf0742
S
579 // If javascript is on, link is inside a menu.
580 if ($this->running_javascript()) {
581 $this->i_open_section_edit_menu($sectionnumber);
582 }
583
0e575f01
DM
584 // Follows the link
585 $moveuplink = $this->get_node_in_container('link', get_string('moveup'), 'xpath_element', $sectionxpath);
586 $moveuplink->click();
587 }
588
589 /**
590 * Moves down the specified section, this step only works with Javascript disabled. Editing mode should be on.
591 *
592 * @Given /^I move down section "(?P<section_number>\d+)"$/
593 * @throws DriverException Step not available when Javascript is enabled
594 * @param int $sectionnumber
595 */
596 public function i_move_down_section($sectionnumber) {
597
598 if ($this->running_javascript()) {
599 throw new DriverException('Move a section down step is not available with Javascript enabled');
600 }
601
602 // Ensures the section exists.
603 $sectionxpath = $this->section_exists($sectionnumber);
604
60cf0742
S
605 // If javascript is on, link is inside a menu.
606 if ($this->running_javascript()) {
607 $this->i_open_section_edit_menu($sectionnumber);
608 }
609
0e575f01
DM
610 // Follows the link
611 $movedownlink = $this->get_node_in_container('link', get_string('movedown'), 'xpath_element', $sectionxpath);
612 $movedownlink->click();
613 }
614
bf648567
DM
615 /**
616 * Checks that the specified activity is visible. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
617 *
618 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be visible$/
619 * @param string $activityname
5dc361e1 620 * @throws ExpectationException
bf648567
DM
621 */
622 public function activity_should_be_visible($activityname) {
623
624 // The activity must exists and be visible.
625 $activitynode = $this->get_activity_node($activityname);
626
627 if ($this->is_course_editor()) {
628
629 // The activity should not be dimmed.
630 try {
fe499285
RT
631 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
632 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
633 $this->find('xpath', $xpath, false, $activitynode);
bf648567
DM
634 throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
635 } catch (ElementNotFoundException $e) {
636 // All ok.
637 }
638
3f950346
MG
639 // Additional check if this is a teacher in editing mode.
640 if ($this->is_editing_on()) {
641 // The 'Hide' button should be available.
642 $nohideexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
643 get_string('hide') . '" icon', $this->getSession());
644 $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
645 }
646 }
647 }
648
649 /**
650 * Checks that the specified activity is visible. You need to be in the course page.
651 * It can be used being logged as a student and as a teacher on editing mode.
652 *
653 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be available but hidden from course page$/
654 * @param string $activityname
655 * @throws ExpectationException
656 */
657 public function activity_should_be_available_but_hidden_from_course_page($activityname) {
658
659 if ($this->is_course_editor()) {
660
661 // The activity must exists and be visible.
662 $activitynode = $this->get_activity_node($activityname);
663
664 // The activity should not be dimmed.
665 try {
666 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | " .
667 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
668 $this->find('xpath', $xpath, false, $activitynode);
669 throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
670 } catch (ElementNotFoundException $e) {
671 // All ok.
672 }
673
674 // Should has "stealth" class.
675 $exception = new ExpectationException('"' . $activityname . '" does not have CSS class "stealth"', $this->getSession());
676 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' stealth ')]";
677 $this->find('xpath', $xpath, $exception, $activitynode);
678
679 // Additional check if this is a teacher in editing mode.
680 if ($this->is_editing_on()) {
681 // Also has either 'Hide' or 'Make unavailable' edit control.
682 $nohideexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('hide') .
683 '" nor "' . get_string('makeunavailable') . '" icons', $this->getSession());
684 try {
685 $this->find('named_partial', array('link', get_string('hide')), false, $activitynode);
686 } catch (ElementNotFoundException $e) {
687 $this->find('named_partial', array('link', get_string('makeunavailable')), $nohideexception, $activitynode);
688 }
689 }
690
691 } else {
692
693 // Student should not see the activity at all.
694 try {
695 $this->get_activity_node($activityname);
696 throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
697 } catch (ElementNotFoundException $e) {
698 // This is good, the activity should not be there.
699 }
bf648567
DM
700 }
701 }
702
703 /**
704 * Checks that the specified activity is hidden. You need to be in the course page. It can be used being logged as a student and as a teacher on editing mode.
705 *
706 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be hidden$/
707 * @param string $activityname
5dc361e1 708 * @throws ExpectationException
bf648567
DM
709 */
710 public function activity_should_be_hidden($activityname) {
711
712 if ($this->is_course_editor()) {
713
759b323e 714 // The activity should exist.
bf648567
DM
715 $activitynode = $this->get_activity_node($activityname);
716
717 // Should be hidden.
718 $exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
fe499285
RT
719 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
720 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
721 $this->find('xpath', $xpath, $exception, $activitynode);
bf648567 722
3f950346
MG
723 // Additional check if this is a teacher in editing mode.
724 if ($this->is_editing_on()) {
725 // Also has either 'Show' or 'Make available' edit control.
726 $noshowexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('show') .
727 '" nor "' . get_string('makeavailable') . '" icons', $this->getSession());
728 try {
729 $this->find('named_partial', array('link', get_string('show')), false, $activitynode);
730 } catch (ElementNotFoundException $e) {
731 $this->find('named_partial', array('link', get_string('makeavailable')), $noshowexception, $activitynode);
732 }
733 }
bf648567
DM
734
735 } else {
736
759b323e 737 // It should not exist at all.
bf648567 738 try {
3f950346 739 $this->get_activity_node($activityname);
ba5c5083 740 throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
bf648567
DM
741 } catch (ElementNotFoundException $e) {
742 // This is good, the activity should not be there.
743 }
744 }
745
746 }
747
3f950346
MG
748 /**
749 * Checks that the specified activity is dimmed. You need to be in the course page.
750 *
751 * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be dimmed$/
752 * @param string $activityname
753 * @throws ExpectationException
754 */
755 public function activity_should_be_dimmed($activityname) {
756
757 // The activity should exist.
758 $activitynode = $this->get_activity_node($activityname);
759
760 // Should be hidden.
761 $exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
762 $xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
763 "/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
764 $this->find('xpath', $xpath, $exception, $activitynode);
765
766 }
767
0e575f01 768 /**
7daab401 769 * Moves the specified activity to the first slot of a section. This step is experimental when using it in Javascript tests. Editing mode should be on.
0e575f01
DM
770 *
771 * @Given /^I move "(?P<activity_name_string>(?:[^"]|\\")*)" activity to section "(?P<section_number>\d+)"$/
772 * @param string $activityname The activity name
773 * @param int $sectionnumber The number of section
774 */
775 public function i_move_activity_to_section($activityname, $sectionnumber) {
776
777 // Ensure the destination is valid.
778 $sectionxpath = $this->section_exists($sectionnumber);
779
0e575f01
DM
780 // JS enabled.
781 if ($this->running_javascript()) {
782
1929d6f8 783 $activitynode = $this->get_activity_element('Move', 'icon', $activityname);
38976081 784 $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
0e575f01 785
eb9ca848
RT
786 $this->execute("behat_general::i_drag_and_i_drop_it_in",
787 array($this->escape($activitynode->getXpath()), "xpath_element",
788 $this->escape($destinationxpath), "xpath_element")
0e575f01
DM
789 );
790
791 } else {
792 // Following links with no-JS.
793
794 // Moving to the fist spot of the section (before all other section's activities).
eb9ca848
RT
795 $this->execute('behat_course::i_click_on_in_the_activity',
796 array("a.editing_move", "css_element", $this->escape($activityname))
797 );
798
799 $this->execute('behat_general::i_click_on_in_the',
800 array("li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element")
0e575f01
DM
801 );
802 }
803 }
804
805 /**
806 * Edits the activity name through the edit activity; this step only works with Javascript enabled. Editing mode should be on.
807 *
808 * @Given /^I change "(?P<activity_name_string>(?:[^"]|\\")*)" activity name to "(?P<new_name_string>(?:[^"]|\\")*)"$/
809 * @throws DriverException Step not available when Javascript is disabled
810 * @param string $activityname
811 * @param string $newactivityname
812 */
813 public function i_change_activity_name_to($activityname, $newactivityname) {
814
815 if (!$this->running_javascript()) {
816 throw new DriverException('Change activity name step is not available with Javascript disabled');
817 }
818
e5de4933 819 $activity = $this->escape($activityname);
eb9ca848
RT
820
821 $this->execute('behat_course::i_click_on_in_the_activity',
822 array(get_string('edittitle'), "link", $activity)
0e575f01 823 );
eb9ca848
RT
824
825 // Adding chr(10) to save changes.
826 $this->execute('behat_forms::i_set_the_field_to',
827 array('title', $this->escape($newactivityname) . chr(10))
828 );
829
0e575f01
DM
830 }
831
2a9275c4
DM
832 /**
833 * Opens an activity actions menu if it is not already opened.
834 *
835 * @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
836 * @throws DriverException The step is not available when Javascript is disabled
837 * @param string $activityname
2a9275c4
DM
838 */
839 public function i_open_actions_menu($activityname) {
840
841 if (!$this->running_javascript()) {
842 throw new DriverException('Activities actions menu not available when Javascript is disabled');
843 }
844
845 // If it is already opened we do nothing.
846 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
847
848 // Find the menu.
849 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
850 if (!$menunode) {
851 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
852 $this->getSession());
853 }
854 $expanded = $menunode->getAttribute('aria-expanded');
855 if ($expanded == 'true') {
2a9275c4
DM
856 return;
857 }
858
eb9ca848 859 $this->execute('behat_course::i_click_on_in_the_activity',
e3652936 860 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
eb9ca848
RT
861 );
862
e3652936 863 $this->actions_menu_should_be_open($activityname);
2a9275c4
DM
864 }
865
d1e55a47
DM
866 /**
867 * Closes an activity actions menu if it is not already closed.
868 *
869 * @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
870 * @throws DriverException The step is not available when Javascript is disabled
871 * @param string $activityname
d1e55a47
DM
872 */
873 public function i_close_actions_menu($activityname) {
874
875 if (!$this->running_javascript()) {
876 throw new DriverException('Activities actions menu not available when Javascript is disabled');
877 }
878
879 // If it is already closed we do nothing.
880 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
881 // Find the menu.
882 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
883 if (!$menunode) {
884 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
885 $this->getSession());
886 }
887 $expanded = $menunode->getAttribute('aria-expanded');
888 if ($expanded != 'true') {
d1e55a47
DM
889 return;
890 }
891
eb9ca848 892 $this->execute('behat_course::i_click_on_in_the_activity',
e3652936 893 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
eb9ca848 894 );
d1e55a47
DM
895 }
896
af4d19ab
AN
897 /**
898 * Checks that the specified activity's action menu is open.
899 *
900 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/
901 * @throws DriverException The step is not available when Javascript is disabled
902 * @param string $activityname
903 */
904 public function actions_menu_should_be_open($activityname) {
905
906 if (!$this->running_javascript()) {
907 throw new DriverException('Activities actions menu not available when Javascript is disabled');
908 }
909
af4d19ab 910 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
911 // Find the menu.
912 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
913 if (!$menunode) {
914 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
915 $this->getSession());
916 }
917 $expanded = $menunode->getAttribute('aria-expanded');
918 if ($expanded != 'true') {
af4d19ab
AN
919 throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
920 }
af4d19ab
AN
921 }
922
3f950346
MG
923 /**
924 * Checks that the specified activity's action menu contains an item.
925 *
926 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
927 * @throws DriverException The step is not available when Javascript is disabled
928 * @param string $activityname
929 * @param string $menuitem
930 */
931 public function actions_menu_should_have_item($activityname, $menuitem) {
932 $activitynode = $this->get_activity_node($activityname);
933
934 $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
935 $menuitem . '" item', $this->getSession());
936 $this->find('named_partial', array('link', $menuitem), $notfoundexception, $activitynode);
937 }
938
939 /**
940 * Checks that the specified activity's action menu does not contains an item.
941 *
942 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
943 * @throws DriverException The step is not available when Javascript is disabled
944 * @param string $activityname
945 * @param string $menuitem
946 */
947 public function actions_menu_should_not_have_item($activityname, $menuitem) {
948 $activitynode = $this->get_activity_node($activityname);
949
950 try {
951 $this->find('named_partial', array('link', $menuitem), false, $activitynode);
952 throw new ExpectationException('"' . $activityname . '" has a "' . $menuitem .
953 '" item when it should not', $this->getSession());
954 } catch (ElementNotFoundException $e) {
955 // This is good, the menu item should not be there.
956 }
957 }
958
0e575f01
DM
959 /**
960 * Indents to the right the activity or resource specified by it's name. Editing mode should be on.
961 *
962 * @Given /^I indent right "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
963 * @param string $activityname
964 */
965 public function i_indent_right_activity($activityname) {
966
e5de4933
SH
967 $activity = $this->escape($activityname);
968 if ($this->running_javascript()) {
eb9ca848 969 $this->i_open_actions_menu($activity);
e5de4933 970 }
0e575f01 971
eb9ca848
RT
972 $this->execute('behat_course::i_click_on_in_the_activity',
973 array(get_string('moveright'), "link", $this->escape($activity))
974 );
975
0e575f01
DM
976 }
977
978 /**
979 * Indents to the left the activity or resource specified by it's name. Editing mode should be on.
980 *
981 * @Given /^I indent left "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
982 * @param string $activityname
983 */
984 public function i_indent_left_activity($activityname) {
985
e5de4933
SH
986 $activity = $this->escape($activityname);
987 if ($this->running_javascript()) {
eb9ca848 988 $this->i_open_actions_menu($activity);
e5de4933 989 }
0e575f01 990
eb9ca848
RT
991 $this->execute('behat_course::i_click_on_in_the_activity',
992 array(get_string('moveleft'), "link", $this->escape($activity))
993 );
0e575f01
DM
994
995 }
996
997 /**
7daab401 998 * Deletes the activity or resource specified by it's name. This step is experimental when using it in Javascript tests. You should be in the course page with editing mode on.
0e575f01
DM
999 *
1000 * @Given /^I delete "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1001 * @param string $activityname
1002 */
1003 public function i_delete_activity($activityname) {
84a1e50a
RL
1004 $steps = array();
1005 $activity = $this->escape($activityname);
1006 if ($this->running_javascript()) {
eb9ca848 1007 $this->i_open_actions_menu($activity);
84a1e50a 1008 }
eb9ca848
RT
1009
1010 $this->execute('behat_course::i_click_on_in_the_activity',
1011 array(get_string('delete'), "link", $this->escape($activity))
1012 );
fbd904ef 1013
0e575f01
DM
1014 // JS enabled.
1015 // Not using chain steps here because the exceptions catcher have problems detecting
1016 // JS modal windows and avoiding interacting them at the same time.
1017 if ($this->running_javascript()) {
eb9ca848
RT
1018 $this->execute('behat_general::i_click_on_in_the',
1019 array(get_string('yes'), "button", "Confirm", "dialogue")
1020 );
0e575f01 1021 } else {
eb9ca848 1022 $this->execute("behat_forms::press_button", get_string('yes'));
0e575f01 1023 }
fbd904ef
DM
1024
1025 return $steps;
0e575f01
DM
1026 }
1027
1028 /**
1029 * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on.
1030 *
1031 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1032 * @param string $activityname
1033 */
1034 public function i_duplicate_activity($activityname) {
e5de4933
SH
1035 $steps = array();
1036 $activity = $this->escape($activityname);
1037 if ($this->running_javascript()) {
eb9ca848 1038 $this->i_open_actions_menu($activity);
e5de4933 1039 }
eb9ca848
RT
1040 $this->execute('behat_course::i_click_on_in_the_activity',
1041 array(get_string('duplicate'), "link", $activity)
1042 );
1043
0e575f01
DM
1044 }
1045
1046 /**
1047 * Duplicates the activity or resource and modifies the new activity with the provided data. You should be in the course page with editing mode on.
1048 *
1049 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/
1050 * @param string $activityname
1051 * @param TableNode $data
1052 */
1053 public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
047a8800 1054
e5de4933 1055 $activity = $this->escape($activityname);
921faad9 1056 $activityliteral = behat_context_helper::escape($activityname);
047a8800 1057
eb9ca848 1058 $this->execute("behat_course::i_duplicate_activity", $activity);
047a8800 1059
60df6787
S
1060 // Determine the future new activity xpath from the former one.
1061 $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
e3652936
MM
1062 "[contains(., $activityliteral)]/following-sibling::li";
1063 $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']";
60df6787
S
1064
1065 if ($this->running_javascript()) {
d1e55a47 1066 // We wait until the AJAX request finishes and the section is visible again.
60df6787 1067 $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
e3652936
MM
1068 "[contains(., $activityliteral)]" .
1069 "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
1070 "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
60df6787 1071
eb9ca848 1072 $this->execute("behat_general::wait_until_exists",
e3652936 1073 array($this->escape($hiddenlightboxxpath), "xpath_element")
eb9ca848 1074 );
d1e55a47
DM
1075
1076 // Close the original activity actions menu.
eb9ca848 1077 $this->i_close_actions_menu($activity);
d1e55a47 1078
047a8800
DM
1079 // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
1080 // this point, it don't even exists in the DOM (the steps are executed when we return them).
eb9ca848 1081 $this->execute('behat_general::i_click_on',
e3652936 1082 array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
eb9ca848 1083 );
e5de4933 1084 }
60df6787
S
1085
1086 // We force the xpath as otherwise mink tries to interact with the former one.
eb9ca848 1087 $this->execute('behat_general::i_click_on_in_the',
e3652936 1088 array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
eb9ca848
RT
1089 );
1090
1091 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
1092 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
60df6787 1093
0e575f01
DM
1094 }
1095
d1e55a47
DM
1096 /**
1097 * Waits until the section is available to interact with it. Useful when the section is performing an action and the section is overlayed with a loading layout.
1098 *
1099 * Using the protected method as this method will be usually
1100 * called by other methods which are not returning a set of
1101 * steps and performs the actions directly, so it would not
1102 * be executed if it returns another step.
1103 *
1104 * Hopefully we would not require test writers to use this step
1105 * and we will manage it from other step definitions.
1106 *
1107 * @Given /^I wait until section "(?P<section_number>\d+)" is available$/
1108 * @param int $sectionnumber
1109 * @return void
1110 */
1111 public function i_wait_until_section_is_available($sectionnumber) {
1112
1113 // Looks for a hidden lightbox or a non-existent lightbox in that section.
1114 $sectionxpath = $this->section_exists($sectionnumber);
1115 $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
1116 " | " .
1117 $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
1118
1119 $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element');
1120 }
1121
0e575f01
DM
1122 /**
1123 * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
1124 *
75ddb695 1125 * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
0e575f01
DM
1126 * @param string $element
1127 * @param string $selectortype
1128 * @param string $activityname
1129 */
1130 public function i_click_on_in_the_activity($element, $selectortype, $activityname) {
1131 $element = $this->get_activity_element($element, $selectortype, $activityname);
1132 $element->click();
1133 }
1134
1135 /**
1136 * Clicks on the specified element inside the activity container.
1137 *
1138 * @throws ElementNotFoundException
1139 * @param string $element
1140 * @param string $selectortype
1141 * @param string $activityname
1142 * @return NodeElement
1143 */
1144 protected function get_activity_element($element, $selectortype, $activityname) {
1145 $activitynode = $this->get_activity_node($activityname);
1146
1147 // Transforming to Behat selector/locator.
1148 list($selector, $locator) = $this->transform_selector($selectortype, $element);
1149 $exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
1150
1151 return $this->find($selector, $locator, $exception, $activitynode);
1152 }
1153
18c84063
DM
1154 /**
1155 * Checks if the course section exists.
1156 *
1157 * @throws ElementNotFoundException Thrown by behat_base::find
1158 * @param int $sectionnumber
b918f9f7 1159 * @return string The xpath of the section.
18c84063
DM
1160 */
1161 protected function section_exists($sectionnumber) {
1162
1163 // Just to give more info in case it does not exist.
1164 $xpath = "//li[@id='section-" . $sectionnumber . "']";
1165 $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber ");
1166 $this->find('xpath', $xpath, $exception);
1167
1168 return $xpath;
1169 }
b918f9f7
DM
1170
1171 /**
1172 * Returns the show section icon or throws an exception.
1173 *
1174 * @throws ElementNotFoundException Thrown by behat_base::find
1175 * @param int $sectionnumber
1176 * @return NodeElement
1177 */
8857c715 1178 protected function show_section_link_exists($sectionnumber) {
b918f9f7
DM
1179
1180 // Gets the section xpath and ensure it exists.
1181 $xpath = $this->section_exists($sectionnumber);
1182
1183 // We need to know the course format as the text strings depends on them.
1184 $courseformat = $this->get_course_format();
1185
1186 // Checking the show button alt text and show icon.
8857c715 1187 $showtext = get_string('showfromothers', $courseformat);
cce54c47 1188 $linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]";
b918f9f7 1189
cce54c47 1190 $exception = new ElementNotFoundException($this->getSession(), 'Show section link');
0e575f01
DM
1191
1192 // Returing the link so both Non-JS and JS browsers can interact with it.
1193 return $this->find('xpath', $linkxpath, $exception);
b918f9f7
DM
1194 }
1195
1196 /**
1197 * Returns the hide section icon link if it exists or throws exception.
1198 *
1199 * @throws ElementNotFoundException Thrown by behat_base::find
1200 * @param int $sectionnumber
1201 * @return NodeElement
1202 */
8857c715 1203 protected function hide_section_link_exists($sectionnumber) {
b918f9f7
DM
1204
1205 // Gets the section xpath and ensure it exists.
1206 $xpath = $this->section_exists($sectionnumber);
1207
1208 // We need to know the course format as the text strings depends on them.
1209 $courseformat = $this->get_course_format();
1210
1211 // Checking the hide button alt text and hide icon.
921faad9 1212 $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat));
38976081 1213 $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
b918f9f7
DM
1214
1215 $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon ');
8857c715 1216 $this->find('icon', 'Hide', $exception);
0e575f01
DM
1217
1218 // Returing the link so both Non-JS and JS browsers can interact with it.
1219 return $this->find('xpath', $linkxpath, $exception);
b918f9f7
DM
1220 }
1221
1222 /**
1223 * Gets the current course format.
1224 *
1225 * @throws ExpectationException If we are not in the course view page.
1226 * @return string The course format in a frankenstyled name.
1227 */
1228 protected function get_course_format() {
1229
1230 $exception = new ExpectationException('You are not in a course page', $this->getSession());
1231
1232 // The moodle body's id attribute contains the course format.
1233 $node = $this->getSession()->getPage()->find('css', 'body');
1234 if (!$node) {
1235 throw $exception;
1236 }
1237
1238 if (!$bodyid = $node->getAttribute('id')) {
1239 throw $exception;
1240 }
1241
1242 if (strstr($bodyid, 'page-course-view-') === false) {
1243 throw $exception;
1244 }
1245
1246 return 'format_' . str_replace('page-course-view-', '', $bodyid);
1247 }
1248
1249 /**
1250 * Gets the section's activites DOM nodes.
1251 *
1252 * @param string $sectionxpath
1253 * @return array NodeElement instances
1254 */
1255 protected function get_section_activities($sectionxpath) {
1256
38976081 1257 $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]";
b918f9f7
DM
1258
1259 // We spin here, as activities usually require a lot of time to load.
1260 try {
1261 $activities = $this->find_all('xpath', $xpath);
1262 } catch (ElementNotFoundException $e) {
1263 return false;
1264 }
1265
1266 return $activities;
1267 }
1268
bf648567
DM
1269 /**
1270 * Returns the DOM node of the activity from <li>.
1271 *
1272 * @throws ElementNotFoundException Thrown by behat_base::find
1273 * @param string $activityname The activity name
1274 * @return NodeElement
1275 */
1276 protected function get_activity_node($activityname) {
1277
921faad9 1278 $activityname = behat_context_helper::escape($activityname);
38976081 1279 $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]";
bf648567
DM
1280
1281 return $this->find('xpath', $xpath);
1282 }
1283
d1e55a47
DM
1284 /**
1285 * Gets the activity instance name from the activity node.
1286 *
1287 * @throws ElementNotFoundException
1288 * @param NodeElement $activitynode
1289 * @return string
1290 */
1291 protected function get_activity_name($activitynode) {
1292 $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode);
1293 return $instancenamenode->getText();
1294 }
1295
b918f9f7
DM
1296 /**
1297 * Returns whether the user can edit the course contents or not.
1298 *
1299 * @return bool
1300 */
1301 protected function is_course_editor() {
1302
1303 // We don't need to behat_base::spin() here as all is already loaded.
e3652936
MM
1304 if (!$this->getSession()->getPage()->findLink(get_string('turneditingoff')) &&
1305 !$this->getSession()->getPage()->findLink(get_string('turneditingon'))) {
b918f9f7
DM
1306 return false;
1307 }
1308
1309 return true;
1310 }
1311
3f950346
MG
1312 /**
1313 * Returns whether the user can edit the course contents and the editing mode is on.
1314 *
1315 * @return bool
1316 */
1317 protected function is_editing_on() {
1318 return $this->getSession()->getPage()->findButton(get_string('turneditingoff')) ? true : false;
1319 }
1320
5dc361e1
SH
1321 /**
1322 * Returns the id of the category with the given idnumber.
8aa3aa3d
SH
1323 *
1324 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1325 *
5dc361e1
SH
1326 * @param string $idnumber
1327 * @return string
8aa3aa3d 1328 * @throws ExpectationException
5dc361e1
SH
1329 */
1330 protected function get_category_id($idnumber) {
1331 global $DB;
8aa3aa3d
SH
1332 try {
1333 return $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1334 } catch (dml_missing_record_exception $ex) {
1335 throw new ExpectationException(sprintf("There is no category in the database with the idnumber '%s'", $idnumber));
1336 }
5dc361e1
SH
1337 }
1338
1339 /**
1340 * Returns the id of the course with the given idnumber.
8aa3aa3d
SH
1341 *
1342 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1343 *
5dc361e1
SH
1344 * @param string $idnumber
1345 * @return string
8aa3aa3d 1346 * @throws ExpectationException
5dc361e1
SH
1347 */
1348 protected function get_course_id($idnumber) {
1349 global $DB;
8aa3aa3d
SH
1350 try {
1351 return $DB->get_field('course', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1352 } catch (dml_missing_record_exception $ex) {
1353 throw new ExpectationException(sprintf("There is no course in the database with the idnumber '%s'", $idnumber));
1354 }
5dc361e1
SH
1355 }
1356
1357 /**
1358 * Returns the category node from within the listing on the management page.
1359 *
1360 * @param string $idnumber
1361 * @return \Behat\Mink\Element\NodeElement
1362 */
1363 protected function get_management_category_listing_node_by_idnumber($idnumber) {
1364 $id = $this->get_category_id($idnumber);
1365 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id);
1366 return $this->find('css', $selector);
1367 }
1368
1369 /**
8aa3aa3d
SH
1370 * Returns a category node from within the management interface.
1371 *
1372 * @param string $name The name of the category.
b155a170 1373 * @param bool $link If set to true we'll resolve to the link rather than just the node.
5dc361e1
SH
1374 * @return \Behat\Mink\Element\NodeElement
1375 */
b155a170
SH
1376 protected function get_management_category_listing_node_by_name($name, $link = false) {
1377 $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']";
1378 if ($link === false) {
3ea677e3 1379 $selector .= "/ancestor::li[@data-id][1]";
b155a170 1380 }
5dc361e1
SH
1381 return $this->find('xpath', $selector);
1382 }
1383
1384 /**
8aa3aa3d
SH
1385 * Returns a course node from within the management interface.
1386 *
1387 * @param string $name The name of the course.
b155a170 1388 * @param bool $link If set to true we'll resolve to the link rather than just the node.
5dc361e1
SH
1389 * @return \Behat\Mink\Element\NodeElement
1390 */
b155a170
SH
1391 protected function get_management_course_listing_node_by_name($name, $link = false) {
1392 $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']";
1393 if ($link === false) {
1394 $selector .= "/ancestor::li[@data-id]";
1395 }
5dc361e1
SH
1396 return $this->find('xpath', $selector);
1397 }
1398
1399 /**
1400 * Returns the course node from within the listing on the management page.
1401 *
1402 * @param string $idnumber
1403 * @return \Behat\Mink\Element\NodeElement
1404 */
1405 protected function get_management_course_listing_node_by_idnumber($idnumber) {
1406 $id = $this->get_course_id($idnumber);
1407 $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id);
1408 return $this->find('css', $selector);
1409 }
1410
1411 /**
8aa3aa3d 1412 * Clicks on a category in the management interface.
5dc361e1 1413 *
75ddb695 1414 * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
5dc361e1 1415 * @param string $name
5dc361e1 1416 */
8aa3aa3d 1417 public function i_click_on_category_in_the_management_interface($name) {
b155a170
SH
1418 $node = $this->get_management_category_listing_node_by_name($name, true);
1419 $node->click();
5dc361e1
SH
1420 }
1421
1422 /**
8aa3aa3d 1423 * Clicks on a course in the management interface.
5dc361e1 1424 *
75ddb695 1425 * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
8aa3aa3d
SH
1426 * @param string $name
1427 */
1428 public function i_click_on_course_in_the_management_interface($name) {
b155a170
SH
1429 $node = $this->get_management_course_listing_node_by_name($name, true);
1430 $node->click();
8aa3aa3d
SH
1431 }
1432
3b732cd6 1433 /**
3ea677e3 1434 * Clicks on a category checkbox in the management interface, if not checked.
3b732cd6 1435 *
75ddb695 1436 * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1437 * @param string $name
1438 */
1439 public function i_select_category_in_the_management_interface($name) {
1440 $node = $this->get_management_category_listing_node_by_name($name);
3ea677e3
RT
1441 $node = $node->findField('bcat[]');
1442 if (!$node->isChecked()) {
1443 $node->click();
1444 }
3b732cd6
RT
1445 }
1446
1447 /**
3ea677e3
RT
1448 * Clicks on a category checkbox in the management interface, if checked.
1449 *
75ddb695 1450 * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3ea677e3
RT
1451 * @param string $name
1452 */
1453 public function i_unselect_category_in_the_management_interface($name) {
1454 $node = $this->get_management_category_listing_node_by_name($name);
1455 $node = $node->findField('bcat[]');
1456 if ($node->isChecked()) {
1457 $node->click();
1458 }
1459 }
1460
1461 /**
1462 * Clicks course checkbox in the management interface, if not checked.
3b732cd6 1463 *
75ddb695 1464 * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1465 * @param string $name
1466 */
1467 public function i_select_course_in_the_management_interface($name) {
1468 $node = $this->get_management_course_listing_node_by_name($name);
3ea677e3
RT
1469 $node = $node->findField('bc[]');
1470 if (!$node->isChecked()) {
1471 $node->click();
1472 }
1473 }
1474
1475 /**
1476 * Clicks course checkbox in the management interface, if checked.
1477 *
75ddb695 1478 * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3ea677e3
RT
1479 * @param string $name
1480 */
1481 public function i_unselect_course_in_the_management_interface($name) {
1482 $node = $this->get_management_course_listing_node_by_name($name);
1483 $node = $node->findField('bc[]');
1484 if ($node->isChecked()) {
1485 $node->click();
1486 }
3b732cd6
RT
1487 }
1488
1489 /**
1490 * Move selected categories to top level in the management interface.
1491 *
75ddb695 1492 * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/
3ea677e3 1493 * @param string $name
3b732cd6 1494 */
3ea677e3
RT
1495 public function i_move_category_to_top_level_in_the_management_interface($name) {
1496 $this->i_select_category_in_the_management_interface($name);
eb9ca848
RT
1497
1498 $this->execute('behat_forms::i_set_the_field_to',
442f12f8 1499 array('menumovecategoriesto', core_course_category::get(0)->get_formatted_name())
3b732cd6 1500 );
eb9ca848
RT
1501
1502 // Save event.
1503 $this->execute("behat_forms::press_button", "bulkmovecategories");
3b732cd6
RT
1504 }
1505
1506 /**
1507 * Checks that a category is a subcategory of specific category.
1508 *
75ddb695 1509 * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1510 * @throws ExpectationException
1511 * @param string $subcatidnumber
1512 * @param string $catidnumber
1513 */
1514 public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1515 $categorynodeid = $this->get_category_id($catidnumber);
1516 $subcategoryid = $this->get_category_id($subcatidnumber);
1517 $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession());
1518 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid);
1519 $this->find('css', $selector, $exception);
1520 }
1521
1522 /**
1523 * Checks that a category is not a subcategory of specific category.
1524 *
75ddb695 1525 * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1526 * @throws ExpectationException
1527 * @param string $subcatidnumber
1528 * @param string $catidnumber
1529 */
1530 public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1531 try {
1532 $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber);
1533 } catch (ExpectationException $e) {
1534 // ExpectedException means that it is not highlighted.
1535 return;
1536 }
1537 throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession());
1538 }
1539
8aa3aa3d
SH
1540 /**
1541 * Click to expand a category revealing its sub categories within the management UI.
1542 *
75ddb695 1543 * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/
5dc361e1
SH
1544 * @param string $idnumber
1545 */
8aa3aa3d 1546 public function i_click_to_expand_category_in_the_management_interface($idnumber) {
5dc361e1
SH
1547 $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber);
1548 $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession());
1549 $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode);
1550 $togglenode->click();
1551 }
1552
1553 /**
8aa3aa3d 1554 * Checks that a category within the management interface is visible.
5dc361e1 1555 *
75ddb695 1556 * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1557 * @param string $idnumber
1558 */
1559 public function category_in_management_listing_should_be_visible($idnumber) {
1560 $id = $this->get_category_id($idnumber);
1561 $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession());
1562 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id);
1563 $this->find('css', $selector, $exception);
1564 }
1565
1566 /**
8aa3aa3d 1567 * Checks that a category within the management interface is dimmed.
5dc361e1 1568 *
75ddb695 1569 * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1570 * @param string $idnumber
1571 */
1572 public function category_in_management_listing_should_be_dimmed($idnumber) {
1573 $id = $this->get_category_id($idnumber);
1574 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id);
1575 $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession());
1576 $this->find('css', $selector, $exception);
1577 }
1578
1579 /**
8aa3aa3d 1580 * Checks that a course within the management interface is visible.
5dc361e1 1581 *
75ddb695 1582 * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1583 * @param string $idnumber
1584 */
1585 public function course_in_management_listing_should_be_visible($idnumber) {
1586 $id = $this->get_course_id($idnumber);
1587 $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession());
1588 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id);
1589 $this->find('css', $selector, $exception);
1590 }
1591
1592 /**
8aa3aa3d 1593 * Checks that a course within the management interface is dimmed.
5dc361e1 1594 *
75ddb695 1595 * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1596 * @param string $idnumber
1597 */
1598 public function course_in_management_listing_should_be_dimmed($idnumber) {
1599 $id = $this->get_course_id($idnumber);
1600 $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession());
1601 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id);
1602 $this->find('css', $selector, $exception);
1603 }
1604
1605 /**
1606 * Toggles the visibility of a course in the management UI.
1607 *
1608 * If it was visible it will be hidden. If it is hidden it will be made visible.
1609 *
75ddb695 1610 * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
5dc361e1
SH
1611 * @param string $idnumber
1612 */
1613 public function i_toggle_visibility_of_course_in_management_listing($idnumber) {
1614 $id = $this->get_course_id($idnumber);
1615 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id);
1616 $node = $this->find('css', $selector);
1617 $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1618 if ($node->getAttribute('data-visible') === '1') {
1619 $toggle = $this->find('css', '.action-hide', $exception, $node);
1620 } else {
1621 $toggle = $this->find('css', '.action-show', $exception, $node);
1622 }
1623 $toggle->click();
1624 }
1625
1626 /**
1627 * Toggles the visibility of a category in the management UI.
1628 *
1629 * If it was visible it will be hidden. If it is hidden it will be made visible.
1630 *
75ddb695 1631 * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
5dc361e1
SH
1632 */
1633 public function i_toggle_visibility_of_category_in_management_listing($idnumber) {
1634 $id = $this->get_category_id($idnumber);
1635 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id);
1636 $node = $this->find('css', $selector);
1637 $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1638 if ($node->getAttribute('data-visible') === '1') {
1639 $toggle = $this->find('css', '.action-hide', $exception, $node);
1640 } else {
1641 $toggle = $this->find('css', '.action-show', $exception, $node);
1642 }
1643 $toggle->click();
1644 }
1645
1646 /**
8aa3aa3d
SH
1647 * Moves a category displayed in the management interface up or down one place.
1648 *
75ddb695 1649 * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
8aa3aa3d
SH
1650 *
1651 * @param string $idnumber The category idnumber
1652 * @param string $direction The direction to move in, either up or down
5dc361e1 1653 */
8aa3aa3d
SH
1654 public function i_click_to_move_category_by_one($idnumber, $direction) {
1655 $node = $this->get_management_category_listing_node_by_idnumber($idnumber);
1656 $this->user_moves_listing_by_one('category', $node, $direction);
1657 }
1658
1659 /**
1660 * Moves a course displayed in the management interface up or down one place.
1661 *
75ddb695 1662 * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
8aa3aa3d
SH
1663 *
1664 * @param string $idnumber The course idnumber
1665 * @param string $direction The direction to move in, either up or down
1666 */
1667 public function i_click_to_move_course_by_one($idnumber, $direction) {
1668 $node = $this->get_management_course_listing_node_by_idnumber($idnumber);
1669 $this->user_moves_listing_by_one('course', $node, $direction);
1670 }
1671
1672 /**
1673 * Moves a course or category listing within the management interface up or down by one.
1674 *
1675 * @param string $listingtype One of course or category
1676 * @param \Behat\Mink\Element\NodeElement $listingnode
1677 * @param string $direction One of up or down.
1678 * @param bool $highlight If set to false we don't check the node has been highlighted.
1679 */
1680 protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) {
1681 $up = (strtolower($direction) === 'up');
5dc361e1 1682 if ($up) {
8aa3aa3d
SH
1683 $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession());
1684 $button = $this->find('css', 'a.action-moveup', $exception, $listingnode);
5dc361e1 1685 } else {
8aa3aa3d
SH
1686 $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession());
1687 $button = $this->find('css', 'a.action-movedown', $exception, $listingnode);
5dc361e1
SH
1688 }
1689 $button->click();
8aa3aa3d
SH
1690 if ($this->running_javascript() && $highlight) {
1691 $listitem = $listingnode->getParent();
5dc361e1
SH
1692 $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession());
1693 $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true);
1694 }
1695 }
1696
1697 /**
8aa3aa3d
SH
1698 * Used by spin to determine the callback has been highlighted.
1699 *
1700 * @param behat_course $self A self reference (default first arg from a spin callback)
1701 * @param \Behat\Mink\Element\NodeElement $selector
1702 * @return bool
5dc361e1
SH
1703 */
1704 protected function listing_is_highlighted($self, $selector) {
1705 $listitem = $this->find('css', $selector);
1706 return $listitem->hasClass('highlight');
1707 }
1708
1709 /**
8aa3aa3d 1710 * Check that one course appears before another in the course category management listings.
5dc361e1 1711 *
75ddb695 1712 * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/
8aa3aa3d
SH
1713 *
1714 * @param string $preceedingcourse The first course to find
1715 * @param string $followingcourse The second course to find (should be AFTER the first course)
1716 * @throws ExpectationException
1717 */
1718 public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) {
1719 $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']";
1720 $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course";
1721 if (!$this->getSession()->getDriver()->find($xpath)) {
1722 throw new ExpectationException($msg, $this->getSession());
1723 }
1724 }
1725
1726 /**
1727 * Check that one category appears before another in the course category management listings.
1728 *
75ddb695 1729 * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/
8aa3aa3d
SH
1730 *
1731 * @param string $preceedingcategory The first category to find
1732 * @param string $followingcategory The second category to find (should be after the first category)
1733 * @throws ExpectationException
5dc361e1 1734 */
8aa3aa3d
SH
1735 public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) {
1736 $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']";
1737 $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category";
5dc361e1
SH
1738 if (!$this->getSession()->getDriver()->find($xpath)) {
1739 throw new ExpectationException($msg, $this->getSession());
1740 }
1741 }
1742
1743 /**
8aa3aa3d 1744 * Checks that we are on the course management page that we expect to be on and that no course has been selected.
5dc361e1 1745 *
75ddb695 1746 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/
8aa3aa3d 1747 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
5dc361e1 1748 */
8aa3aa3d 1749 public function i_should_see_the_courses_management_page($mode) {
eb9ca848
RT
1750 $this->execute("behat_general::assert_element_contains_text",
1751 array("Course and category management", "h2", "css_element")
5dc361e1 1752 );
eb9ca848 1753
5dc361e1
SH
1754 switch ($mode) {
1755 case "Courses":
eb9ca848
RT
1756 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1757 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1 1758 break;
eb9ca848 1759
5dc361e1 1760 case "Course categories":
eb9ca848
RT
1761 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1762 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1 1763 break;
eb9ca848 1764
5dc361e1
SH
1765 case "Courses categories and courses":
1766 default:
eb9ca848
RT
1767 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1768 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1
SH
1769 break;
1770 }
eb9ca848
RT
1771
1772 $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element"));
5dc361e1
SH
1773 }
1774
1775 /**
8aa3aa3d
SH
1776 * Checks that we are on the course management page that we expect to be on and that a course has been selected.
1777 *
75ddb695 1778 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/
8aa3aa3d 1779 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
5dc361e1 1780 */
8aa3aa3d 1781 public function i_should_see_the_courses_management_page_with_a_course_selected($mode) {
eb9ca848
RT
1782 $this->execute("behat_general::assert_element_contains_text",
1783 array("Course and category management", "h2", "css_element"));
1784
1785 switch ($mode) {
1786 case "Courses":
1787 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1788 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1789 break;
1790
1791 case "Course categories":
1792 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1793 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1794 break;
1795
1796 case "Courses categories and courses":
1797 default:
1798 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1799 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1800 break;
1801 }
1802
1803 $this->execute("behat_general::should_exist", array("#course-detail", "css_element"));
8aa3aa3d
SH
1804 }
1805
1806 /**
1807 * Locates a course in the course category management interface and then triggers an action for it.
1808 *
75ddb695 1809 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/
8aa3aa3d
SH
1810 *
1811 * @param string $action The action to take. One of
1812 * @param string $name The name of the course as it is displayed in the management interface.
1813 */
1814 public function i_click_on_action_for_item_in_management_course_listing($action, $name) {
1815 $node = $this->get_management_course_listing_node_by_name($name);
1816 $this->user_clicks_on_management_listing_action('course', $node, $action);
1817 }
1818
1819 /**
1820 * Locates a category in the course category management interface and then triggers an action for it.
1821 *
75ddb695 1822 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/
8aa3aa3d
SH
1823 *
1824 * @param string $action The action to take. One of
1825 * @param string $name The name of the category as it is displayed in the management interface.
1826 */
1827 public function i_click_on_action_for_item_in_management_category_listing($action, $name) {
1828 $node = $this->get_management_category_listing_node_by_name($name);
1829 $this->user_clicks_on_management_listing_action('category', $node, $action);
1830 }
1831
32fcea74
DM
1832 /**
1833 * Clicks to expand or collapse a category displayed on the frontpage
1834 *
1835 * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/
1836 * @throws ExpectationException
1837 * @param string $categoryname
1838 */
1839 public function i_toggle_category_children_visibility_in_frontpage($categoryname) {
1840
1841 $headingtags = array();
1842 for ($i = 1; $i <= 6; $i++) {
1843 $headingtags[] = 'self::h' . $i;
1844 }
1845
1846 $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
921faad9 1847 $categoryliteral = behat_context_helper::escape($categoryname);
32fcea74
DM
1848 $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
1849 $node = $this->find('xpath', $xpath, $exception);
1850 $node->click();
1851
1852 // Smooth expansion.
3cf0d01a 1853 $this->getSession()->wait(1000);
32fcea74
DM
1854 }
1855
8aa3aa3d
SH
1856 /**
1857 * Finds the node to use for a management listitem action and clicks it.
1858 *
1859 * @param string $listingtype Either course or category.
1860 * @param \Behat\Mink\Element\NodeElement $listingnode
1861 * @param string $action The action being taken
1862 * @throws Behat\Mink\Exception\ExpectationException
1863 */
1864 protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
e3652936
MM
1865 $actionsnode = $listingnode->find('xpath', "//*" .
1866 "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
5dc361e1 1867 if (!$actionsnode) {
8aa3aa3d 1868 throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
5dc361e1
SH
1869 }
1870 $actionnode = $actionsnode->find('css', '.action-'.$action);
5dc361e1
SH
1871 if (!$actionnode) {
1872 throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
1873 }
cda49969 1874 if ($this->running_javascript() && !$actionnode->isVisible()) {
e3652936 1875 $actionsnode->find('css', 'a[data-toggle=dropdown]')->click();
cda49969
SH
1876 $actionnode = $actionsnode->find('css', '.action-'.$action);
1877 }
5dc361e1
SH
1878 $actionnode->click();
1879 }
d0647301
SH
1880
1881 /**
1882 * Clicks on a category in the management interface.
1883 *
75ddb695 1884 * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/
d0647301
SH
1885 * @param string $name The name of the category to click.
1886 */
1887 public function i_click_on_category_in_the_management_category_listing($name) {
1888 $node = $this->get_management_category_listing_node_by_name($name);
1889 $node->find('css', 'a.categoryname')->click();
1890 }
ebcff7e2
MG
1891
1892 /**
1893 * Go to the course participants
1894 *
1895 * @Given /^I navigate to course participants$/
1896 */
1897 public function i_navigate_to_course_participants() {
e3652936 1898 $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('participants'));
ebcff7e2 1899 }
a487a3ed
DK
1900
1901 /**
1902 * Check that one teacher appears before another in the course contacts.
1903 *
1904 * @Given /^I should see teacher "(?P<pteacher_string>(?:[^"]|\\")*)" before "(?P<fteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1905 *
1906 * @param string $pteacher The first teacher to find
1907 * @param string $fteacher The second teacher to find (should be after the first teacher)
1908 *
1909 * @throws ExpectationException
1910 */
1911 public function i_should_see_teacher_before($pteacher, $fteacher) {
1912 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1913 $msg = "Teacher {$pteacher} does not appear before Teacher {$fteacher}";
1914 if (!$this->getSession()->getDriver()->find($xpath)) {
1915 throw new ExpectationException($msg, $this->getSession());
1916 }
1917 }
1918
1919 /**
1920 * Check that one teacher oes not appears after another in the course contacts.
1921 *
1922 * @Given /^I should not see teacher "(?P<fteacher_string>(?:[^"]|\\")*)" after "(?P<pteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1923 *
1924 * @param string $fteacher The teacher that should not be found (after the other teacher)
1925 * @param string $pteacher The teacher after who the other should not be found (this teacher must be found!)
1926 *
1927 * @throws ExpectationException
1928 */
1929 public function i_should_not_see_teacher_after($fteacher, $pteacher) {
1930 $xpathliteral = behat_context_helper::escape($pteacher);
1931 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
1932 "[count(descendant::*[contains(., $xpathliteral)]) = 0]";
1933 try {
1934 $nodes = $this->find_all('xpath', $xpath);
1935 } catch (ElementNotFoundException $e) {
1936 throw new ExpectationException('"' . $pteacher . '" text was not found in the page', $this->getSession());
1937 }
1938 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1939 $msg = "Teacher {$fteacher} appears after Teacher {$pteacher}";
1940 if ($this->getSession()->getDriver()->find($xpath)) {
1941 throw new ExpectationException($msg, $this->getSession());
1942 }
1943 }
a1990e50 1944}