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