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