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