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