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