MDL-58428 theme: Shift templates ready for Bootstrapbase removal
[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',
309 array(get_string('markthistopic'), "link", $this->escape($xpath), "xpath_element")
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',
331 array(get_string('markedthistopic'), "link", $this->escape($xpath), "xpath_element")
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()) {
d1e55a47
DM
351 $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
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()) {
d1e55a47
DM
385 $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
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.
8857c715 449 $this->execute('behat_general::should_exist_in_the', ['This topic is highlighted as the current topic', 'icon', $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
8857c715 780 $activitynode = $this->get_activity_element('Move', 'icon', $activityname);
0e575f01
DM
781
782 // JS enabled.
783 if ($this->running_javascript()) {
784
38976081 785 $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
0e575f01 786
eb9ca848
RT
787 $this->execute("behat_general::i_drag_and_i_drop_it_in",
788 array($this->escape($activitynode->getXpath()), "xpath_element",
789 $this->escape($destinationxpath), "xpath_element")
0e575f01
DM
790 );
791
792 } else {
793 // Following links with no-JS.
794
795 // Moving to the fist spot of the section (before all other section's activities).
eb9ca848
RT
796 $this->execute('behat_course::i_click_on_in_the_activity',
797 array("a.editing_move", "css_element", $this->escape($activityname))
798 );
799
800 $this->execute('behat_general::i_click_on_in_the',
801 array("li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element")
0e575f01
DM
802 );
803 }
804 }
805
806 /**
807 * Edits the activity name through the edit activity; this step only works with Javascript enabled. Editing mode should be on.
808 *
809 * @Given /^I change "(?P<activity_name_string>(?:[^"]|\\")*)" activity name to "(?P<new_name_string>(?:[^"]|\\")*)"$/
810 * @throws DriverException Step not available when Javascript is disabled
811 * @param string $activityname
812 * @param string $newactivityname
813 */
814 public function i_change_activity_name_to($activityname, $newactivityname) {
815
816 if (!$this->running_javascript()) {
817 throw new DriverException('Change activity name step is not available with Javascript disabled');
818 }
819
e5de4933 820 $activity = $this->escape($activityname);
eb9ca848
RT
821
822 $this->execute('behat_course::i_click_on_in_the_activity',
823 array(get_string('edittitle'), "link", $activity)
0e575f01 824 );
eb9ca848
RT
825
826 // Adding chr(10) to save changes.
827 $this->execute('behat_forms::i_set_the_field_to',
828 array('title', $this->escape($newactivityname) . chr(10))
829 );
830
0e575f01
DM
831 }
832
2a9275c4
DM
833 /**
834 * Opens an activity actions menu if it is not already opened.
835 *
836 * @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
837 * @throws DriverException The step is not available when Javascript is disabled
838 * @param string $activityname
2a9275c4
DM
839 */
840 public function i_open_actions_menu($activityname) {
841
842 if (!$this->running_javascript()) {
843 throw new DriverException('Activities actions menu not available when Javascript is disabled');
844 }
845
846 // If it is already opened we do nothing.
847 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
848
849 // Find the menu.
850 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
851 if (!$menunode) {
852 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
853 $this->getSession());
854 }
855 $expanded = $menunode->getAttribute('aria-expanded');
856 if ($expanded == 'true') {
2a9275c4
DM
857 return;
858 }
859
eb9ca848 860 $this->execute('behat_course::i_click_on_in_the_activity',
e3652936 861 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
eb9ca848
RT
862 );
863
e3652936 864 $this->actions_menu_should_be_open($activityname);
2a9275c4
DM
865 }
866
d1e55a47
DM
867 /**
868 * Closes an activity actions menu if it is not already closed.
869 *
870 * @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
871 * @throws DriverException The step is not available when Javascript is disabled
872 * @param string $activityname
d1e55a47
DM
873 */
874 public function i_close_actions_menu($activityname) {
875
876 if (!$this->running_javascript()) {
877 throw new DriverException('Activities actions menu not available when Javascript is disabled');
878 }
879
880 // If it is already closed we do nothing.
881 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
882 // Find the menu.
883 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
884 if (!$menunode) {
885 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
886 $this->getSession());
887 }
888 $expanded = $menunode->getAttribute('aria-expanded');
889 if ($expanded != 'true') {
d1e55a47
DM
890 return;
891 }
892
eb9ca848 893 $this->execute('behat_course::i_click_on_in_the_activity',
e3652936 894 array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
eb9ca848 895 );
d1e55a47
DM
896 }
897
af4d19ab
AN
898 /**
899 * Checks that the specified activity's action menu is open.
900 *
901 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/
902 * @throws DriverException The step is not available when Javascript is disabled
903 * @param string $activityname
904 */
905 public function actions_menu_should_be_open($activityname) {
906
907 if (!$this->running_javascript()) {
908 throw new DriverException('Activities actions menu not available when Javascript is disabled');
909 }
910
af4d19ab 911 $activitynode = $this->get_activity_node($activityname);
e3652936
MM
912 // Find the menu.
913 $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
914 if (!$menunode) {
915 throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
916 $this->getSession());
917 }
918 $expanded = $menunode->getAttribute('aria-expanded');
919 if ($expanded != 'true') {
af4d19ab
AN
920 throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
921 }
af4d19ab
AN
922 }
923
3f950346
MG
924 /**
925 * Checks that the specified activity's action menu contains an item.
926 *
927 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
928 * @throws DriverException The step is not available when Javascript is disabled
929 * @param string $activityname
930 * @param string $menuitem
931 */
932 public function actions_menu_should_have_item($activityname, $menuitem) {
933 $activitynode = $this->get_activity_node($activityname);
934
935 $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
936 $menuitem . '" item', $this->getSession());
937 $this->find('named_partial', array('link', $menuitem), $notfoundexception, $activitynode);
938 }
939
940 /**
941 * Checks that the specified activity's action menu does not contains an item.
942 *
943 * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
944 * @throws DriverException The step is not available when Javascript is disabled
945 * @param string $activityname
946 * @param string $menuitem
947 */
948 public function actions_menu_should_not_have_item($activityname, $menuitem) {
949 $activitynode = $this->get_activity_node($activityname);
950
951 try {
952 $this->find('named_partial', array('link', $menuitem), false, $activitynode);
953 throw new ExpectationException('"' . $activityname . '" has a "' . $menuitem .
954 '" item when it should not', $this->getSession());
955 } catch (ElementNotFoundException $e) {
956 // This is good, the menu item should not be there.
957 }
958 }
959
0e575f01
DM
960 /**
961 * Indents to the right the activity or resource specified by it's name. Editing mode should be on.
962 *
963 * @Given /^I indent right "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
964 * @param string $activityname
965 */
966 public function i_indent_right_activity($activityname) {
967
e5de4933
SH
968 $activity = $this->escape($activityname);
969 if ($this->running_javascript()) {
eb9ca848 970 $this->i_open_actions_menu($activity);
e5de4933 971 }
0e575f01 972
eb9ca848
RT
973 $this->execute('behat_course::i_click_on_in_the_activity',
974 array(get_string('moveright'), "link", $this->escape($activity))
975 );
976
0e575f01
DM
977 }
978
979 /**
980 * Indents to the left the activity or resource specified by it's name. Editing mode should be on.
981 *
982 * @Given /^I indent left "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
983 * @param string $activityname
984 */
985 public function i_indent_left_activity($activityname) {
986
e5de4933
SH
987 $activity = $this->escape($activityname);
988 if ($this->running_javascript()) {
eb9ca848 989 $this->i_open_actions_menu($activity);
e5de4933 990 }
0e575f01 991
eb9ca848
RT
992 $this->execute('behat_course::i_click_on_in_the_activity',
993 array(get_string('moveleft'), "link", $this->escape($activity))
994 );
0e575f01
DM
995
996 }
997
998 /**
7daab401 999 * 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
1000 *
1001 * @Given /^I delete "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1002 * @param string $activityname
1003 */
1004 public function i_delete_activity($activityname) {
84a1e50a
RL
1005 $steps = array();
1006 $activity = $this->escape($activityname);
1007 if ($this->running_javascript()) {
eb9ca848 1008 $this->i_open_actions_menu($activity);
84a1e50a 1009 }
eb9ca848
RT
1010
1011 $this->execute('behat_course::i_click_on_in_the_activity',
1012 array(get_string('delete'), "link", $this->escape($activity))
1013 );
fbd904ef 1014
0e575f01
DM
1015 // JS enabled.
1016 // Not using chain steps here because the exceptions catcher have problems detecting
1017 // JS modal windows and avoiding interacting them at the same time.
1018 if ($this->running_javascript()) {
eb9ca848
RT
1019 $this->execute('behat_general::i_click_on_in_the',
1020 array(get_string('yes'), "button", "Confirm", "dialogue")
1021 );
0e575f01 1022 } else {
eb9ca848 1023 $this->execute("behat_forms::press_button", get_string('yes'));
0e575f01 1024 }
fbd904ef
DM
1025
1026 return $steps;
0e575f01
DM
1027 }
1028
1029 /**
1030 * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on.
1031 *
1032 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
1033 * @param string $activityname
1034 */
1035 public function i_duplicate_activity($activityname) {
e5de4933
SH
1036 $steps = array();
1037 $activity = $this->escape($activityname);
1038 if ($this->running_javascript()) {
eb9ca848 1039 $this->i_open_actions_menu($activity);
e5de4933 1040 }
eb9ca848
RT
1041 $this->execute('behat_course::i_click_on_in_the_activity',
1042 array(get_string('duplicate'), "link", $activity)
1043 );
1044
0e575f01
DM
1045 }
1046
1047 /**
1048 * 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.
1049 *
1050 * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/
1051 * @param string $activityname
1052 * @param TableNode $data
1053 */
1054 public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
047a8800 1055
e5de4933 1056 $activity = $this->escape($activityname);
921faad9 1057 $activityliteral = behat_context_helper::escape($activityname);
047a8800 1058
eb9ca848 1059 $this->execute("behat_course::i_duplicate_activity", $activity);
047a8800 1060
60df6787
S
1061 // Determine the future new activity xpath from the former one.
1062 $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
e3652936
MM
1063 "[contains(., $activityliteral)]/following-sibling::li";
1064 $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']";
60df6787
S
1065
1066 if ($this->running_javascript()) {
d1e55a47 1067 // We wait until the AJAX request finishes and the section is visible again.
60df6787 1068 $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
e3652936
MM
1069 "[contains(., $activityliteral)]" .
1070 "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
1071 "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
60df6787 1072
eb9ca848 1073 $this->execute("behat_general::wait_until_exists",
e3652936 1074 array($this->escape($hiddenlightboxxpath), "xpath_element")
eb9ca848 1075 );
d1e55a47
DM
1076
1077 // Close the original activity actions menu.
eb9ca848 1078 $this->i_close_actions_menu($activity);
d1e55a47 1079
047a8800
DM
1080 // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
1081 // this point, it don't even exists in the DOM (the steps are executed when we return them).
eb9ca848 1082 $this->execute('behat_general::i_click_on',
e3652936 1083 array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
eb9ca848 1084 );
e5de4933 1085 }
60df6787
S
1086
1087 // We force the xpath as otherwise mink tries to interact with the former one.
eb9ca848 1088 $this->execute('behat_general::i_click_on_in_the',
e3652936 1089 array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
eb9ca848
RT
1090 );
1091
1092 $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
1093 $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse'));
60df6787 1094
0e575f01
DM
1095 }
1096
d1e55a47
DM
1097 /**
1098 * 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.
1099 *
1100 * Using the protected method as this method will be usually
1101 * called by other methods which are not returning a set of
1102 * steps and performs the actions directly, so it would not
1103 * be executed if it returns another step.
1104 *
1105 * Hopefully we would not require test writers to use this step
1106 * and we will manage it from other step definitions.
1107 *
1108 * @Given /^I wait until section "(?P<section_number>\d+)" is available$/
1109 * @param int $sectionnumber
1110 * @return void
1111 */
1112 public function i_wait_until_section_is_available($sectionnumber) {
1113
1114 // Looks for a hidden lightbox or a non-existent lightbox in that section.
1115 $sectionxpath = $this->section_exists($sectionnumber);
1116 $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
1117 " | " .
1118 $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
1119
1120 $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element');
1121 }
1122
0e575f01
DM
1123 /**
1124 * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
1125 *
75ddb695 1126 * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/
0e575f01
DM
1127 * @param string $element
1128 * @param string $selectortype
1129 * @param string $activityname
1130 */
1131 public function i_click_on_in_the_activity($element, $selectortype, $activityname) {
1132 $element = $this->get_activity_element($element, $selectortype, $activityname);
1133 $element->click();
1134 }
1135
1136 /**
1137 * Clicks on the specified element inside the activity container.
1138 *
1139 * @throws ElementNotFoundException
1140 * @param string $element
1141 * @param string $selectortype
1142 * @param string $activityname
1143 * @return NodeElement
1144 */
1145 protected function get_activity_element($element, $selectortype, $activityname) {
1146 $activitynode = $this->get_activity_node($activityname);
1147
1148 // Transforming to Behat selector/locator.
1149 list($selector, $locator) = $this->transform_selector($selectortype, $element);
1150 $exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
1151
1152 return $this->find($selector, $locator, $exception, $activitynode);
1153 }
1154
18c84063
DM
1155 /**
1156 * Checks if the course section exists.
1157 *
1158 * @throws ElementNotFoundException Thrown by behat_base::find
1159 * @param int $sectionnumber
b918f9f7 1160 * @return string The xpath of the section.
18c84063
DM
1161 */
1162 protected function section_exists($sectionnumber) {
1163
1164 // Just to give more info in case it does not exist.
1165 $xpath = "//li[@id='section-" . $sectionnumber . "']";
1166 $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber ");
1167 $this->find('xpath', $xpath, $exception);
1168
1169 return $xpath;
1170 }
b918f9f7
DM
1171
1172 /**
1173 * Returns the show section icon or throws an exception.
1174 *
1175 * @throws ElementNotFoundException Thrown by behat_base::find
1176 * @param int $sectionnumber
1177 * @return NodeElement
1178 */
8857c715 1179 protected function show_section_link_exists($sectionnumber) {
b918f9f7
DM
1180
1181 // Gets the section xpath and ensure it exists.
1182 $xpath = $this->section_exists($sectionnumber);
1183
1184 // We need to know the course format as the text strings depends on them.
1185 $courseformat = $this->get_course_format();
1186
1187 // Checking the show button alt text and show icon.
8857c715
DW
1188 $showtext = get_string('showfromothers', $courseformat);
1189 $linkxpath = $xpath . "/descendant::a[@title=" . behat_context_helper::escape($showtext) . "]";
b918f9f7 1190
8857c715 1191 $exception = new ElementNotFoundException($this->getSession(), 'Show section link ');
0e575f01
DM
1192
1193 // Returing the link so both Non-JS and JS browsers can interact with it.
1194 return $this->find('xpath', $linkxpath, $exception);
b918f9f7
DM
1195 }
1196
1197 /**
1198 * Returns the hide section icon link if it exists or throws exception.
1199 *
1200 * @throws ElementNotFoundException Thrown by behat_base::find
1201 * @param int $sectionnumber
1202 * @return NodeElement
1203 */
8857c715 1204 protected function hide_section_link_exists($sectionnumber) {
b918f9f7
DM
1205
1206 // Gets the section xpath and ensure it exists.
1207 $xpath = $this->section_exists($sectionnumber);
1208
1209 // We need to know the course format as the text strings depends on them.
1210 $courseformat = $this->get_course_format();
1211
1212 // Checking the hide button alt text and hide icon.
921faad9 1213 $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat));
38976081 1214 $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
b918f9f7
DM
1215
1216 $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon ');
8857c715 1217 $this->find('icon', 'Hide', $exception);
0e575f01
DM
1218
1219 // Returing the link so both Non-JS and JS browsers can interact with it.
1220 return $this->find('xpath', $linkxpath, $exception);
b918f9f7
DM
1221 }
1222
1223 /**
1224 * Gets the current course format.
1225 *
1226 * @throws ExpectationException If we are not in the course view page.
1227 * @return string The course format in a frankenstyled name.
1228 */
1229 protected function get_course_format() {
1230
1231 $exception = new ExpectationException('You are not in a course page', $this->getSession());
1232
1233 // The moodle body's id attribute contains the course format.
1234 $node = $this->getSession()->getPage()->find('css', 'body');
1235 if (!$node) {
1236 throw $exception;
1237 }
1238
1239 if (!$bodyid = $node->getAttribute('id')) {
1240 throw $exception;
1241 }
1242
1243 if (strstr($bodyid, 'page-course-view-') === false) {
1244 throw $exception;
1245 }
1246
1247 return 'format_' . str_replace('page-course-view-', '', $bodyid);
1248 }
1249
1250 /**
1251 * Gets the section's activites DOM nodes.
1252 *
1253 * @param string $sectionxpath
1254 * @return array NodeElement instances
1255 */
1256 protected function get_section_activities($sectionxpath) {
1257
38976081 1258 $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]";
b918f9f7
DM
1259
1260 // We spin here, as activities usually require a lot of time to load.
1261 try {
1262 $activities = $this->find_all('xpath', $xpath);
1263 } catch (ElementNotFoundException $e) {
1264 return false;
1265 }
1266
1267 return $activities;
1268 }
1269
bf648567
DM
1270 /**
1271 * Returns the DOM node of the activity from <li>.
1272 *
1273 * @throws ElementNotFoundException Thrown by behat_base::find
1274 * @param string $activityname The activity name
1275 * @return NodeElement
1276 */
1277 protected function get_activity_node($activityname) {
1278
921faad9 1279 $activityname = behat_context_helper::escape($activityname);
38976081 1280 $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]";
bf648567
DM
1281
1282 return $this->find('xpath', $xpath);
1283 }
1284
d1e55a47
DM
1285 /**
1286 * Gets the activity instance name from the activity node.
1287 *
1288 * @throws ElementNotFoundException
1289 * @param NodeElement $activitynode
1290 * @return string
1291 */
1292 protected function get_activity_name($activitynode) {
1293 $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode);
1294 return $instancenamenode->getText();
1295 }
1296
b918f9f7
DM
1297 /**
1298 * Returns whether the user can edit the course contents or not.
1299 *
1300 * @return bool
1301 */
1302 protected function is_course_editor() {
1303
1304 // We don't need to behat_base::spin() here as all is already loaded.
e3652936
MM
1305 if (!$this->getSession()->getPage()->findLink(get_string('turneditingoff')) &&
1306 !$this->getSession()->getPage()->findLink(get_string('turneditingon'))) {
b918f9f7
DM
1307 return false;
1308 }
1309
1310 return true;
1311 }
1312
3f950346
MG
1313 /**
1314 * Returns whether the user can edit the course contents and the editing mode is on.
1315 *
1316 * @return bool
1317 */
1318 protected function is_editing_on() {
1319 return $this->getSession()->getPage()->findButton(get_string('turneditingoff')) ? true : false;
1320 }
1321
5dc361e1
SH
1322 /**
1323 * Returns the id of the category with the given idnumber.
8aa3aa3d
SH
1324 *
1325 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1326 *
5dc361e1
SH
1327 * @param string $idnumber
1328 * @return string
8aa3aa3d 1329 * @throws ExpectationException
5dc361e1
SH
1330 */
1331 protected function get_category_id($idnumber) {
1332 global $DB;
8aa3aa3d
SH
1333 try {
1334 return $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1335 } catch (dml_missing_record_exception $ex) {
1336 throw new ExpectationException(sprintf("There is no category in the database with the idnumber '%s'", $idnumber));
1337 }
5dc361e1
SH
1338 }
1339
1340 /**
1341 * Returns the id of the course with the given idnumber.
8aa3aa3d
SH
1342 *
1343 * Please note that this function requires the category to exist. If it does not exist an ExpectationException is thrown.
1344 *
5dc361e1
SH
1345 * @param string $idnumber
1346 * @return string
8aa3aa3d 1347 * @throws ExpectationException
5dc361e1
SH
1348 */
1349 protected function get_course_id($idnumber) {
1350 global $DB;
8aa3aa3d
SH
1351 try {
1352 return $DB->get_field('course', 'id', array('idnumber' => $idnumber), MUST_EXIST);
1353 } catch (dml_missing_record_exception $ex) {
1354 throw new ExpectationException(sprintf("There is no course in the database with the idnumber '%s'", $idnumber));
1355 }
5dc361e1
SH
1356 }
1357
1358 /**
1359 * Returns the category node from within the listing on the management page.
1360 *
1361 * @param string $idnumber
1362 * @return \Behat\Mink\Element\NodeElement
1363 */
1364 protected function get_management_category_listing_node_by_idnumber($idnumber) {
1365 $id = $this->get_category_id($idnumber);
1366 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id);
1367 return $this->find('css', $selector);
1368 }
1369
1370 /**
8aa3aa3d
SH
1371 * Returns a category node from within the management interface.
1372 *
1373 * @param string $name The name of the category.
b155a170 1374 * @param bool $link If set to true we'll resolve to the link rather than just the node.
5dc361e1
SH
1375 * @return \Behat\Mink\Element\NodeElement
1376 */
b155a170
SH
1377 protected function get_management_category_listing_node_by_name($name, $link = false) {
1378 $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']";
1379 if ($link === false) {
3ea677e3 1380 $selector .= "/ancestor::li[@data-id][1]";
b155a170 1381 }
5dc361e1
SH
1382 return $this->find('xpath', $selector);
1383 }
1384
1385 /**
8aa3aa3d
SH
1386 * Returns a course node from within the management interface.
1387 *
1388 * @param string $name The name of the course.
b155a170 1389 * @param bool $link If set to true we'll resolve to the link rather than just the node.
5dc361e1
SH
1390 * @return \Behat\Mink\Element\NodeElement
1391 */
b155a170
SH
1392 protected function get_management_course_listing_node_by_name($name, $link = false) {
1393 $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']";
1394 if ($link === false) {
1395 $selector .= "/ancestor::li[@data-id]";
1396 }
5dc361e1
SH
1397 return $this->find('xpath', $selector);
1398 }
1399
1400 /**
1401 * Returns the course node from within the listing on the management page.
1402 *
1403 * @param string $idnumber
1404 * @return \Behat\Mink\Element\NodeElement
1405 */
1406 protected function get_management_course_listing_node_by_idnumber($idnumber) {
1407 $id = $this->get_course_id($idnumber);
1408 $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id);
1409 return $this->find('css', $selector);
1410 }
1411
1412 /**
8aa3aa3d 1413 * Clicks on a category in the management interface.
5dc361e1 1414 *
75ddb695 1415 * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
5dc361e1 1416 * @param string $name
5dc361e1 1417 */
8aa3aa3d 1418 public function i_click_on_category_in_the_management_interface($name) {
b155a170
SH
1419 $node = $this->get_management_category_listing_node_by_name($name, true);
1420 $node->click();
5dc361e1
SH
1421 }
1422
1423 /**
8aa3aa3d 1424 * Clicks on a course in the management interface.
5dc361e1 1425 *
75ddb695 1426 * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
8aa3aa3d
SH
1427 * @param string $name
1428 */
1429 public function i_click_on_course_in_the_management_interface($name) {
b155a170
SH
1430 $node = $this->get_management_course_listing_node_by_name($name, true);
1431 $node->click();
8aa3aa3d
SH
1432 }
1433
3b732cd6 1434 /**
3ea677e3 1435 * Clicks on a category checkbox in the management interface, if not checked.
3b732cd6 1436 *
75ddb695 1437 * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1438 * @param string $name
1439 */
1440 public function i_select_category_in_the_management_interface($name) {
1441 $node = $this->get_management_category_listing_node_by_name($name);
3ea677e3
RT
1442 $node = $node->findField('bcat[]');
1443 if (!$node->isChecked()) {
1444 $node->click();
1445 }
3b732cd6
RT
1446 }
1447
1448 /**
3ea677e3
RT
1449 * Clicks on a category checkbox in the management interface, if checked.
1450 *
75ddb695 1451 * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3ea677e3
RT
1452 * @param string $name
1453 */
1454 public function i_unselect_category_in_the_management_interface($name) {
1455 $node = $this->get_management_category_listing_node_by_name($name);
1456 $node = $node->findField('bcat[]');
1457 if ($node->isChecked()) {
1458 $node->click();
1459 }
1460 }
1461
1462 /**
1463 * Clicks course checkbox in the management interface, if not checked.
3b732cd6 1464 *
75ddb695 1465 * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1466 * @param string $name
1467 */
1468 public function i_select_course_in_the_management_interface($name) {
1469 $node = $this->get_management_course_listing_node_by_name($name);
3ea677e3
RT
1470 $node = $node->findField('bc[]');
1471 if (!$node->isChecked()) {
1472 $node->click();
1473 }
1474 }
1475
1476 /**
1477 * Clicks course checkbox in the management interface, if checked.
1478 *
75ddb695 1479 * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/
3ea677e3
RT
1480 * @param string $name
1481 */
1482 public function i_unselect_course_in_the_management_interface($name) {
1483 $node = $this->get_management_course_listing_node_by_name($name);
1484 $node = $node->findField('bc[]');
1485 if ($node->isChecked()) {
1486 $node->click();
1487 }
3b732cd6
RT
1488 }
1489
1490 /**
1491 * Move selected categories to top level in the management interface.
1492 *
75ddb695 1493 * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/
3ea677e3 1494 * @param string $name
3b732cd6 1495 */
3ea677e3
RT
1496 public function i_move_category_to_top_level_in_the_management_interface($name) {
1497 $this->i_select_category_in_the_management_interface($name);
eb9ca848
RT
1498
1499 $this->execute('behat_forms::i_set_the_field_to',
442f12f8 1500 array('menumovecategoriesto', core_course_category::get(0)->get_formatted_name())
3b732cd6 1501 );
eb9ca848
RT
1502
1503 // Save event.
1504 $this->execute("behat_forms::press_button", "bulkmovecategories");
3b732cd6
RT
1505 }
1506
1507 /**
1508 * Checks that a category is a subcategory of specific category.
1509 *
75ddb695 1510 * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1511 * @throws ExpectationException
1512 * @param string $subcatidnumber
1513 * @param string $catidnumber
1514 */
1515 public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1516 $categorynodeid = $this->get_category_id($catidnumber);
1517 $subcategoryid = $this->get_category_id($subcatidnumber);
1518 $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession());
1519 $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid);
1520 $this->find('css', $selector, $exception);
1521 }
1522
1523 /**
1524 * Checks that a category is not a subcategory of specific category.
1525 *
75ddb695 1526 * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/
3b732cd6
RT
1527 * @throws ExpectationException
1528 * @param string $subcatidnumber
1529 * @param string $catidnumber
1530 */
1531 public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
1532 try {
1533 $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber);
1534 } catch (ExpectationException $e) {
1535 // ExpectedException means that it is not highlighted.
1536 return;
1537 }
1538 throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession());
1539 }
1540
8aa3aa3d
SH
1541 /**
1542 * Click to expand a category revealing its sub categories within the management UI.
1543 *
75ddb695 1544 * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/
5dc361e1
SH
1545 * @param string $idnumber
1546 */
8aa3aa3d 1547 public function i_click_to_expand_category_in_the_management_interface($idnumber) {
5dc361e1
SH
1548 $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber);
1549 $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession());
1550 $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode);
1551 $togglenode->click();
1552 }
1553
1554 /**
8aa3aa3d 1555 * Checks that a category within the management interface is visible.
5dc361e1 1556 *
75ddb695 1557 * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1558 * @param string $idnumber
1559 */
1560 public function category_in_management_listing_should_be_visible($idnumber) {
1561 $id = $this->get_category_id($idnumber);
1562 $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession());
1563 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id);
1564 $this->find('css', $selector, $exception);
1565 }
1566
1567 /**
8aa3aa3d 1568 * Checks that a category within the management interface is dimmed.
5dc361e1 1569 *
75ddb695 1570 * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1571 * @param string $idnumber
1572 */
1573 public function category_in_management_listing_should_be_dimmed($idnumber) {
1574 $id = $this->get_category_id($idnumber);
1575 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id);
1576 $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession());
1577 $this->find('css', $selector, $exception);
1578 }
1579
1580 /**
8aa3aa3d 1581 * Checks that a course within the management interface is visible.
5dc361e1 1582 *
75ddb695 1583 * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1584 * @param string $idnumber
1585 */
1586 public function course_in_management_listing_should_be_visible($idnumber) {
1587 $id = $this->get_course_id($idnumber);
1588 $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession());
1589 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id);
1590 $this->find('css', $selector, $exception);
1591 }
1592
1593 /**
8aa3aa3d 1594 * Checks that a course within the management interface is dimmed.
5dc361e1 1595 *
75ddb695 1596 * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/
5dc361e1
SH
1597 * @param string $idnumber
1598 */
1599 public function course_in_management_listing_should_be_dimmed($idnumber) {
1600 $id = $this->get_course_id($idnumber);
1601 $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession());
1602 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id);
1603 $this->find('css', $selector, $exception);
1604 }
1605
1606 /**
1607 * Toggles the visibility of a course in the management UI.
1608 *
1609 * If it was visible it will be hidden. If it is hidden it will be made visible.
1610 *
75ddb695 1611 * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
5dc361e1
SH
1612 * @param string $idnumber
1613 */
1614 public function i_toggle_visibility_of_course_in_management_listing($idnumber) {
1615 $id = $this->get_course_id($idnumber);
1616 $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id);
1617 $node = $this->find('css', $selector);
1618 $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1619 if ($node->getAttribute('data-visible') === '1') {
1620 $toggle = $this->find('css', '.action-hide', $exception, $node);
1621 } else {
1622 $toggle = $this->find('css', '.action-show', $exception, $node);
1623 }
1624 $toggle->click();
1625 }
1626
1627 /**
1628 * Toggles the visibility of a category in the management UI.
1629 *
1630 * If it was visible it will be hidden. If it is hidden it will be made visible.
1631 *
75ddb695 1632 * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/
5dc361e1
SH
1633 */
1634 public function i_toggle_visibility_of_category_in_management_listing($idnumber) {
1635 $id = $this->get_category_id($idnumber);
1636 $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id);
1637 $node = $this->find('css', $selector);
1638 $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession());
1639 if ($node->getAttribute('data-visible') === '1') {
1640 $toggle = $this->find('css', '.action-hide', $exception, $node);
1641 } else {
1642 $toggle = $this->find('css', '.action-show', $exception, $node);
1643 }
1644 $toggle->click();
1645 }
1646
1647 /**
8aa3aa3d
SH
1648 * Moves a category displayed in the management interface up or down one place.
1649 *
75ddb695 1650 * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
8aa3aa3d
SH
1651 *
1652 * @param string $idnumber The category idnumber
1653 * @param string $direction The direction to move in, either up or down
5dc361e1 1654 */
8aa3aa3d
SH
1655 public function i_click_to_move_category_by_one($idnumber, $direction) {
1656 $node = $this->get_management_category_listing_node_by_idnumber($idnumber);
1657 $this->user_moves_listing_by_one('category', $node, $direction);
1658 }
1659
1660 /**
1661 * Moves a course displayed in the management interface up or down one place.
1662 *
75ddb695 1663 * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/
8aa3aa3d
SH
1664 *
1665 * @param string $idnumber The course idnumber
1666 * @param string $direction The direction to move in, either up or down
1667 */
1668 public function i_click_to_move_course_by_one($idnumber, $direction) {
1669 $node = $this->get_management_course_listing_node_by_idnumber($idnumber);
1670 $this->user_moves_listing_by_one('course', $node, $direction);
1671 }
1672
1673 /**
1674 * Moves a course or category listing within the management interface up or down by one.
1675 *
1676 * @param string $listingtype One of course or category
1677 * @param \Behat\Mink\Element\NodeElement $listingnode
1678 * @param string $direction One of up or down.
1679 * @param bool $highlight If set to false we don't check the node has been highlighted.
1680 */
1681 protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) {
1682 $up = (strtolower($direction) === 'up');
5dc361e1 1683 if ($up) {
8aa3aa3d
SH
1684 $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession());
1685 $button = $this->find('css', 'a.action-moveup', $exception, $listingnode);
5dc361e1 1686 } else {
8aa3aa3d
SH
1687 $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession());
1688 $button = $this->find('css', 'a.action-movedown', $exception, $listingnode);
5dc361e1
SH
1689 }
1690 $button->click();
8aa3aa3d
SH
1691 if ($this->running_javascript() && $highlight) {
1692 $listitem = $listingnode->getParent();
5dc361e1
SH
1693 $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession());
1694 $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true);
1695 }
1696 }
1697
1698 /**
8aa3aa3d
SH
1699 * Used by spin to determine the callback has been highlighted.
1700 *
1701 * @param behat_course $self A self reference (default first arg from a spin callback)
1702 * @param \Behat\Mink\Element\NodeElement $selector
1703 * @return bool
5dc361e1
SH
1704 */
1705 protected function listing_is_highlighted($self, $selector) {
1706 $listitem = $this->find('css', $selector);
1707 return $listitem->hasClass('highlight');
1708 }
1709
1710 /**
8aa3aa3d 1711 * Check that one course appears before another in the course category management listings.
5dc361e1 1712 *
75ddb695 1713 * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/
8aa3aa3d
SH
1714 *
1715 * @param string $preceedingcourse The first course to find
1716 * @param string $followingcourse The second course to find (should be AFTER the first course)
1717 * @throws ExpectationException
1718 */
1719 public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) {
1720 $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']";
1721 $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course";
1722 if (!$this->getSession()->getDriver()->find($xpath)) {
1723 throw new ExpectationException($msg, $this->getSession());
1724 }
1725 }
1726
1727 /**
1728 * Check that one category appears before another in the course category management listings.
1729 *
75ddb695 1730 * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/
8aa3aa3d
SH
1731 *
1732 * @param string $preceedingcategory The first category to find
1733 * @param string $followingcategory The second category to find (should be after the first category)
1734 * @throws ExpectationException
5dc361e1 1735 */
8aa3aa3d
SH
1736 public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) {
1737 $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']";
1738 $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category";
5dc361e1
SH
1739 if (!$this->getSession()->getDriver()->find($xpath)) {
1740 throw new ExpectationException($msg, $this->getSession());
1741 }
1742 }
1743
1744 /**
8aa3aa3d 1745 * Checks that we are on the course management page that we expect to be on and that no course has been selected.
5dc361e1 1746 *
75ddb695 1747 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/
8aa3aa3d 1748 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
5dc361e1 1749 */
8aa3aa3d 1750 public function i_should_see_the_courses_management_page($mode) {
eb9ca848
RT
1751 $this->execute("behat_general::assert_element_contains_text",
1752 array("Course and category management", "h2", "css_element")
5dc361e1 1753 );
eb9ca848 1754
5dc361e1
SH
1755 switch ($mode) {
1756 case "Courses":
eb9ca848
RT
1757 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1758 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1 1759 break;
eb9ca848 1760
5dc361e1 1761 case "Course categories":
eb9ca848
RT
1762 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1763 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1 1764 break;
eb9ca848 1765
5dc361e1
SH
1766 case "Courses categories and courses":
1767 default:
eb9ca848
RT
1768 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1769 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
5dc361e1
SH
1770 break;
1771 }
eb9ca848
RT
1772
1773 $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element"));
5dc361e1
SH
1774 }
1775
1776 /**
8aa3aa3d
SH
1777 * Checks that we are on the course management page that we expect to be on and that a course has been selected.
1778 *
75ddb695 1779 * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/
8aa3aa3d 1780 * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses'
5dc361e1 1781 */
8aa3aa3d 1782 public function i_should_see_the_courses_management_page_with_a_course_selected($mode) {
eb9ca848
RT
1783 $this->execute("behat_general::assert_element_contains_text",
1784 array("Course and category management", "h2", "css_element"));
1785
1786 switch ($mode) {
1787 case "Courses":
1788 $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element"));
1789 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1790 break;
1791
1792 case "Course categories":
1793 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1794 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1795 break;
1796
1797 case "Courses categories and courses":
1798 default:
1799 $this->execute("behat_general::should_exist", array("#category-listing", "css_element"));
1800 $this->execute("behat_general::should_exist", array("#course-listing", "css_element"));
1801 break;
1802 }
1803
1804 $this->execute("behat_general::should_exist", array("#course-detail", "css_element"));
8aa3aa3d
SH
1805 }
1806
1807 /**
1808 * Locates a course in the course category management interface and then triggers an action for it.
1809 *
75ddb695 1810 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/
8aa3aa3d
SH
1811 *
1812 * @param string $action The action to take. One of
1813 * @param string $name The name of the course as it is displayed in the management interface.
1814 */
1815 public function i_click_on_action_for_item_in_management_course_listing($action, $name) {
1816 $node = $this->get_management_course_listing_node_by_name($name);
1817 $this->user_clicks_on_management_listing_action('course', $node, $action);
1818 }
1819
1820 /**
1821 * Locates a category in the course category management interface and then triggers an action for it.
1822 *
75ddb695 1823 * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/
8aa3aa3d
SH
1824 *
1825 * @param string $action The action to take. One of
1826 * @param string $name The name of the category as it is displayed in the management interface.
1827 */
1828 public function i_click_on_action_for_item_in_management_category_listing($action, $name) {
1829 $node = $this->get_management_category_listing_node_by_name($name);
1830 $this->user_clicks_on_management_listing_action('category', $node, $action);
1831 }
1832
32fcea74
DM
1833 /**
1834 * Clicks to expand or collapse a category displayed on the frontpage
1835 *
1836 * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/
1837 * @throws ExpectationException
1838 * @param string $categoryname
1839 */
1840 public function i_toggle_category_children_visibility_in_frontpage($categoryname) {
1841
1842 $headingtags = array();
1843 for ($i = 1; $i <= 6; $i++) {
1844 $headingtags[] = 'self::h' . $i;
1845 }
1846
1847 $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
921faad9 1848 $categoryliteral = behat_context_helper::escape($categoryname);
32fcea74
DM
1849 $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
1850 $node = $this->find('xpath', $xpath, $exception);
1851 $node->click();
1852
1853 // Smooth expansion.
3cf0d01a 1854 $this->getSession()->wait(1000);
32fcea74
DM
1855 }
1856
8aa3aa3d
SH
1857 /**
1858 * Finds the node to use for a management listitem action and clicks it.
1859 *
1860 * @param string $listingtype Either course or category.
1861 * @param \Behat\Mink\Element\NodeElement $listingnode
1862 * @param string $action The action being taken
1863 * @throws Behat\Mink\Exception\ExpectationException
1864 */
1865 protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
e3652936
MM
1866 $actionsnode = $listingnode->find('xpath', "//*" .
1867 "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
5dc361e1 1868 if (!$actionsnode) {
8aa3aa3d 1869 throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
5dc361e1
SH
1870 }
1871 $actionnode = $actionsnode->find('css', '.action-'.$action);
5dc361e1
SH
1872 if (!$actionnode) {
1873 throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
1874 }
cda49969 1875 if ($this->running_javascript() && !$actionnode->isVisible()) {
e3652936 1876 $actionsnode->find('css', 'a[data-toggle=dropdown]')->click();
cda49969
SH
1877 $actionnode = $actionsnode->find('css', '.action-'.$action);
1878 }
5dc361e1
SH
1879 $actionnode->click();
1880 }
d0647301
SH
1881
1882 /**
1883 * Clicks on a category in the management interface.
1884 *
75ddb695 1885 * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/
d0647301
SH
1886 * @param string $name The name of the category to click.
1887 */
1888 public function i_click_on_category_in_the_management_category_listing($name) {
1889 $node = $this->get_management_category_listing_node_by_name($name);
1890 $node->find('css', 'a.categoryname')->click();
1891 }
ebcff7e2
MG
1892
1893 /**
1894 * Go to the course participants
1895 *
1896 * @Given /^I navigate to course participants$/
1897 */
1898 public function i_navigate_to_course_participants() {
e3652936 1899 $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('participants'));
ebcff7e2 1900 }
a487a3ed
DK
1901
1902 /**
1903 * Check that one teacher appears before another in the course contacts.
1904 *
1905 * @Given /^I should see teacher "(?P<pteacher_string>(?:[^"]|\\")*)" before "(?P<fteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1906 *
1907 * @param string $pteacher The first teacher to find
1908 * @param string $fteacher The second teacher to find (should be after the first teacher)
1909 *
1910 * @throws ExpectationException
1911 */
1912 public function i_should_see_teacher_before($pteacher, $fteacher) {
1913 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1914 $msg = "Teacher {$pteacher} does not appear before Teacher {$fteacher}";
1915 if (!$this->getSession()->getDriver()->find($xpath)) {
1916 throw new ExpectationException($msg, $this->getSession());
1917 }
1918 }
1919
1920 /**
1921 * Check that one teacher oes not appears after another in the course contacts.
1922 *
1923 * @Given /^I should not see teacher "(?P<fteacher_string>(?:[^"]|\\")*)" after "(?P<pteacher_string>(?:[^"]|\\")*)" in the course contact listing$/
1924 *
1925 * @param string $fteacher The teacher that should not be found (after the other teacher)
1926 * @param string $pteacher The teacher after who the other should not be found (this teacher must be found!)
1927 *
1928 * @throws ExpectationException
1929 */
1930 public function i_should_not_see_teacher_after($fteacher, $pteacher) {
1931 $xpathliteral = behat_context_helper::escape($pteacher);
1932 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
1933 "[count(descendant::*[contains(., $xpathliteral)]) = 0]";
1934 try {
1935 $nodes = $this->find_all('xpath', $xpath);
1936 } catch (ElementNotFoundException $e) {
1937 throw new ExpectationException('"' . $pteacher . '" text was not found in the page', $this->getSession());
1938 }
1939 $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']";
1940 $msg = "Teacher {$fteacher} appears after Teacher {$pteacher}";
1941 if ($this->getSession()->getDriver()->find($xpath)) {
1942 throw new ExpectationException($msg, $this->getSession());
1943 }
1944 }
a1990e50 1945}