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