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