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