require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
if ($options['profile']) {
$profile = $options['profile'];
- if (!isset($CFG->behat_config[$profile])) {
- echo "Invalid profile passed: " . $profile;
+ if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile])) {
+ echo "Invalid profile passed: " . $profile . PHP_EOL;
exit(1);
}
$extraopts[] = '--profile="' . $profile . '"';
$time = round(microtime(true) - $time, 1);
echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
+ksort($exitcodes);
// Print exit info from each run.
-$status = false;
+// Status bits contains pass/fail status of parallel runs.
+$status = 0;
+$processcounter = 0;
foreach ($exitcodes as $exitcode) {
- $status = (bool)$status || (bool)$exitcode;
+ if ($exitcode) {
+ $status |= (1 << $processcounter);
+ }
+ $processcounter++;
}
// Run finished. Show exit code and output from individual process.
$verbose = empty($options['verbose']) ? false : true;
-$verbose = $verbose || $status;
+$verbose = $verbose || !empty($status);
// Show exit code from each process, if any process failed.
if ($verbose) {
// Echo exit codes.
echo "Exit codes for each behat run: " . PHP_EOL;
- ksort($exitcodes);
foreach ($exitcodes as $run => $exitcode) {
echo $run . ": " . $exitcode . PHP_EOL;
}
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
-exit((int) $status);
+exit($status);
/**
* Signal handler for terminal exit.
// This is only displayed once for parallel install.
if (empty($options['run'])) {
+ // Notify user that 2.5 profile has been converted to 3.5.
+ if (behat_config_manager::$autoprofileconversion) {
+ mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
+ }
+
$runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
When I set the field "First name" to "Field value"
And I set the field "Select a country" to "Japan"
And I set the field "Unmask" to "1"
- And I expand all fieldsets
Then the field "First name" matches value "Field value"
And the "Select a country" select box should contain "Japan"
And the field "Unmask" matches value "1"
// YAML decides when is is necessary to wrap strings between single quotes, so not controlled
// values like paths should not be asserted including the key name as they would depend on the
// directories values.
- $this->assertContains($CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'features', $contents);
+ $this->assertContains($CFG->dirroot, $contents);
// Not quoted strings.
$this->assertContains('micarro: /me/lo/robaron', $contents);
- $this->assertContains('class: behat_init_context', $contents);
// YAML uses single quotes to wrap URL strings.
$this->assertContains("base_url: '" . $CFG->behat_wwwroot . "'", $contents);
Scenario: Install language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Available language packs" to "English - Pirate (en_ar)"
+ When I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
Then I should see "Language pack 'en_ar' was successfully installed"
- And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was installed."
And I log out
Scenario: Try to uninstall language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- And I set the field "Available language packs" to "English - Pirate (en_ar)"
+ And I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
- When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+ When I set the field "Installed language packs" to "en_ar"
And I press "Uninstall selected language pack(s)"
And I press "Continue"
Then I should see "Language pack 'en_ar' was uninstalled"
- And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
- And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should not contain "en_ar"
+ And the "Available language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was removed."
And I should see "Language pack uninstalled"
Scenario: Try to uninstall English language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Installed language packs" to "English (en)"
+ When I set the field "Installed language packs" to "en"
And I press "Uninstall selected language pack(s)"
Then I should see "The English language pack cannot be uninstalled."
And I navigate to "Live logs" node in "Site administration > Reports"
--- /dev/null
+@auth @auth_manual
+Feature: Test manual authentication works.
+ In order to check manual authentication
+ As a teacher
+ I need to go on login page and enter username and password.
+
+ Background:
+ Given the following "users" exist:
+ | username |
+ | teacher1 |
+
+ @javascript
+ Scenario: Check login works with javascript.
+ Given I am on homepage
+ And I expand navigation bar
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
+
+ Scenario: Check login works without javascript.
+ Given I am on homepage
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
-use Behat\Behat\Context\Step\When as When;
+use Moodle\BehatExtension\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\When as When;
/**
* Log in log out steps definitions.
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
*/
public function i_log_in_as($username) {
+ // Visit login page.
+ $this->getSession()->visit($this->locate_path('login/index.php'));
- // Running this step using the API rather than a chained step because
- // we need to see if the 'Log in' link is available or we need to click
- // the dropdown to expand the navigation bar before.
- $this->getSession()->visit($this->locate_path('/'));
+ // Enter username and password.
+ $behatforms = behat_context_helper::get('behat_forms');
+ $behatforms->i_set_the_field_to('Username', $this->escape($username));
+ $behatforms->i_set_the_field_to('Password', $this->escape($username));
- // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
- $steps = array(
- new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
- new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
- new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
- new Given('I press "' . get_string('login') . '"')
- );
-
- // If Javascript is disabled we have enough with these steps.
- if (!$this->running_javascript()) {
- return $steps;
- }
-
- // Wait for the homepage to be ready.
- $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-
- // If it is needed, it expands the navigation bar with the 'Log in' link.
- if ($clicknavbar = $this->get_expand_navbar_step()) {
- array_unshift($steps, $clicknavbar);
- }
-
- return $steps;
+ // Press log in button.
+ $behatforms->press_button(get_string('login'));
}
/**
return $steps;
}
-
- /**
- * Returns a step to open the navigation bar if it is needed.
- *
- * The top log in and log out links are hidden when middle or small
- * size windows (or devices) are used. This step returns a step definition
- * clicking to expand the navbar if it is hidden.
- *
- * @return Given|bool A step definition or false if there is no need to show the navbar.
- */
- protected function get_expand_navbar_step() {
-
- // Checking if we need to click the navbar button to show the navigation menu, it
- // is hidden by default when using clean theme and a medium or small screen size.
-
- // The DOM and the JS should be all ready and loaded. Running without spinning
- // as this is a widely used step and we can not spend time here trying to see
- // a DOM node that is not always there (at the moment clean is not even the
- // default theme...).
- $navbuttonjs = "return (
- Y.one('.btn-navbar') &&
- Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
- )";
-
- // Adding an extra click we need to show the 'Log in' link.
- if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
- return false;
- }
-
- return new Given('I click on ".btn-navbar" "css_element"');
- }
}
"/descendant::div[@class='restore-course-search']" .
"/descendant::tr[contains(., $existingcourse)]" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::div[@class='restore-course-search']" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
// Merge without deleting radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='1']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
// Delete contents radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='0']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
return;
}
- $pageoptions = clone $options;
-
$rows = $options->getRows();
$newrows = array();
foreach ($rows as $k => $data) {
$newrows[] = $data;
}
}
- $pageoptions->setRows($newrows);
+ $pageoptions = new TableNode($newrows);
+
return $pageoptions;
}
default:
- paths:
- features: lib/behat/features
- bootstrap: lib/behat/features/bootstrap
- context:
- class: behat_init_context
+ suites:
+ default:
+ paths: { }
+ contexts: { }
extensions:
- Behat\MinkExtension\Extension:
+ Behat\MinkExtension:
base_url: 'http://localhost:8000'
goutte: null
selenium2: null
- Moodle\BehatExtension\Extension:
- features: { }
+ Moodle\BehatExtension:
+ moodledirroot: /Should/Change/To/Moodle/www/dir
steps_definitions: { }
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Blocks management steps definitions.
// NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
use Behat\Gherkin\Node\TableNode as TableNode;
/**
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Steps definitions for cohort actions.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given,
- Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+ Moodle\BehatExtension\Context\Step\Then,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
"require-dev": {
"phpunit/phpunit": "4.8.*",
"phpunit/dbUnit": "1.4.*",
- "moodlehq/behat-extension": "1.31.0"
+ "moodlehq/behat-extension": "3.31.0"
}
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "a957f7332dd1d221be96fb356cb9f03d",
- "content-hash": "463a75022982e6a64bd9fc0513d9b44c",
+ "hash": "769fa23c4b31f60c9fb82d5b23171e0f",
+ "content-hash": "5fca4c69d043cb1f985fc08cd82a64f8",
"packages": [],
"packages-dev": [
{
"name": "behat/behat",
- "version": "v2.5.5",
+ "version": "v3.0.15",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120"
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
"shasum": ""
},
"require": {
- "behat/gherkin": "~2.3.0",
- "php": ">=5.3.1",
+ "behat/gherkin": "~4.3",
+ "behat/transliterator": "~1.0",
+ "ext-mbstring": "*",
+ "php": ">=5.3.3",
+ "symfony/class-loader": "~2.1",
"symfony/config": "~2.3",
- "symfony/console": "~2.0",
- "symfony/dependency-injection": "~2.0",
- "symfony/event-dispatcher": "~2.0",
- "symfony/finder": "~2.0",
+ "symfony/console": "~2.1",
+ "symfony/dependency-injection": "~2.1",
+ "symfony/event-dispatcher": "~2.1",
"symfony/translation": "~2.3",
- "symfony/yaml": "~2.0"
+ "symfony/yaml": "~2.1"
},
"require-dev": {
- "phpunit/phpunit": "~3.7.19"
+ "phpspec/prophecy-phpunit": "~1.0",
+ "phpunit/phpunit": "~4.0",
+ "symfony/process": "~2.1"
},
"suggest": {
"behat/mink-extension": "for integration with Mink testing framework",
"bin/behat"
],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
- "Behat\\Behat": "src/"
+ "Behat\\Behat": "src/",
+ "Behat\\Testwork": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"description": "Scenario-oriented BDD framework for PHP 5.3",
"homepage": "http://behat.org/",
"keywords": [
+ "Agile",
"BDD",
- "Behat",
- "Symfony2"
+ "ScenarioBDD",
+ "Scrum",
+ "StoryBDD",
+ "User story",
+ "business",
+ "development",
+ "documentation",
+ "examples",
+ "symfony",
+ "testing"
],
- "time": "2015-06-01 09:37:55"
+ "time": "2015-02-22 14:10:33"
},
{
"name": "behat/gherkin",
- "version": "v2.3.5",
+ "version": "v4.4.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852"
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
"shasum": ""
},
"require": {
- "php": ">=5.3.1",
- "symfony/finder": "~2.0"
+ "php": ">=5.3.1"
},
"require-dev": {
- "symfony/config": "~2.0",
- "symfony/translation": "~2.0",
- "symfony/yaml": "~2.0"
+ "phpunit/phpunit": "~4.0",
+ "symfony/yaml": "~2.1"
},
"suggest": {
- "symfony/config": "If you want to use Config component to manage resources",
- "symfony/translation": "If you want to use Symfony2 translations adapter",
"symfony/yaml": "If you want to parse features, represented in YAML files"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "2.2-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
"keywords": [
"BDD",
"Behat",
+ "Cucumber",
"DSL",
- "Symfony2",
+ "gherkin",
"parser"
],
- "time": "2013-10-15 11:22:17"
+ "time": "2015-12-30 14:47:00"
},
{
"name": "behat/mink",
- "version": "v1.5.0",
+ "version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/Mink.git",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+ "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf",
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf",
"shasum": ""
},
"require": {
"php": ">=5.3.1",
- "symfony/css-selector": "~2.0"
+ "symfony/css-selector": "~2.1"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
},
"suggest": {
"behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "1.5.x-dev"
+ "dev-master": "1.7.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink": "src/"
+ "psr-4": {
+ "Behat\\Mink\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "http://everzet.com"
}
],
- "description": "Web acceptance testing framework for PHP 5.3",
+ "description": "Browser controller/emulator abstraction for PHP",
"homepage": "http://mink.behat.org/",
"keywords": [
"browser",
"testing",
"web"
],
- "time": "2013-04-13 23:39:27"
+ "time": "2015-09-20 20:24:03"
},
{
"name": "behat/mink-browserkit-driver",
- "version": "v1.1.0",
+ "version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+ "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/2650f5420e713e3807c7f09a07370a4f48367bf9",
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "php": ">=5.3.1",
- "symfony/browser-kit": "~2.0",
- "symfony/dom-crawler": "~2.0"
+ "behat/mink": "~1.7@dev",
+ "php": ">=5.3.6",
+ "symfony/browser-kit": "~2.3|~3.0",
+ "symfony/dom-crawler": "~2.3|~3.0"
},
"require-dev": {
- "silex/silex": "@dev"
+ "silex/silex": "~1.2",
+ "symfony/phpunit-bridge": "~2.7|~3.0"
},
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"browser",
"testing"
],
- "time": "2013-04-13 23:46:30"
+ "time": "2016-01-19 16:59:07"
},
{
"name": "behat/mink-extension",
- "version": "v1.3.3",
+ "version": "v2.2",
"source": {
"type": "git",
"url": "https://github.com/Behat/MinkExtension.git",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8"
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
+ "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
"shasum": ""
},
"require": {
- "behat/behat": "~2.5.0",
+ "behat/behat": "~3.0,>=3.0.5",
"behat/mink": "~1.5",
"php": ">=5.3.2",
- "symfony/config": "~2.2"
+ "symfony/config": "~2.2|~3.0"
},
"require-dev": {
- "behat/mink-goutte-driver": "~1.0",
- "fabpot/goutte": "~1.0"
+ "behat/mink-goutte-driver": "~1.1",
+ "phpspec/phpspec": "~2.0"
},
"type": "behat-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
"Behat\\MinkExtension": "src/"
"MIT"
],
"authors": [
+ {
+ "name": "Christophe Coevoet",
+ "email": "stof@notk.org"
+ },
{
"name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
+ "email": "ever.zet@gmail.com"
}
],
"description": "Mink extension for Behat",
- "homepage": "http://mink.behat.org",
+ "homepage": "http://extensions.behat.org/mink",
"keywords": [
"browser",
"gui",
"test",
"web"
],
- "time": "2014-05-15 19:27:39"
+ "time": "2016-02-15 07:55:18"
},
{
"name": "behat/mink-goutte-driver",
- "version": "v1.0.9",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkGoutteDriver.git",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8"
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/fa1b073b48761464feb0b05e6825da44b20118d8",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8",
+ "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f",
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f",
"shasum": ""
},
"require": {
- "behat/mink-browserkit-driver": ">=1.0.5,<1.2.0",
- "fabpot/goutte": "~1.0.1",
+ "behat/mink": "~1.6@dev",
+ "behat/mink-browserkit-driver": "~1.2@dev",
+ "fabpot/goutte": "~1.0.4|~2.0|~3.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.2.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"headless",
"testing"
],
- "time": "2013-07-03 18:43:54"
+ "time": "2015-09-21 21:31:11"
},
{
"name": "behat/mink-selenium2-driver",
- "version": "v1.1.1",
+ "version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkSelenium2Driver.git",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476"
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bcf1b537de37db6db0822d9e7bd97e600fd7a476",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476",
+ "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "instaclick/php-webdriver": "~1.0.12",
+ "behat/mink": "~1.7@dev",
+ "instaclick/php-webdriver": "~1.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"testing",
"webdriver"
],
- "time": "2013-06-02 19:09:45"
+ "time": "2015-09-21 21:02:54"
+ },
+ {
+ "name": "behat/transliterator",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Behat/Transliterator.git",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Behat\\Transliterator": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Artistic-1.0"
+ ],
+ "description": "String transliterator",
+ "keywords": [
+ "i18n",
+ "slug",
+ "transliterator"
+ ],
+ "time": "2015-09-28 16:26:35"
},
{
"name": "doctrine/instantiator",
},
{
"name": "fabpot/goutte",
- "version": "v1.0.7",
+ "version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/Goutte.git",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "guzzle/http": "~3.1",
- "php": ">=5.3.0",
+ "guzzlehttp/guzzle": ">=4,<6",
+ "php": ">=5.4.0",
"symfony/browser-kit": "~2.1",
"symfony/css-selector": "~2.1",
- "symfony/dom-crawler": "~2.1",
- "symfony/finder": "~2.1",
- "symfony/process": "~2.1"
- },
- "require-dev": {
- "guzzle/plugin-history": "~3.1",
- "guzzle/plugin-mock": "~3.1"
+ "symfony/dom-crawler": "~2.1"
},
"type": "application",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Goutte": "."
+ "psr-4": {
+ "Goutte\\": "Goutte"
}
},
"notification-url": "https://packagist.org/downloads/",
}
],
"description": "A simple PHP Web Scraper",
- "homepage": "https://github.com/fabpot/Goutte",
+ "homepage": "https://github.com/FriendsOfPHP/Goutte",
"keywords": [
"scraper"
],
- "time": "2014-10-09 15:52:51"
+ "time": "2015-05-05 21:14:57"
},
{
"name": "guzzlehttp/guzzle",
- "version": "v3.8.1",
+ "version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93",
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "php": ">=5.3.3",
- "symfony/event-dispatcher": ">=2.1"
- },
- "replace": {
- "guzzle/batch": "self.version",
- "guzzle/cache": "self.version",
- "guzzle/common": "self.version",
- "guzzle/http": "self.version",
- "guzzle/inflection": "self.version",
- "guzzle/iterator": "self.version",
- "guzzle/log": "self.version",
- "guzzle/parser": "self.version",
- "guzzle/plugin": "self.version",
- "guzzle/plugin-async": "self.version",
- "guzzle/plugin-backoff": "self.version",
- "guzzle/plugin-cache": "self.version",
- "guzzle/plugin-cookie": "self.version",
- "guzzle/plugin-curlauth": "self.version",
- "guzzle/plugin-error-response": "self.version",
- "guzzle/plugin-history": "self.version",
- "guzzle/plugin-log": "self.version",
- "guzzle/plugin-md5": "self.version",
- "guzzle/plugin-mock": "self.version",
- "guzzle/plugin-oauth": "self.version",
- "guzzle/service": "self.version",
- "guzzle/stream": "self.version"
+ "guzzlehttp/ringphp": "^1.1",
+ "php": ">=5.4.0"
},
"require-dev": {
- "doctrine/cache": "*",
- "monolog/monolog": "1.*",
- "phpunit/phpunit": "3.7.*",
- "psr/log": "1.0.*",
- "symfony/class-loader": "*",
- "zendframework/zend-cache": "<2.3",
- "zendframework/zend-log": "<2.3"
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.0",
+ "psr/log": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.8-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Guzzle": "src/",
- "Guzzle\\Tests": "tests/"
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
- },
- {
- "name": "Guzzle Community",
- "homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"rest",
"web service"
],
- "time": "2014-01-28 22:29:15"
+ "time": "2015-05-20 03:47:55"
+ },
+ {
+ "name": "guzzlehttp/ringphp",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/RingPHP.git",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/streams": "~3.0",
+ "php": ">=5.4.0",
+ "react/promise": "~2.0"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "ext-curl": "Guzzle will use specific adapters if cURL is present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Ring\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+ "time": "2015-05-20 03:37:09"
+ },
+ {
+ "name": "guzzlehttp/streams",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/streams.git",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple abstraction over streams of data",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "Guzzle",
+ "stream"
+ ],
+ "time": "2014-10-12 19:18:40"
},
{
"name": "instaclick/php-webdriver",
- "version": "1.0.17",
+ "version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/instaclick/php-webdriver.git",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5"
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/47a6019553a7a5b42d35493276ffc2c9252c53d5",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5",
+ "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.2"
},
- "bin": [
- "bin/webunit"
- ],
+ "require-dev": {
+ "satooshi/php-coveralls": "dev-master"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.4.x-dev"
}
},
"autoload": {
{
"name": "Anthon Pang",
"email": "apang@softwaredevelopment.ca",
- "role": "developer"
+ "role": "Fork Maintainer"
}
],
"description": "PHP WebDriver for Selenium 2",
"webdriver",
"webtest"
],
- "time": "2013-10-04 15:03:51"
+ "time": "2015-06-15 20:19:33"
},
{
"name": "moodlehq/behat-extension",
- "version": "v1.31.0",
+ "version": "v3.31.0",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b"
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
+ "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d985e9da29914b0da90d61c47aadc455586eeee5",
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5",
"shasum": ""
},
"require": {
- "behat/behat": "2.5.5",
- "behat/mink": "1.5.0",
- "behat/mink-extension": "1.3.3",
- "behat/mink-goutte-driver": "1.0.9",
- "behat/mink-selenium2-driver": "1.1.1",
- "guzzlehttp/guzzle": "~3.1",
+ "behat/behat": "3.0.*",
+ "behat/mink": "~1.7",
+ "behat/mink-extension": "~2.1",
+ "behat/mink-goutte-driver": "~1.2",
+ "behat/mink-selenium2-driver": "~1.3",
"php": ">=5.4.4",
- "symfony/browser-kit": "2.7.5",
- "symfony/css-selector": "2.7.5",
- "symfony/dom-crawler": "2.7.5",
- "symfony/filesystem": "2.7.5",
- "symfony/finder": "2.7.5"
+ "symfony/process": "2.8.*"
},
"type": "library",
"autoload": {
"Behat",
"moodle"
],
- "time": "2016-01-05 02:55:24"
+ "time": "2016-03-04 07:15:40"
},
{
"name": "phpdocumentor/reflection-docblock",
},
{
"name": "phpspec/prophecy",
- "version": "v1.5.0",
+ "version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
- "sebastian/comparator": "~1.1"
+ "sebastian/comparator": "~1.1",
+ "sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "1.5.x-dev"
}
},
"autoload": {
"spy",
"stub"
],
- "time": "2015-08-13 10:07:40"
+ "time": "2016-02-15 07:46:21"
},
{
"name": "phpunit/dbunit",
},
{
"name": "phpunit/phpunit",
- "version": "4.8.21",
+ "version": "4.8.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"shasum": ""
},
"require": {
"testing",
"xunit"
],
- "time": "2015-12-12 07:45:58"
+ "time": "2016-02-11 14:56:33"
},
{
"name": "phpunit/phpunit-mock-objects",
],
"time": "2015-10-02 06:51:40"
},
+ {
+ "name": "react/promise",
+ "version": "v2.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "time": "2016-02-26 19:09:02"
+ },
{
"name": "sebastian/comparator",
"version": "1.2.0",
},
{
"name": "sebastian/environment",
- "version": "1.3.3",
+ "version": "1.3.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df"
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"shasum": ""
},
"require": {
"environment",
"hhvm"
],
- "time": "2015-12-02 08:37:27"
+ "time": "2016-02-26 18:40:46"
},
{
"name": "sebastian/exporter",
},
{
"name": "symfony/browser-kit",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
- "symfony/dom-crawler": "~2.0,>=2.0.5"
+ "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
},
"require-dev": {
- "symfony/css-selector": "~2.0,>=2.0.5",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.0,>=2.0.5"
+ "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
+ "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
},
"suggest": {
"symfony/process": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2015-09-06 08:36:38"
+ "time": "2016-01-27 11:34:40"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v2.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-01-30 15:58:35"
},
{
"name": "symfony/config",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2"
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
+ "url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3|~3.0.0"
},
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
"type": "library",
"extra": {
"branch-alias": {
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-22 16:12:45"
},
{
"name": "symfony/console",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
+ "url": "https://api.github.com/repos/symfony/console/zipball/56cc5caf051189720b8de974e4746090aaa10d44",
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44",
"shasum": ""
},
"require": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-12-22 10:25:57"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/css-selector",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970"
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2015-09-22 13:49:29"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/dependency-injection",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd"
+ "reference": "62251761a7615435b22ccf562384c588b431be44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c5086d186f538c2711b9af6f727be7b0446979cd",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62251761a7615435b22ccf562384c588b431be44",
+ "reference": "62251761a7615435b22ccf562384c588b431be44",
"shasum": ""
},
"require": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-28 16:34:46"
},
{
"name": "symfony/dom-crawler",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "2e185ca136399f902b948694987e62c80099c052"
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
- "reference": "2e185ca136399f902b948694987e62c80099c052",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": ">=5.3.9",
+ "symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
- "symfony/css-selector": "~2.3",
- "symfony/phpunit-bridge": "~2.7"
+ "symfony/css-selector": "~2.8|~3.0.0"
},
"suggest": {
"symfony/css-selector": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2015-09-20 21:13:58"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"shasum": ""
},
"require": {
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-10-30 20:15:42"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/filesystem",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2015-09-09 17:42:36"
+ "time": "2016-02-22 15:02:30"
},
{
- "name": "symfony/finder",
- "version": "v2.7.5",
+ "name": "symfony/polyfill-apcu",
+ "version": "v1.1.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+ "url": "https://github.com/symfony/polyfill-apcu.git",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+ "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
- },
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
+ "php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- }
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Finder Component",
+ "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
"homepage": "https://symfony.com",
- "time": "2015-09-19 19:59:23"
+ "keywords": [
+ "apcu",
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.0.1",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
+ "reference": "1289d16209491b584839022f29257ad859b8532d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
+ "reference": "1289d16209491b584839022f29257ad859b8532d",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2015-11-20 09:19:13"
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/process",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c"
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/62c254438b5040bc2217156e1570cf2206e8540c",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c",
+ "url": "https://api.github.com/repos/symfony/process/zipball/7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
"shasum": ""
},
"require": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2015-12-23 11:03:46"
+ "time": "2016-02-02 13:33:15"
},
{
"name": "symfony/translation",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f"
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/c1db87c51251167dd91198b9d1edf897773adb4f",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"shasum": ""
},
"require": {
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2015-12-05 17:37:59"
+ "time": "2016-02-02 09:49:18"
},
{
"name": "symfony/yaml",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
"shasum": ""
},
"require": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-23 07:41:20"
}
],
"aliases": [],
// params hierarchy. More info: http://docs.behat.org/guides/7.config.html
// Example:
// $CFG->behat_config = array(
-// 'default' => array(
-// 'formatter' => array(
-// 'name' => 'pretty',
-// 'parameters' => array(
-// 'decorated' => true,
-// 'verbose' => false
-// )
-// )
-// ),
// 'Mac-Firefox' => array(
+// 'suites' => array (
+// 'default' => array(
+// 'filters' => array(
+// 'tags' => '~@_file_upload'
+// ),
+// ),
+// ),
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'firefox',
// 'capabilities' => array(
// ),
// 'Mac-Safari' => array(
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'safari',
// 'capabilities' => array(
// )
// )
// );
+// You can also use the following config to override default Moodle configuration for Behat.
+// This config is limited to default suite and will be supported in later versions.
+// It will have precedence over $CFG->behat_config.
+// $CFG->behat_profiles = array(
+// 'phantomjs' => array(
+// 'browser' => 'phantomjs',
+// 'tags' => '~@_file_upload&&~@_alert&&~@_bug_phantomjs',
+// 'wd_host' => 'http://127.0.0.1:4443/wd/hub',
+// 'capabilities' => array(
+// 'platform' => 'Linux',
+// 'version' => 2.1
+// )
+// ),
+// );
//
// You can force the browser session (not user's sessions) to restart after N seconds. This could
// be useful if you are using a cloud-based service with time restrictions in the browser side.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\DriverException as DriverException,
unset($rows[$key]);
}
}
- $table->setRows($rows);
+ $table = new TableNode($rows);
// Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
// format field when the editor is being rendered and the click misses the field coordinates.
// The 'Hide' button should be available.
$nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
- $this->find('named', array('link', get_string('hide')), $nohideexception, $activitynode);
+ $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
}
}
// Also 'Show' icon.
$noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
- $this->find('named', array('link', get_string('show')), $noshowexception, $activitynode);
+ $this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
} else {
And I should see the "Course categories and courses" management page
And I click on <sortby> action for "Master cat" in management category listing
And a new page should have loaded since I started watching
- And I start watching to see if a new page loads
And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
And I should see category listing <cat2> before <cat3>
And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
And a new page should have loaded since I started watching
- And I start watching to see if a new page loads
And I should see the "Course categories and courses" management page
And I should see course listing <course1> before <course2>
And I should see course listing <course2> before <course3>
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
if (!$moving && count($grade_edit_tree->categories) > 1) {
echo '<br /><br />';
echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
- $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty');
+ $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty singleselect');
echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
$OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
- Behat\Behat\Context\Step\Then as Then,
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When,
+ Moodle\BehatExtension\Context\Step\Then as Then,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
- Behat\Behat\Context\Step\Then as Then,
+ Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
}
}
+ // Remove empty criterion, as TableNode might contain them to make table rows equal size.
+ $newcriterion = array();
+ foreach ($criterion as $k => $c) {
+ if (!empty($c)) {
+ $newcriterion[$k] = $c;
+ }
+ }
+ $criterion = $newcriterion;
+
// Checking the number of cells.
if (count($criterion) % 2 === 0) {
throw new ExpectationException(
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When;
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When;
/**
* Generic grading methods step definitions.
// Shortcut in case we already are in the grading page.
$usergradetextliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($usergradetext);
- if ($this->getSession()->getPage()->find('named', array('link', $usergradetextliteral))) {
+ if ($this->getSession()->getPage()->find('named_partial', array('link', $usergradetextliteral))) {
return $gradeuserstep;
}
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given,
- Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+ Moodle\BehatExtension\Context\Step\Then,
Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
class behat_grade extends behat_base {
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
+ And I change window size to "large"
And I give the grade "60.00" to the user "Student 1" for the grade item "Test assignment one"
And I give the grade "20.00" to the user "Student 1" for the grade item "Test assignment two"
And I give the grade "40.00" to the user "Student 1" for the grade item "Test assignment three"
| Hidden | 1 |
And I set the following settings for grade item "Test assignment eight":
| Hidden | 1 |
+ And I change window size to "medium"
And I navigate to "Course grade settings" node in "Grade administration > Setup"
And I set the field "Grade display type" to "Real (percentage)"
And I press "Save changes"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Then;
+use Moodle\BehatExtension\Context\Step\Then;
use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
$fulloption = $groupoption->getText();
$select->selectOption($fulloption);
+ // This is needed by some drivers to ensure relevant event is triggred and button is enabled.
+ $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+ $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script);
+ $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+
// Here we don't need to wait for the AJAX response.
$this->find_button(get_string('adduserstogroup', 'group'))->click();
*/
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
+ // Throw exception, so dev knows it is not supported.
+ if ($selector === 'named') {
+ $exception = 'Using the "named" selector is deprecated as of 3.1. '
+ .' Use the "named_partial" or use the "named_exact" selector instead.';
+ throw new ExpectationException($exception, $this->getSession());
+ }
+
// Returns the first match.
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
return count($items) ? reset($items) : null;
*/
protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
+ // Throw exception, so dev knows it is not supported.
+ if ($selector === 'named') {
+ $exception = 'Using the "named" selector is deprecated as of 3.1. '
+ .' Use the "named_partial" or use the "named_exact" selector instead.';
+ throw new ExpectationException($exception, $this->getSession());
+ }
+
// Generic info.
if (!$exception) {
// With named selectors we can be more specific.
- if ($selector == 'named') {
+ if (($selector == 'named_exact') || ($selector == 'named_partial')) {
$exceptiontype = $locator[0];
$exceptionlocator = $locator[1];
// Redirecting execution to the find method with the specified selector.
// It will detect if it's pointing to an unexisting named selector.
- return $this->find('named',
+ return $this->find('named_partial',
array(
$cleanname,
$this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0])
}
$this->getSession()->getDriver()->resizeWindow($width, $height);
}
+
+ /**
+ * Waits for all the JS to be loaded.
+ *
+ * @throws \Exception
+ * @throws NoSuchWindow
+ * @throws UnknownError
+ * @return bool True or false depending whether all the JS is loaded or not.
+ */
+ public function wait_for_pending_js() {
+ // Waiting for JS is only valid for JS scenarios.
+ if (!$this->running_javascript()) {
+ return;
+ }
+
+ // We don't use behat_base::spin() here as we don't want to end up with an exception
+ // if the page & JSs don't finish loading properly.
+ for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
+ $pending = '';
+ try {
+ $jscode = '
+ return function() {
+ if (typeof M === "undefined") {
+ if (document.readyState === "complete") {
+ return "";
+ } else {
+ return "incomplete";
+ }
+ } else if (' . self::PAGE_READY_JS . ') {
+ return "";
+ } else {
+ return M.util.pending_js.join(":");
+ }
+ }();';
+ $pending = $this->getSession()->evaluateScript($jscode);
+ } catch (NoSuchWindow $nsw) {
+ // We catch an exception here, in case we just closed the window we were interacting with.
+ // No javascript is running if there is no window right?
+ $pending = '';
+ } catch (UnknownError $e) {
+ // M is not defined when the window or the frame don't exist anymore.
+ if (strstr($e->getMessage(), 'M is not defined') != false) {
+ $pending = '';
+ }
+ }
+
+ // If there are no pending JS we stop waiting.
+ if ($pending === '') {
+ return true;
+ }
+
+ // 0.1 seconds.
+ usleep(100000);
+ }
+
+ // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
+ // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
+ // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
+ // number of JS pending code and JS completed code will not match and we will reach this point.
+ throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
+ ' seconds. There is a Javascript error or the code is extremely slow.');
+ }
}
*/
class behat_config_manager {
+ /**
+ * @var bool Keep track of the automatic profile conversion. So we can notify user.
+ */
+ public static $autoprofileconversion = false;
+
/**
* Updates a config file
*
$CFG->behat_wwwroot = 'http://itwillnotbeused.com';
}
- $basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
-
+ // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
+ // https://github.com/Behat/Behat/pull/628.
$config = array(
'default' => array(
- 'paths' => array(
- 'features' => $basedir . DIRECTORY_SEPARATOR . 'features',
- 'bootstrap' => $basedir . DIRECTORY_SEPARATOR . 'features' . DIRECTORY_SEPARATOR . 'bootstrap',
+ 'formatters' => array(
+ 'moodle_progress' => array(
+ 'output_styles' => array(
+ 'comment' => array('magenta'))
+ )
),
- 'context' => array(
- 'class' => 'behat_init_context'
+ 'suites' => array(
+ 'default' => array(
+ 'paths' => $features,
+ 'contexts' => array_keys($stepsdefinitions)
+ )
),
'extensions' => array(
- 'Behat\MinkExtension\Extension' => array(
+ 'Behat\MinkExtension' => array(
'base_url' => $CFG->behat_wwwroot,
'goutte' => null,
'selenium2' => $selenium2wdhost
),
- 'Moodle\BehatExtension\Extension' => array(
- 'formatters' => array(
- 'moodle_progress' => 'Moodle\BehatExtension\Formatter\MoodleProgressFormatter',
- 'moodle_list' => 'Moodle\BehatExtension\Formatter\MoodleListFormatter',
- 'moodle_step_count' => 'Moodle\BehatExtension\Formatter\MoodleStepCountFormatter'
- ),
- 'features' => $features,
+ 'Moodle\BehatExtension' => array(
+ 'moodledirroot' => $CFG->dirroot,
'steps_definitions' => $stepsdefinitions
)
- ),
- 'formatter' => array(
- 'name' => 'moodle_progress'
)
)
);
// In case user defined overrides respect them over our default ones.
if (!empty($CFG->behat_config)) {
- $config = self::merge_config($config, $CFG->behat_config);
+ foreach ($CFG->behat_config as $profile => $values) {
+ $config = self::merge_config($config, self::merge_behat_config($profile, $values));
+ }
+ }
+ // Check for Moodle custom ones.
+ if (!empty($CFG->behat_profiles) && is_array($CFG->behat_profiles)) {
+ foreach ($CFG->behat_profiles as $profile => $values) {
+ $config = self::merge_config($config, self::get_behat_profile($profile, $values));
+ }
}
return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
}
+ /**
+ * Parse $CFG->behat_config and return the array with required config structure for behat.yml
+ *
+ * @param string $profile profile name
+ * @param array $values values for profile
+ * @return array
+ */
+ protected static function merge_behat_config($profile, $values) {
+ // Only add profile which are compatible with Behat 3.x
+ // Just check if any of Bheat 2.5 config is set. Not checking for 3.x as it might have some other configs
+ // Like : rerun_cache etc.
+ if (!isset($values['filters']['tags']) && !isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+ return array($profile => $values);
+ }
+
+ // Parse 2.5 format and get related values.
+ $oldconfigvalues = array();
+ if (isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+ $extensionvalues = $values['extensions']['Behat\MinkExtension\Extension'];
+ if (isset($extensionvalues['selenium2']['browser'])) {
+ $oldconfigvalues['browser'] = $extensionvalues['selenium2']['browser'];
+ }
+ if (isset($extensionvalues['selenium2']['wd_host'])) {
+ $oldconfigvalues['wd_host'] = $extensionvalues['selenium2']['wd_host'];
+ }
+ if (isset($extensionvalues['capabilities'])) {
+ $oldconfigvalues['capabilities'] = $extensionvalues['capabilities'];
+ }
+ }
+
+ if (isset($values['filters']['tags'])) {
+ $oldconfigvalues['tags'] = $values['filters']['tags'];
+ }
+
+ if (!empty($oldconfigvalues)) {
+ self::$autoprofileconversion = true;
+ return self::get_behat_profile($profile, $oldconfigvalues);
+ }
+
+ // If nothing set above then return empty array.
+ return array();
+ }
+
+ /**
+ * Parse $CFG->behat_profile and return the array with required config structure for behat.yml.
+ *
+ * $CFG->behat_profiles = array(
+ * 'profile' = array(
+ * 'browser' => 'firefox',
+ * 'tags' => '@javascript',
+ * 'wd_host' => 'http://127.0.0.1:4444/wd/hub',
+ * 'capabilities' => array(
+ * 'platform' => 'Linux',
+ * 'version' => 44
+ * )
+ * )
+ * );
+ *
+ * @param string $profile profile name
+ * @param array $values values for profile.
+ * @return array
+ */
+ protected static function get_behat_profile($profile, $values) {
+ // Values should be an array.
+ if (!is_array($values)) {
+ return array();
+ }
+
+ // Check suite values.
+ $behatprofilesuites = array();
+ // Fill tags information.
+ if (isset($values['tags'])) {
+ $behatprofilesuites = array(
+ 'suites' => array(
+ 'default' => array(
+ 'filters' => array(
+ 'tags' => $values['tags'],
+ )
+ )
+ )
+ );
+ }
+
+ // Selenium2 config values.
+ $behatprofileextension = array();
+ $seleniumconfig = array();
+ if (isset($values['browser'])) {
+ $seleniumconfig['browser'] = $values['browser'];
+ }
+ if (isset($values['wd_host'])) {
+ $seleniumconfig['wd_host'] = $values['wd_host'];
+ }
+ if (isset($values['capabilities'])) {
+ $seleniumconfig['capabilities'] = $values['capabilities'];
+ }
+ if (!empty($seleniumconfig)) {
+ $behatprofileextension = array(
+ 'extensions' => array(
+ 'Behat\MinkExtension' => array(
+ 'selenium2' => $seleniumconfig,
+ )
+ )
+ );
+ }
+
+ return array($profile => array_merge($behatprofilesuites, $behatprofileextension));
+ }
+
/**
* Attempt to split feature list into fairish buckets using timing information, if available.
* Simply add each one to lightest buckets until all files allocated.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
-use \Behat\Behat\Context\BehatContext;
+use Behat\Testwork\Environment\Environment;
/**
* Helper to get behat contexts.
class behat_context_helper {
/**
- * @var BehatContext main behat context.
+ * Behat environment.
+ *
+ * @var Environment
*/
- protected static $maincontext = false;
+ protected static $environment = null;
/**
- * Save main behat context reference to be used for finding sub-contexts.
+ * Sets the browser session.
*
- * @param BehatContext $maincontext
+ * @param Environment $environment
* @return void
*/
- public static function set_main_context(BehatContext $maincontext) {
- self::$maincontext = $maincontext;
+ public static function set_session(Environment $environment) {
+ self::$environment = $environment;
}
/**
*/
public static function get($classname) {
- if (!$subcontext = self::$maincontext->getSubcontextByClassName($classname)) {
+ if (!$subcontext = self::$environment->getContext($classname)) {
throw coding_exception('The required "' . $classname . '" class does not exist');
}
} else {
// Named selectors uses arrays as locators including the type of named selector.
$locator = array($selectortype, $session->getSelectorsHandler()->xpathLiteral($element));
- $selector = 'named';
+ $selector = 'named_partial';
}
return array($selector, $locator);
public static function register_moodle_selectors(Behat\Mink\Session $session) {
foreach (self::get_moodle_selectors() as $name => $xpath) {
- $session->getSelectorsHandler()->getSelector('named')->registerNamedXpath($name, $xpath);
+ $session->getSelectorsHandler()->getSelector('named_partial')->registerNamedXpath($name, $xpath);
}
}
$this->field->click();
// Trigger the onchange event as triggered when 'checking' the checkbox.
- $this->session->getDriver()->triggerSynScript(
- $this->field->getXPath(),
- "Syn.trigger('change', {}, {{ELEMENT}})"
- );
+ $this->trigger_on_change();
} else if (empty($value) && $this->field->isChecked()) {
$this->field->click();
// Trigger the onchange event as triggered when 'checking' the checkbox.
- $this->session->getDriver()->triggerSynScript(
- $this->field->getXPath(),
- "Syn.trigger('change', {}, {{ELEMENT}})"
- );
+ $this->trigger_on_change();
}
}
return false;
}
+ /**
+ * Trigger on change event.
+ */
+ protected function trigger_on_change() {
+ $this->session->getDriver()->triggerSynScript(
+ $this->field->getXPath(),
+ "Syn.trigger('change', {}, {{ELEMENT}})"
+ );
+ }
}
* @return string The value attribute
*/
public function get_value() {
- return (bool)$this->field->getAttribute('checked');
+ return $this->field->isSelected();
}
/**
public function set_value($value) {
if ($this->running_javascript()) {
- parent::set_value($value);
+ // Check on radio button.
+ $this->field->click();
+
+ // Trigger the onchange event as triggered when 'selecting' the radio.
+ if (!empty($value) && !$this->field->isSelected()) {
+ $this->trigger_on_change();
+ }
} else {
// Goutte does not accept a check nor a click in an input[type=radio].
$this->field->setValue($this->field->getAttribute('value'));
}
}
- /**
- * Returns whether the provided value matches the current value or not.
- *
- * @param string $expectedvalue
- * @return bool
- */
- public function matches($expectedvalue = false) {
- return $this->text_matches($expectedvalue);
- }
}
*/
public function set_value($value) {
- // In some browsers we select an option and it triggers all the
- // autosubmits and works as expected but not in all of them, so we
- // try to catch all the possibilities to make this function work as
- // expected.
-
- // Get the internal id of the element we are going to click.
- // This kind of internal IDs are only available in the selenium wire
- // protocol, so only available using selenium drivers, phantomjs and family.
- if ($this->running_javascript()) {
- $currentelementid = $this->get_internal_field_id();
- }
-
// Is the select multiple?
$multiple = $this->field->hasAttribute('multiple');
-
- // By default, assume the passed value is a non-multiple option.
- $options = array(trim($value));
+ $singleselect = ($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'));
// Here we select the option(s).
if ($multiple) {
// Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash.
- $options = preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $value));
+ $options = preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', trim($value)));
// This is a multiple select, let's pass the multiple flag after first option.
$afterfirstoption = false;
foreach ($options as $option) {
$afterfirstoption = true;
}
} else {
- // If value is already set then don't set it again.
- if ($this->field->getValue() == $value) {
- return;
- } else {
- $opt = $this->field->find('named', array(
- 'option', $this->field->getSession()->getSelectorsHandler()->xpathLiteral($value)
- ));
- if ($opt && ($this->field->getValue() == $opt->getValue())) {
- return;
- }
- }
-
- // If not running JS or not a singleselect then use selectOption.
- // For singleselect only click event is enough.
- if (!$this->running_javascript() ||
- !($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'))) {
-
- // This is a single select, let's pass the last one specified.
- $this->field->selectOption(end($options));
- }
- }
-
- // With JS disabled this is enough and we finish here.
- if (!$this->running_javascript()) {
- return;
- }
-
- // With JS enabled we add more clicks as some selenium
- // drivers requires it to fire JS events.
-
- // In some browsers the selectOption actions can perform a form submit or reload page
- // so we need to ensure the element is still available to continue interacting
- // with it. We don't wait here.
- // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch.
- $selectxpath = $this->field->getXpath();
- if (!$this->session->getDriver()->find($selectxpath)) {
- return;
- }
-
- // We also check the selenium internal element id, if it have changed
- // we are dealing with an autosubmit that was already executed, and we don't to
- // execute anything else as the action we wanted was already performed.
- if ($currentelementid != $this->get_internal_field_id()) {
- return;
- }
+ // By default, assume the passed value is a non-multiple option.
+ $this->field->selectOption(trim($value));
+ }
// Wait for all the possible AJAX requests that have been
// already triggered by selectOption() to be finished.
- $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
- // Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
- try {
- $multiple = $this->field->hasAttribute('multiple');
- } catch (Exception $e) {
- // We do not specify any specific Exception type as there are
- // different exceptions that can be thrown by the driver and
- // we can not control them all, also depending on the selenium
- // version the exception type can change.
- return;
- }
-
- // Single select sometimes needs an extra click in the option.
- if (!$multiple) {
-
- // Var $options only contains 1 option.
- $optionxpath = $this->get_option_xpath(end($options), $selectxpath);
-
- // Using the driver direcly because Element methods are messy when dealing
- // with elements inside containers.
- if ($optionnodes = $this->session->getDriver()->find($optionxpath)) {
-
- // Wrapped in a try & catch as we can fall into race conditions
- // and the element may not be there.
- try {
- current($optionnodes)->click();
- } catch (Exception $e) {
- // We continue and return as this means that the element is not there or it is not the same.
- return;
- }
- }
-
- } else {
-
- // Wrapped in a try & catch as we can fall into race conditions
- // and the element may not be there.
- try {
- // Multiple ones needs the click in the select.
- $this->field->click();
- } catch (Exception $e) {
- // We continue and return as this means that the element is not there or it is not the same.
- return;
- }
-
- // We also check that the option(s) are still there. We neither wait.
- foreach ($options as $option) {
- $optionxpath = $this->get_option_xpath($option, $selectxpath);
- if (!$this->session->getDriver()->find($optionxpath)) {
- return;
- }
+ if ($this->running_javascript()) {
+ // Trigger change event as this is needed by some drivers (Phantomjs). Don't do it for
+ // Singleselect as this will cause multiple event fire and lead to race-around condition.
+ $browser = \Moodle\BehatExtension\Driver\MoodleSelenium2Driver::getBrowser();
+ if (!$singleselect && ($browser == 'phantomjs')) {
+ $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+ $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
}
-
- // Wait for all the possible AJAX requests that have been
- // already triggered by clicking on the field to be finished.
$this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
- // Wrapped in a try & catch as we can fall into race conditions
- // and the element may not be there.
- try {
-
- // Repeating the select(s) as some drivers (chrome that I know) are moving
- // to another option after the general select field click above.
- $afterfirstoption = false;
- foreach ($options as $option) {
- $this->field->selectOption(trim($option), $afterfirstoption);
- $afterfirstoption = true;
- }
- } catch (Exception $e) {
- // We continue and return as this means that the element is not there or it is not the same.
- return;
- }
}
}
// We are dealing with a multi-select.
- // Can pass multiple comma separated, with valuable commas escaped with backslash.
- $expectedarr = array(); // Array of passed text options to test.
-
// Unescape + trim all options and flip it to have the expected values as keys.
$expectedoptions = $this->get_unescaped_options($expectedvalue);
$selectedoptions = array(); // To accumulate found selected options.
- // Selenium getValue() implementation breaks - separates - values having
- // commas within them, so we'll be looking for options with the 'selected' attribute instead.
- if ($this->running_javascript()) {
- // Get all the options in the select and extract their value/text pairs.
- $alloptions = $this->field->findAll('xpath', '//option');
- foreach ($alloptions as $option) {
- // Is it selected?
- if ($option->hasAttribute('selected')) {
- if ($multiple) {
- // If the select is multiple, text commas must be encoded.
- $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
- } else {
- $selectedoptions[] = trim($option->{$method}());
- }
- }
- }
-
- } else {
- // Goutte does not keep the 'selected' attribute updated, but its getValue() returns
- // the selected elements correctly, also those having commas within them.
-
- // Goutte returns the values as an array or as a string depending
- // on whether multiple options are selected or not.
- $values = $this->field->getValue();
- if (!is_array($values)) {
- $values = array($values);
- }
+ // Driver returns the values as an array or as a string depending
+ // on whether multiple options are selected or not.
+ $values = $this->field->getValue();
+ if (!is_array($values)) {
+ $values = array($values);
+ }
- // Get all the options in the select and extract their value/text pairs.
- $alloptions = $this->field->findAll('xpath', '//option');
- foreach ($alloptions as $option) {
- // Is it selected?
- if (in_array($option->getValue(), $values)) {
- if ($multiple) {
- // If the select is multiple, text commas must be encoded.
- $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
- } else {
- $selectedoptions[] = trim($option->{$method}());
- }
+ // Get all the options in the select and extract their value/text pairs.
+ $alloptions = $this->field->findAll('xpath', '//option');
+ foreach ($alloptions as $option) {
+ // Is it selected?
+ if (in_array($option->getValue(), $values)) {
+ if ($multiple) {
+ // If the select is multiple, text commas must be encoded.
+ $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
+ } else {
+ $selectedoptions[] = trim($option->{$method}());
}
}
}
And I wait "2" seconds
And I click on "Image" "button"
And the field "Enter URL" matches value "/broken-image"
- And I set the field "Describe this image" to "No more warning!"
+ And I set the field "Describe this image for someone who cannot see it" to "No more warning!"
And I press "Save image"
And I press "Accessibility checker"
And I should see "Congratulations, no accessibility problems found!"
And I click on ".moodle-dialogue-focused .closebutton" "css_element"
And I select the text in the "Description" Atto editor
And I click on "Image" "button"
- And I set the field "Describe this image" to ""
+ And I set the field "Describe this image for someone who cannot see it" to ""
And I set the field "Description not necessary" to "1"
And I press "Save image"
And I press "Accessibility checker"
And I click on "Private files" "link"
And I click on "moodle-logo.png" "link"
And I click on "Select this file" "button"
- And I set the field "Describe this image" to "It's the Moodle"
+ And I set the field "Describe this image for someone who cannot see it" to "It's the Moodle"
# Wait for the page to "settle".
And I wait until the page is ready
And the field "Width" matches value "204"
And I follow "Edit profile"
And I select the text in the "Description" Atto editor
And I click on "Image" "button"
- Then the field "Describe this image" matches value "It's the Moodle"
+ Then the field "Describe this image for someone who cannot see it" matches value "It's the Moodle"
And the field "Width" matches value "123"
And the field "Height" matches value "456"
And I select the text in the "Description" Atto editor
When I click on "Image" "button"
Then the field "Enter URL" matches value "/nothing/here"
- And I set the field "Describe this image" to "Something"
+ And I set the field "Describe this image for someone who cannot see it" to "Something"
And I set the field "Enter URL" to ""
And I press "Save image"
And I set the field "Description" to "<p>Image: <img src='/nothing/again' width='123' height='456' alt='Awesome!'>.</p>"
And I press "Save and display"
And I should not see "You must choose whether to rescale existing grades or not"
- Scenario: Attempting to change the grade type when grades already exist
- Given I log in as "teacher1"
- And I follow "Course 1"
- And I turn editing mode on
- And I add a "Assignment" to section "1" and I fill the form with:
- | Assignment name | Test assignment name |
- | Description | Test assignment description |
- And I follow "Test assignment name"
- And I follow "View/grade all submissions"
- And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
- And I set the field "Grade out of 100" to "50"
- And I press "Save changes"
- And I press "Continue"
- And I click on "Edit settings" "link" in the "Administration" "block"
- When I expand all fieldsets
- Then I should see "Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades."
- And I set the field "grade[modgrade_type]" to "Scale"
- And I press "Save and display"
- And I should see "You cannot change the type, as grades already exist for this item"
-
Scenario: Attempting to change the scale when grades already exist
Given I log in as "admin"
And I navigate to "Scales" node in "Site administration > Grades"
And I click on "Edit settings" "link" in the "Administration" "block"
When I expand all fieldsets
Then I should see "Some grades have already been awarded, so the grade type and scale cannot be changed"
- And I set the field "grade[modgrade_scale]" to "Letter scale"
- And I press "Save and display"
- And I should see "You cannot change the scale, as grades already exist for this item"
Scenario: Attempting to change the maximum grade when ratings exist
Given I log in as "teacher1"
$grade->feedbackformat = $feedbackformat;
}
+ $gradechanged = false;
if (empty($grade->id)) {
$grade->timecreated = null; // hack alert - date submitted - no submission yet
$grade->timemodified = time(); // hack alert - date graded
if ($result && !is_null($grade->finalgrade)) {
\core\event\user_graded::create_from_grade($grade)->trigger();
}
- } else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
- or $grade->feedback !== $oldgrade->feedback
- or $grade->feedbackformat != $oldgrade->feedbackformat
- or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
- or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
- or ($oldgrade->overridden == 0 and $grade->overridden > 0)) {
+ $gradechanged = true;
+ } else {
+ // Existing grade_grades.
+
+ if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
+ or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
+ or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
+ or ($oldgrade->overridden == 0 and $grade->overridden > 0)) {
+ $gradechanged = true;
+ }
+
+ if ($grade->feedback === $oldgrade->feedback and $grade->feedbackformat == $oldgrade->feedbackformat and
+ $gradechanged === false) {
+ // No grade nor feedback changed.
+ return $result;
+ }
+
$grade->timemodified = time(); // hack alert - date graded
$result = $grade->update($source);
if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
\core\event\user_graded::create_from_grade($grade)->trigger();
}
- } else {
- // no grade change
- return $result;
}
if (!$result) {
- // something went wrong - better force final grade recalculation
+ // Something went wrong - better force final grade recalculation.
$this->force_regrading();
+ return $result;
+ }
+
+ // If we are not updating grades we don't need to recalculate the whole course.
+ if (!$gradechanged) {
+ return $result;
+ }
- } else if ($this->is_course_item() and !$this->needsupdate) {
+ if ($this->is_course_item() and !$this->needsupdate) {
if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
$this->force_regrading();
}
} else if (!$this->needsupdate) {
+
$course_item = grade_item::fetch_course_item($this->courseid);
if (!$course_item->needsupdate) {
if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
}
// end of hack alert
+ $gradechanged = false;
if (empty($grade->id)) {
$result = (bool)$grade->insert($source);
if ($result && !is_null($grade->finalgrade)) {
\core\event\user_graded::create_from_grade($grade)->trigger();
}
- } else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
- or grade_floats_different($grade->rawgrade, $oldgrade->rawgrade)
- or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
- or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
- or $grade->rawscaleid != $oldgrade->rawscaleid
- or $grade->feedback !== $oldgrade->feedback
- or $grade->feedbackformat != $oldgrade->feedbackformat
- or $grade->timecreated != $oldgrade->timecreated // part of hack above
- or $grade->timemodified != $oldgrade->timemodified // part of hack above
- ) {
+ $gradechanged = true;
+ } else {
+ // Existing grade_grades.
+
+ if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
+ or grade_floats_different($grade->rawgrade, $oldgrade->rawgrade)
+ or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
+ or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
+ or $grade->rawscaleid != $oldgrade->rawscaleid) {
+ $gradechanged = true;
+ }
+
+ // The timecreated and timemodified checking is part of the hack above.
+ if ($gradechanged === false and
+ $grade->feedback === $oldgrade->feedback and
+ $grade->feedbackformat == $oldgrade->feedbackformat and
+ $grade->timecreated == $oldgrade->timecreated and
+ $grade->timemodified == $oldgrade->timemodified) {
+ // No changes.
+ return $result;
+ }
$result = $grade->update($source);
// If the grade update was successful and the actual grade has changed then trigger a user_graded event.
if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
\core\event\user_graded::create_from_grade($grade)->trigger();
}
- } else {
- return $result;
}
if (!$result) {
- // something went wrong - better force final grade recalculation
+ // Something went wrong - better force final grade recalculation.
$this->force_regrading();
+ return $result;
+ }
- } else if (!$this->needsupdate) {
+ // If we are not updating grades we don't need to recalculate the whole course.
+ if (!$gradechanged) {
+ return $result;
+ }
+
+ if (!$this->needsupdate) {
$course_item = grade_item::fetch_course_item($this->courseid);
if (!$course_item->needsupdate) {
if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
return true;
}
+/**
+ * Generate a unique email Message-ID using the moodle domain and install path
+ *
+ * @param string $localpart An optional unique message id prefix.
+ * @return string The formatted ID ready for appending to the email headers.
+ */
+function generate_email_messageid($localpart = null) {
+ global $CFG;
+
+ $urlinfo = parse_url($CFG->wwwroot);
+ $base = '@' . $urlinfo['host'];
+
+ // If multiple moodles are on the same domain we want to tell them
+ // apart so we add the install path to the local part. This means
+ // that the id local part should never contain a / character so
+ // we can correctly parse the id to reassemble the wwwroot.
+ if (isset($urlinfo['path'])) {
+ $base = $urlinfo['path'] . $base;
+ }
+
+ if (empty($localpart)) {
+ $localpart = uniqid('', true);
+ }
+
+ // Because we may have an option /installpath suffix to the local part
+ // of the id we need to escape any / chars which are in the $localpart.
+ $localpart = str_replace('/', '%2F', $localpart);
+
+ return '<' . $localpart . $base . '>';
+}
+
/**
* Send an email to a specified user
*
$mail->FromName = $renderer->render_from_template('core/email_fromname', $context);
$messagetext = $renderer->render_from_template('core/email_text', $context);
+ // Autogenerate a MessageID if it's missing.
+ if (empty($mail->MessageID)) {
+ $mail->MessageID = generate_email_messageid();
+ }
+
if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) {
// Don't ever send HTML to users who don't want it.
$mail->isHTML(true);
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\Then as Then,
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\Then as Then,
Behat\Gherkin\Node\TableNode as TableNode;
/**
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
- Behat\Behat\Context\Step\Then as Then,
- Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When,
+ Moodle\BehatExtension\Context\Step\Then as Then,
Behat\Gherkin\Node\PyStringNode as PyStringNode,
- Behat\Mink\Element\NodeElement as NodeElement,
Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
WebDriver\Exception\NoSuchElement as NoSuchElement,
WebDriver\Exception\StaleElementReference as StaleElementReference,
Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given;
+ Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Cross component steps definitions.
// unnamed window (presumably the main window) to some other named
// window, then we first set the main window name to a conventional
// value that we can later use this name to switch back.
- $this->getSession()->evaluateScript(
+ $this->getSession()->executeScript(
'if (window.name == "") window.name = "' . self::MAIN_WINDOW_NAME . '"');
$this->getSession()->switchToWindow($windowname);
// Check if value exists in specific row/column.
// Get row xpath.
- $rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)=" . $rowliteral . "] or td[normalize-space(.)=" . $rowliteral . "]]";
+ // GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant.
+ $rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral .
+ "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
$this->pageloaddetectionrunning = true;
- $session->evaluateScript(
+ $session->executeScript(
'var span = document.createElement("span");
span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '");
span.setAttribute("style", "display: none;");
require_once(__DIR__ . '/../../behat/behat_base.php');
-use Behat\Behat\Event\SuiteEvent as SuiteEvent,
- Behat\Behat\Event\ScenarioEvent as ScenarioEvent,
- Behat\Behat\Event\FeatureEvent as FeatureEvent,
- Behat\Behat\Event\OutlineExampleEvent as OutlineExampleEvent,
- Behat\Behat\Event\StepEvent as StepEvent,
+use Behat\Testwork\Hook\Scope\BeforeSuiteScope,
+ Behat\Testwork\Hook\Scope\AfterSuiteScope,
+ Behat\Behat\Hook\Scope\BeforeFeatureScope,
+ Behat\Behat\Hook\Scope\AfterFeatureScope,
+ Behat\Behat\Hook\Scope\BeforeScenarioScope,
+ Behat\Behat\Hook\Scope\AfterScenarioScope,
+ Behat\Behat\Hook\Scope\BeforeStepScope,
+ Behat\Behat\Hook\Scope\AfterStepScope,
Behat\Mink\Exception\DriverException as DriverException,
WebDriver\Exception\NoSuchWindow as NoSuchWindow,
WebDriver\Exception\UnexpectedAlertOpen as UnexpectedAlertOpen,
* @throws Exception
* @BeforeSuite
*/
- public static function before_suite(SuiteEvent $event) {
+ public static function before_suite(BeforeSuiteScope $scope) {
global $CFG;
// Defined only when the behat CLI command is running, the moodle init setup process will
define('BEHAT_TEST', 1);
define('CLI_SCRIPT', 1);
-
// With BEHAT_TEST we will be using $CFG->behat_* instead of $CFG->dataroot, $CFG->prefix and $CFG->wwwroot.
require_once(__DIR__ . '/../../../config.php');
* @param FeatureEvent $event event fired before feature.
* @BeforeFeature
*/
- public static function before_feature(FeatureEvent $event) {
+ public static function before_feature(BeforeFeatureScope $event) {
if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
return;
}
* @param FeatureEvent $event event fired after feature.
* @AfterFeature
*/
- public static function after_feature(FeatureEvent $event) {
+ public static function after_feature(AfterFeatureScope $event) {
if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
return;
}
* @param SuiteEvent $event event fired after suite.
* @AfterSuite
*/
- public static function after_suite(SuiteEvent $event) {
+ public static function after_suite(AfterSuiteScope $event) {
if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
return;
}
* @throws coding_exception If here we are not using the test database it should be because of a coding error
* @BeforeScenario
*/
- public function before_scenario($event) {
+ public function before_scenario(BeforeScenarioScope $scope) {
global $DB, $SESSION, $CFG;
// As many checks as we can.
} catch (CurlExec $e) {
// Exception thrown by WebDriver, so only @javascript tests will be caugth; in
// behat_util::is_server_running() we already checked that the server is running.
- throw new Exception($driverexceptionmsg);
+ $this->stop_execution($driverexceptionmsg);
} catch (DriverException $e) {
- throw new Exception($driverexceptionmsg);
+ $this->stop_execution($driverexceptionmsg);
} catch (UnknownError $e) {
// Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions.
- $this->throw_unknown_exception($e);
+ $this->stop_execution($e->getMessage());
}
-
// We need the Mink session to do it and we do it only before the first scenario.
if (self::is_first_scenario()) {
behat_selectors::register_moodle_selectors($session);
- behat_context_helper::set_main_context($event->getContext()->getMainContext());
+ behat_context_helper::set_session($scope->getEnvironment());
}
// Reset mink session between the scenarios.
// Let's be conservative as we never know when new upstream issues will affect us.
$session->visit($this->locate_path('/'));
} catch (UnknownError $e) {
- $this->throw_unknown_exception($e);
+ $this->stop_execution($e->getMessage());
}
* default would be at framework level, which will stop the execution of
* the run.
*
- * @param StepEvent $event event fired before step.
- * @BeforeStep @javascript
+ * @BeforeStep
*/
- public function before_step_javascript(StepEvent $event) {
+ public function before_step_javascript(BeforeStepScope $scope) {
+ self::$currentstepexception = null;
- try {
- $this->wait_for_pending_js();
- self::$currentstepexception = null;
- } catch (Exception $e) {
- self::$currentstepexception = $e;
+ // Only run if JS.
+ if ($this->running_javascript()) {
+ try {
+ $this->wait_for_pending_js();
+ } catch (Exception $e) {
+ self::$currentstepexception = $e;
+ }
}
}
* default would be at framework level, which will stop the execution of
* the run.
*
- * @param StepEvent $event event fired after step.
- * @AfterStep @javascript
+ * @AfterStep
*/
- public function after_step_javascript(StepEvent $event) {
- global $CFG;
+ public function after_step_javascript(AfterStepScope $scope) {
+ global $CFG, $DB;
+
+ // Save the page content if the step failed.
+ if (!empty($CFG->behat_faildump_path) &&
+ $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
+ $this->take_contentdump($scope);
+ }
+
+ // Abort any open transactions to prevent subsequent tests hanging.
+ // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
+ // want to see a message in the behat output.
+ if (($scope->getTestResult() instanceof \Behat\Behat\Tester\Result\ExecutedStepResult) &&
+ $scope->getTestResult()->hasException()) {
+ if ($DB && $DB->is_transaction_started()) {
+ $DB->force_transaction_rollback();
+ }
+ }
+
+ // Only run if JS.
+ if (!$this->running_javascript()) {
+ return;
+ }
// Save a screenshot if the step failed.
if (!empty($CFG->behat_faildump_path) &&
- $event->getResult() === StepEvent::FAILED) {
- $this->take_screenshot($event);
+ $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
+ $this->take_screenshot($scope);
}
try {
}
}
- /**
- * Execute any steps required after the step has finished.
- *
- * This includes creating an HTML dump of the content if there was a failure.
- *
- * @param StepEvent $event event fired after step.
- * @AfterStep
- */
- public function after_step(StepEvent $event) {
- global $CFG, $DB;
-
- // Save the page content if the step failed.
- if (!empty($CFG->behat_faildump_path) &&
- $event->getResult() === StepEvent::FAILED) {
- $this->take_contentdump($event);
- }
-
- // Abort any open transactions to prevent subsequent tests hanging.
- // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
- // want to see a message in the behat output.
- if ($event->hasException()) {
- if ($DB && $DB->is_transaction_started()) {
- $DB->force_transaction_rollback();
- }
- }
- }
-
/**
* Executed after scenario having switch window to restart session.
* This is needed to close all extra browser windows and starting
* one browser window.
*
- * @param ScenarioEvent $event event fired after scenario.
+ * @param AfterScenarioScope $event event fired after scenario.
* @AfterScenario @_switch_window
*/
- public function after_scenario_switchwindow(ScenarioEvent $event) {
- for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count) {
+ public function after_scenario_switchwindow(AfterScenarioScope $event) {
+ for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count++) {
try {
$this->getSession()->restart();
break;
* Take screenshot when a step fails.
*
* @throws Exception
- * @param StepEvent $event
+ * @param AfterStepScope $scope
*/
- protected function take_screenshot(StepEvent $event) {
+ protected function take_screenshot(AfterStepScope $scope) {
// Goutte can't save screenshots.
if (!$this->running_javascript()) {
return false;
}
- list ($dir, $filename) = $this->get_faildump_filename($event, 'png');
+ list ($dir, $filename) = $this->get_faildump_filename($scope, 'png');
$this->saveScreenshot($filename, $dir);
}
* Take a dump of the page content when a step fails.
*
* @throws Exception
- * @param StepEvent $event
+ * @param AfterStepScope $scope
*/
- protected function take_contentdump(StepEvent $event) {
- list ($dir, $filename) = $this->get_faildump_filename($event, 'html');
+ protected function take_contentdump(AfterStepScope $scope) {
+ list ($dir, $filename) = $this->get_faildump_filename($scope, 'html');
$fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w');
fwrite($fh, $this->getSession()->getPage()->getContent());
*
* This is used for content such as the DOM, and screenshots.
*
- * @param StepEvent $event
+ * @param AfterStepScope $scope
* @param String $filetype The file suffix to use. Limited to 4 chars.
*/
- protected function get_faildump_filename(StepEvent $event, $filetype) {
+ protected function get_faildump_filename(AfterStepScope $scope, $filetype) {
global $CFG;
// All the contentdumps should be in the same parent dir.
// The scenario title + the failed step text.
// We want a i-am-the-scenario-title_i-am-the-failed-step.$filetype format.
- $filename = $event->getStep()->getParent()->getTitle() . '_' . $event->getStep()->getText();
+ $filename = $scope->getFeature()->getTitle() . '_' . $scope->getStep()->getText();
$filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename);
- // File name limited to 255 characters. Leaving 4 chars for the file
+ // File name limited to 255 characters. Leaving 5 chars for line number and 4 chars for the file.
// extension as we allow .png for images and .html for DOM contents.
- $filename = substr($filename, 0, 250) . '.' . $filetype;
+ $filename = substr($filename, 0, 245) . '_' . $scope->getStep()->getLine() . '.' . $filetype;
return array($dir, $filename);
}
- /**
- * Waits for all the JS to be loaded.
- *
- * @throws \Exception
- * @throws NoSuchWindow
- * @throws UnknownError
- * @return bool True or false depending whether all the JS is loaded or not.
- */
- protected function wait_for_pending_js() {
-
- // We don't use behat_base::spin() here as we don't want to end up with an exception
- // if the page & JSs don't finish loading properly.
- for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
- $pending = '';
- try {
- $jscode =
- 'if (typeof M === "undefined") {
- if (document.readyState === "complete") {
- return "";
- } else {
- return "incomplete";
- }
- } else if (' . self::PAGE_READY_JS . ') {
- return "";
- } else {
- return M.util.pending_js.join(":");
- }';
- $pending = $this->getSession()->evaluateScript($jscode);
- } catch (NoSuchWindow $nsw) {
- // We catch an exception here, in case we just closed the window we were interacting with.
- // No javascript is running if there is no window right?
- $pending = '';
- } catch (UnknownError $e) {
- // M is not defined when the window or the frame don't exist anymore.
- if (strstr($e->getMessage(), 'M is not defined') != false) {
- $pending = '';
- }
- }
-
- // If there are no pending JS we stop waiting.
- if ($pending === '') {
- return true;
- }
-
- // 0.1 seconds.
- usleep(100000);
- }
-
- // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
- // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
- // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
- // number of JS pending code and JS completed code will not match and we will reach this point.
- throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
- ' seconds. There is a Javascript error or the code is extremely slow.');
- }
-
/**
* Internal step definition to find exceptions, debugging() messages and PHP debug messages.
*
}
/**
- * Throws an exception after appending an extra info text.
+ * Stops execution because of some exception.
*
- * @throws Exception
- * @param UnknownError $exception
+ * @param string $exception
* @return void
*/
- protected function throw_unknown_exception(UnknownError $exception) {
+ protected function stop_execution($exception) {
$text = get_string('unknownexceptioninfo', 'tool_behat');
- throw new Exception($text . PHP_EOL . $exception->getMessage());
+ echo $text . PHP_EOL . $exception . PHP_EOL;
+ exit(1);
}
}
require_once(__DIR__ . '/../../behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given;
+use Moodle\BehatExtension\Context\Step\When;
use Behat\Mink\Exception\ExpectationException as ExpectationException;
use Behat\Mink\Exception\DriverException as DriverException;
-use Behat\Behat\Context\Step\When as When;
/**
* Steps definitions to navigate through the navigation tree nodes.
if ($parentnodes[0] === $siteadminstr) {
// We don't know if there if Site admin is already expanded so
// don't wait, it is non-JS and we already waited for the DOM.
- if ($siteadminlink = $this->getSession()->getPage()->find('named', array('link', "'" . $siteadminstr . "'"))) {
+ $siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'"));
+ if ($siteadminlink) {
$siteadminlink->click();
}
}
}
return $node;
}
+
+ /**
+ * Step to open the navigation bar if it is needed.
+ *
+ * The top log in and log out links are hidden when middle or small
+ * size windows (or devices) are used. This step returns a step definition
+ * clicking to expand the navbar if it is hidden.
+ *
+ * @Given /^I expand navigation bar$/
+ */
+ public function get_expand_navbar_step() {
+
+ // Checking if we need to click the navbar button to show the navigation menu, it
+ // is hidden by default when using clean theme and a medium or small screen size.
+
+ // The DOM and the JS should be all ready and loaded. Running without spinning
+ // as this is a widely used step and we can not spend time here trying to see
+ // a DOM node that is not always there (at the moment clean is not even the
+ // default theme...).
+ $navbuttonjs = "return (
+ Y.one('.btn-navbar') &&
+ Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
+ )";
+
+ // Adding an extra click we need to show the 'Log in' link.
+ if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
+ return false;
+ }
+
+ return new Given('I click on ".btn-navbar" "css_element"');
+ }
}
require_once(__DIR__ . '/../../behat/behat_base.php');
use Behat\Mink\Exception\ExpectationException as ExpectationException,
- Behat\Behat\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
* Transformations applicable to TableNode arguments should also
* be applied, adding them in a different method for Behat API restrictions.
*
- * @Transform /^table:(.*)/
+ * @Transform table:Surname,My Surname $NASTYSTRING2
* @param TableNode $tablenode
* @return TableNode The transformed table
*/
- public function tablenode_transformations(TableNode $tablenode) {
+ public function prefixed_tablenode_transformations(TableNode $tablenode) {
+ return $this->tablenode_transformations($tablenode);
+ }
+ /**
+ * Transformations for TableNode arguments.
+ *
+ * Transformations applicable to TableNode arguments should also
+ * be applied, adding them in a different method for Behat API restrictions.
+ *
+ * @Transform table:Surname,$NASTYSTRING1
+ * @param TableNode $tablenode
+ * @return TableNode The transformed table
+ */
+ public function tablenode_transformations(TableNode $tablenode) {
// Walk through all values including the optional headers.
$rows = $tablenode->getRows();
foreach ($rows as $rowkey => $row) {
}
// Return the transformed TableNode.
- $tablenode->setRows($rows);
+ unset($tablenode);
+ $tablenode = new TableNode($rows);
+
return $tablenode;
}
$this->assertEventContextNotUsed($event);
}
+ /**
+ * A data provider for testing email messageid
+ */
+ public function generate_email_messageid_provider() {
+ return array(
+ 'nopath' => array(
+ 'wwwroot' => 'http://www.example.com',
+ 'ids' => array(
+ 'a-custom-id' => '<a-custom-id@www.example.com>',
+ 'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash@www.example.com>',
+ ),
+ ),
+ 'path' => array(
+ 'wwwroot' => 'http://www.example.com/path/subdir',
+ 'ids' => array(
+ 'a-custom-id' => '<a-custom-id/path/subdir@www.example.com>',
+ 'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash/path/subdir@www.example.com>',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Test email message id generation
+ *
+ * @dataProvider generate_email_messageid_provider
+ *
+ * @param string $wwwroot The wwwroot
+ * @param array $msgids An array of msgid local parts and the final result
+ */
+ public function test_generate_email_messageid($wwwroot, $msgids) {
+ global $CFG;
+
+ $this->resetAfterTest();
+ $CFG->wwwroot = $wwwroot;
+
+ foreach ($msgids as $local => $final) {
+ $this->assertEquals($final, generate_email_messageid($local));
+ }
+ }
+ /**
+ * A data provider for testing email diversion
+ */
public function diverted_emails_provider() {
return array(
'nodiverts' => array(
}
/**
+ * Test email diversion
+ *
* @dataProvider diverted_emails_provider
+ *
+ * @param string $divertallemailsto An optional email address
+ * @param string $divertallemailsexcept An optional exclusion list
+ * @param array $addresses An array of test addresses
+ * @param boolean $expected Expected result
*/
public function test_email_should_be_diverted($divertallemailsto, $divertallemailsexcept, $addresses, $expected) {
global $CFG;
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
And "Delete" "link" should exist in the "#message_1" "css_element"
And "Delete" "link" should exist in the "#message_2" "css_element"
# Confirm that there is a confirmation box before deleting, and that when we cancel the messages remain.
- And I hover "#message_2" "css_element"
+ And I click on "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Cancel"
And I should see "Hey bud, what's happening?"
And I should see "Whoops, forgot to mention that I drank all your beers. Lol."
# Confirm we can delete a message and then can no longer see it.
- And I hover "#message_2" "css_element"
+ And I click on "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Delete"
And I should see "Hey bud, what's happening?"
And "Delete" "link" should exist in the "#message_3" "css_element"
And "Delete" "link" should exist in the "#message_4" "css_element"
# Now, delete one of the messages that User 1 sent and one by User 2.
- And I hover "#message_1" "css_element"
+ And I click on "#message_1" "css_element"
And I click on "Delete" "link" in the "#message_1" "css_element"
And I press "Delete"
- And I hover "#message_2" "css_element"
+ And I click on "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Delete"
# Confirm that the messages are no longer listed.
# Send a message from the admin to User 1
And I send "Hey there, this is the all-powerful administrator. Obey my commands." message to "User 1" user
# Check the admin is still able to delete messages.
- And I hover "#message_1" "css_element"
+ And I click on "#message_1" "css_element"
And I click on "Delete" "link" in the "#message_1" "css_element"
And I press "Delete"
And I should not see "Hey there, this is the all-powerful administrator. Obey my commands."
And I follow "Course 1"
And I follow "Test assignment name"
And I follow "View/grade all submissions"
+ And I click on "Edit" "link" in the "Submitted for grading" "table_row"
And I click on "Grade" "link" in the "Submitted for grading" "table_row"
And I follow "Launch PDF editor..."
And I change window size to "large"
And I follow "Course 1"
And I follow "Test assignment name"
And I follow "View/grade all submissions"
+ And I click on "Edit" "link" in the "Student 2" "table_row"
And I click on "Grade" "link" in the "Student 2" "table_row"
And I follow "Launch PDF editor..."
And I change window size to "large"
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
-
/**
* Steps definitions related with the editpdf.
*
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| assignfeedback_comments_enabled | 1 |
- | assignfeedback_comments_commentinline | 1 |
| assignfeedback_file_enabled | 1 |
+ | assignfeedback_comments_commentinline | 1 |
And I log out
And I log in as "student1"
And I follow "Course 1"
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Choice activity definitions.
* @return array
*/
public function I_choose_options_from_activity($option, $choiceactivity) {
- // Escaping again the strings as backslashes have been removed by the automatic transformation.
- $return = array(new Given('I follow "' . $this->escape($choiceactivity) . '"'));
+ // Get Behat general and forms contexts.
+ $behatgeneral = behat_context_helper::get('behat_general');
+ $behatforms = behat_context_helper::get('behat_forms');
+
+ // Go to choice activity.
+ $behatgeneral->click_link($this->escape($choiceactivity));
+
+ // Wait for page to be loaded.
+ $this->wait_for_pending_js();
+
+ // Set all options.
$options = explode('","', trim($option, '"'));
foreach ($options as $option) {
- $return[] = new Given('I set the field "' . $this->escape($option) . '" to "1"');
+ $behatforms->i_set_the_field_to($this->escape($option), '1');
}
- $return[] = new Given('I press "' . get_string('savemychoice', 'choice') . '"');
- return $return;
+
+ // Save choice.
+ $behatforms->press_button(get_string('savemychoice', 'choice'));
}
}
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
+use Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When,
Behat\Gherkin\Node\TableNode as TableNode;
/**
* Database-related steps definitions.
| Required | yes |
| Options | Required Checkbox Option 1 |
And I follow "Fields"
- And I set the field "newtype" to "Checkbox"
- And I click on "Go" "button" in the ".fieldadd" "css_element"
+ And I select "Checkbox" from the "newtype" singleselect
And I set the following fields to these values:
| Field name | Required Two-Option Checkbox |
| Field description | Required Two-Option Checkbox |
| Required | yes |
| Options | Option 1 |
And I follow "Fields"
- And I set the field "newtype" to "Multimenu"
- And I click on "Go" "button" in the ".fieldadd" "css_element"
+ And I select "Multimenu" from the "newtype" singleselect
And I set the following fields to these values:
| Field name | Required Two-Option Multimenu |
| Field description | Required Two-Option Multimenu |
$additem = $this->escape(get_string('add_item', 'feedback'));
$rv[] = new Given("I select \"{$questiontype}\" from the \"{$additem}\" singleselect");
- $newdata = new TableNode();
$rows = $questiondata->getRows();
+ $modifiedrows = array();
foreach ($rows as $row) {
foreach ($row as $key => $value) {
$row[$key] = preg_replace('|\\\\n|', "\n", $value);
}
- $newdata->addRow($row);
+ $modifiedrows[] = $row;
}
+ $newdata = new TableNode($modifiedrows);
+
$rv[] = new Given('I set the following fields to these values:', $newdata);
$saveitem = $this->escape(get_string('save_item', 'feedback'));
And I should see "C1" in the "(info)" "table"
And I should see "my long answer" in the "(longertext)" "table"
And I should see "lots of feedbacks" in the "(longertext)" "table"
- #And I should see "1 (50.00 %)" in the "option a:" "table_row" // TODO: MDL-46891
- #And I should see "1 (50.00 %)" in the "option b:" "table_row" // TODO: MDL-46891
And I should see "2 (100.00 %)" in the "option d:" "table_row"
And I should see "1 (50.00 %)" in the "option e:" "table_row"
And I should see "1 (50.00 %)" in the "option f:" "table_row"
And I should see "Average: 53.00" in the "(numeric)" "table"
And I should see "no way" in the "(shorttext)" "table"
And I should see "hello" in the "(shorttext)" "table"
- And I log out
Scenario: Create different types of questions in feedback with javascript disabled
+ And I log out
@javascript
Scenario: Create different types of questions in feedback with javascript enabled
+ And I should see "1 (50.00 %)" in the "option a:" "table_row"
+ And I should see "1 (50.00 %)" in the "option b:" "table_row"
+ And I log out
*
* @param int $postid The ID of the forum post we are notifying the user about
* @param int $usertoid The ID of the user being notified
- * @param string $hostname The server's hostname
* @return string A unique message-id
*/
-function forum_get_email_message_id($postid, $usertoid, $hostname) {
- return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
+function forum_get_email_message_id($postid, $usertoid) {
+ return generate_email_messageid(hash('sha256', $postid . 'to' . $usertoid));
}
/**
if ($users && $posts) {
- $urlinfo = parse_url($CFG->wwwroot);
- $hostname = $urlinfo['host'];
-
foreach ($users as $userto) {
// Terminate if processing of any account takes longer than 2 minutes.
core_php_time_limit::raise(120);
$userfrom->customheaders = array (
// Headers to make emails easier to track.
- 'List-Id: "' . $cleanforumname . '" <moodleforum' . $forum->id . '@' . $hostname.'>',
+ 'List-Id: "' . $cleanforumname . '" ' . generate_email_messageid('moodleforum' . $forum->id),
'List-Help: ' . $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id,
- 'Message-ID: ' . forum_get_email_message_id($post->id, $userto->id, $hostname),
+ 'Message-ID: ' . forum_get_email_message_id($post->id, $userto->id),
'X-Course-Id: ' . $course->id,
'X-Course-Name: ' . format_string($course->fullname, true),
$a->courseshortname = $data->get_coursename();
$postsubject = html_to_text(get_string('postmailsubject', 'forum', $a), 0);
- $rootid = forum_get_email_message_id($discussion->firstpost, $userto->id, $hostname);
+ $rootid = forum_get_email_message_id($discussion->firstpost, $userto->id);
if ($post->parent) {
// This post is a reply, so add reply header (RFC 2822).
- $parentid = forum_get_email_message_id($post->parent, $userto->id, $hostname);
+ $parentid = forum_get_email_message_id($post->parent, $userto->id);
$userfrom->customheaders[] = "In-Reply-To: $parentid";
// If the post is deeply nested we also reference the parent message id and
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
* Forum-related steps definitions.
This files describes API changes in /mod/forum/*,
information provided here is intended especially for developers.
+=== 3.1 ===
+ * The inteface to forum_get_email_message_id() has changed and no longer needs the $host argument.
+
=== 3.0 ===
* External function get_forums_by_courses now returns and additional field "cancreatediscussions" that indicates if the user
can create discussions in the forum.
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
use moodle\mod\lti as lti;
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
+require_once($CFG->libdir.'/weblib.php');
define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');
} else {
$requestparams['tool_consumer_instance_name'] = get_site()->fullname;
}
+ $requestparams['tool_consumer_instance_description'] = html_to_text(get_site()->summary, 0);
return $requestparams;
}
* Test view_lti
*/
public function test_get_tool_launch_data() {
- global $USER;
+ global $USER, $SITE;
+
+ $SITE->summary = "This is a <b>long</b> front page summary with html, it should exceed more than seventy-five characters.";
$result = mod_lti_external::get_tool_launch_data($this->lti->id);
$result = external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
// Basic test, the function returns what it's expected.
self::assertEquals($this->lti->toolurl, $result['endpoint']);
- self::assertCount(35, $result['parameters']);
+ self::assertCount(36, $result['parameters']);
// Check some parameters.
$parameters = array();
self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
self::assertEquals(fullname($USER), $parameters['lis_person_name_full']);
self::assertEquals($USER->username, $parameters['ext_user_username']);
+ self::assertEquals("This is a LONG front page summary with html, it should exceed more than seventy-five characters.",
+ $parameters['tool_consumer_instance_description']);
}
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../question/tests/behat/behat_question_base.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Mink\Exception\ExpectationException as ExpectationException;
+use Moodle\BehatExtension\Context\Step\Given as Given,
+ Behat\Gherkin\Node\TableNode as TableNode;
+
+use Behat\Mink\Exception\ExpectationException as ExpectationException;
/**
* Steps definitions related to mod_quiz.
}
$rows = $data->getRows();
array_unshift($rows, $headings);
- $data->setRows($rows);
+ $data = new TableNode($rows);
}
// Add the questions.
| Attempts allowed | 3 |
And I press "Save"
And I navigate to "Edit settings" node in "Quiz administration"
+ And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And I set the field "Attempts allowed" to "2"
And I press "Save"
And I navigate to "Edit settings" node in "Quiz administration"
+ And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
| Attempts allowed | Unlimited |
And I press "Save"
And I navigate to "Edit settings" node in "Quiz administration"
+ And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
);
}
+ /**
+ * Describes the parameters for get_subwiki_pages.
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.1
+ */
+ public static function get_subwiki_pages_parameters() {
+ return new external_function_parameters (
+ array(
+ 'wikiid' => new external_value(PARAM_INT, 'Wiki instance ID.'),
+ 'groupid' => new external_value(PARAM_INT, 'Subwiki\'s group ID, -1 means current group. It will be ignored'
+ . ' if the wiki doesn\'t use groups.', VALUE_DEFAULT, -1),
+ 'userid' => new external_value(PARAM_INT, 'Subwiki\'s user ID, 0 means current user. It will be ignored'
+ .' in collaborative wikis.', VALUE_DEFAULT, 0),
+ 'options' => new external_single_structure(
+ array(
+ 'sortby' => new external_value(PARAM_ALPHA,
+ 'Field to sort by (id, title, ...).', VALUE_DEFAULT, 'title'),
+ 'sortdirection' => new external_value(PARAM_ALPHA,
+ 'Sort direction: ASC or DESC.', VALUE_DEFAULT, 'ASC'),
+ 'includecontent' => new external_value(PARAM_INT,
+ 'Include each page contents or not.', VALUE_DEFAULT, 1),
+ ), 'Options', VALUE_DEFAULT, array()),
+ )
+ );
+ }
+
+ /**
+ * Returns the list of pages from a specific subwiki.
+ *
+ * @param int $wikiid The wiki instance ID.
+ * @param int $groupid The group ID. If not defined, use current group.
+ * @param int $userid The user ID. If not defined, use current user.
+ * @param array $options Several options like sort by, sort direction, ...
+ * @return array Containing a list of warnings and a list of pages.
+ * @since Moodle 3.1
+ */
+ public static function get_subwiki_pages($wikiid, $groupid = -1, $userid = 0, $options = array()) {
+ global $USER, $DB;
+
+ $returnedpages = array();
+ $warnings = array();
+
+ $params = self::validate_parameters(self::get_subwiki_pages_parameters(),
+ array(
+ 'wikiid' => $wikiid,
+ 'groupid' => $groupid,
+ 'userid' => $userid,
+ 'options' => $options
+ )
+ );
+
+ // Get wiki instance.
+ if (!$wiki = wiki_get_wiki($params['wikiid'])) {
+ throw new moodle_exception('incorrectwikiid', 'wiki');
+ }
+ list($course, $cm) = get_course_and_cm_from_instance($wiki, 'wiki');
+ $context = context_module::instance($cm->id);
+ self::validate_context($context);
+
+ // Determine group.
+ $groupmode = groups_get_activity_groupmode($cm);
+ if ($groupmode == NOGROUPS) {
+ $groupid = 0;
+ } else if ($params['groupid'] == -1) {
+ // Use current group.
+ $groupid = groups_get_activity_group($cm);
+ $groupid = !empty($groupid) ? $groupid : 0;
+ } else {
+ $groupid = $params['groupid'];
+ }
+
+ // Determine user.
+ if ($wiki->wikimode == 'collaborative') {
+ // Collaborative wikis don't use userid in subwikis.
+ $userid = 0;
+ } else if (empty($params['userid'])) {
+ // Use current user.
+ $userid = $USER->id;
+ } else {
+ $userid = $params['userid'];
+ }
+
+ // Get subwiki based on group and user.
+ if (!$subwiki = wiki_get_subwiki_by_group($cm->instance, $groupid, $userid)) {
+ // The subwiki doesn't exist.
+ // Validate if user is valid.
+ if ($userid != 0 && $userid != $USER->id && !$user = $DB->get_record('user', array('id' => $userid))) {
+ throw new moodle_exception('invaliduserid', 'error');
+ }
+
+ // Validate that groupid is valid.
+ if ($groupid != 0 && !groups_group_exists($groupid)) {
+ throw new moodle_exception('cannotfindgroup', 'error');
+ }
+
+ // Valid data but subwiki not found. We'll simulate a subwiki object to check if the user would be able to see it
+ // if it existed. If he's able to see it then we'll return an empty array because the subwiki has no pages.
+ $subwiki = new stdClass();
+ $subwiki->wikiid = $wiki->id;
+ $subwiki->userid = $userid;
+ $subwiki->groupid = $groupid;
+
+ // Check that the user can view the subwiki. This function checks capabilities.
+ if (!wiki_user_can_view($subwiki, $wiki)) {
+ throw new moodle_exception('cannotviewpage', 'wiki');
+ }
+ } else {
+ // Check that the user can view the subwiki. This function checks capabilities.
+ if (!wiki_user_can_view($subwiki, $wiki)) {
+ throw new moodle_exception('cannotviewpage', 'wiki');
+ }
+
+ // Set sort param.
+ $options = $params['options'];
+ if (!empty($options['sortby'])) {
+ if ($options['sortdirection'] != 'ASC' && $options['sortdirection'] != 'DESC') {
+ // Invalid sort direction. Use default.
+ $options['sortdirection'] = 'ASC';
+ }
+ $sort = $options['sortby'] . ' ' . $options['sortdirection'];
+ }
+
+ $pages = wiki_get_page_list($subwiki->id, $sort);
+ $caneditpages = wiki_user_can_edit($subwiki);
+ $firstpage = wiki_get_first_page($subwiki->id);
+
+ foreach ($pages as $page) {
+ $retpage = array(
+ 'id' => $page->id,
+ 'subwikiid' => $page->subwikiid,
+ 'title' => external_format_string($page->title, $context->id),
+ 'timecreated' => $page->timecreated,
+ 'timemodified' => $page->timemodified,
+ 'timerendered' => $page->timerendered,
+ 'userid' => $page->userid,
+ 'pageviews' => $page->pageviews,
+ 'readonly' => $page->readonly,
+ 'caneditpage' => $caneditpages,
+ 'firstpage' => $page->id == $firstpage->id
+ );
+
+ if ($options['includecontent']) {
+ // Refresh page cached content if needed.
+ if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) {
+ if ($content = wiki_refresh_cachedcontent($page)) {
+ $page = $content['page'];
+ }
+ }
+
+ list($retpage['cachedcontent'], $retpage['contentformat']) = external_format_text(
+ $page->cachedcontent, FORMAT_HTML, $context->id, 'mod_wiki', 'attachments', $subwiki->id);
+ }
+
+ $returnedpages[] = $retpage;
+ }
+
+ }
+
+ $result = array();
+ $result['pages'] = $returnedpages;
+ $result['warnings'] = $warnings;
+ return $result;
+ }
+
+ /**
+ * Describes the get_subwiki_pages return value.
+ *
+ * @return external_single_structure
+ * @since Moodle 3.1
+ */
+ public static function get_subwiki_pages_returns() {
+
+ return new external_single_structure(
+ array(
+ 'pages' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'Page ID.'),
+ 'subwikiid' => new external_value(PARAM_INT, 'Page\'s subwiki ID.'),
+ 'title' => new external_value(PARAM_RAW, 'Page title.'),
+ 'timecreated' => new external_value(PARAM_INT, 'Time of creation.'),
+ 'timemodified' => new external_value(PARAM_INT, 'Time of last modification.'),
+ 'timerendered' => new external_value(PARAM_INT, 'Time of last renderization.'),
+ 'userid' => new external_value(PARAM_INT, 'ID of the user that last modified the page.'),
+ 'pageviews' => new external_value(PARAM_INT, 'Number of times the page has been viewed.'),
+ 'readonly' => new external_value(PARAM_INT, '1 if readonly, 0 otherwise.'),
+ 'caneditpage' => new external_value(PARAM_BOOL, 'True if user can edit the page.'),
+ 'firstpage' => new external_value(PARAM_BOOL, 'True if it\'s the first page.'),
+ 'cachedcontent' => new external_value(PARAM_RAW, 'Page contents.', VALUE_OPTIONAL),
+ 'contentformat' => new external_format_value('cachedcontent', VALUE_OPTIONAL),
+ ), 'Pages'
+ )
+ ),
+ 'warnings' => new external_warnings(),
+ )
+ );
+ }
+
+ /**
+ * Describes the parameters for get_page_contents.
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.1
+ */
+ public static function get_page_contents_parameters() {
+ return new external_function_parameters (
+ array(
+ 'pageid' => new external_value(PARAM_INT, 'Page ID.')
+ )
+ );
+ }
+
+ /**
+ * Get a page contents.
+ *
+ * @param int $pageid The page ID.
+ * @return array of warnings and page data.
+ * @since Moodle 3.1
+ */
+ public static function get_page_contents($pageid) {
+
+ $params = self::validate_parameters(self::get_page_contents_parameters(),
+ array(
+ 'pageid' => $pageid
+ )
+ );
+ $warnings = array();
+
+ // Get wiki page.
+ if (!$page = wiki_get_page($params['pageid'])) {
+ throw new moodle_exception('incorrectpageid', 'wiki');
+ }
+
+ // Get wiki instance.
+ if (!$wiki = wiki_get_wiki_from_pageid($params['pageid'])) {
+ throw new moodle_exception('incorrectwikiid', 'wiki');
+ }
+
+ // Permission validation.
+ $cm = get_coursemodule_from_instance('wiki', $wiki->id, $wiki->course);
+ $context = context_module::instance($cm->id);
+ self::validate_context($context);
+
+ // Check if user can view this wiki.
+ if (!$subwiki = wiki_get_subwiki($page->subwikiid)) {
+ throw new moodle_exception('incorrectsubwikiid', 'wiki');
+ }
+ if (!wiki_user_can_view($subwiki, $wiki)) {
+ throw new moodle_exception('cannotviewpage', 'wiki');
+ }
+
+ $returnedpage = array();
+ $returnedpage['id'] = $page->id;
+ $returnedpage['wikiid'] = $wiki->id;
+ $returnedpage['subwikiid'] = $page->subwikiid;
+ $returnedpage['groupid'] = $subwiki->groupid;
+ $returnedpage['userid'] = $subwiki->userid;
+ $returnedpage['title'] = $page->title;
+
+ // Refresh page cached content if needed.
+ if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) {
+ if ($content = wiki_refresh_cachedcontent($page)) {
+ $page = $content['page'];
+ }
+ }
+
+ list($returnedpage['cachedcontent'], $returnedpage['contentformat']) = external_format_text(
+ $page->cachedcontent, FORMAT_HTML, $context->id, 'mod_wiki', 'attachments', $subwiki->id);
+ $returnedpage['caneditpage'] = wiki_user_can_edit($subwiki);
+
+ $result = array();
+ $result['page'] = $returnedpage;
+ $result['warnings'] = $warnings;
+ return $result;
+ }
+
+ /**
+ * Describes the get_page_contents return value.
+ *
+ * @return external_single_structure
+ * @since Moodle 3.1
+ */
+ public static function get_page_contents_returns() {
+ return new external_single_structure(
+ array(
+ 'page' => new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'Page ID.'),
+ 'wikiid' => new external_value(PARAM_INT, 'Page\'s wiki ID.'),
+ 'subwikiid' => new external_value(PARAM_INT, 'Page\'s subwiki ID.'),
+ 'groupid' => new external_value(PARAM_INT, 'Page\'s group ID.'),
+ 'userid' => new external_value(PARAM_INT, 'Page\'s user ID.'),
+ 'title' => new external_value(PARAM_RAW, 'Page title.'),
+ 'cachedcontent' => new external_value(PARAM_RAW, 'Page contents.'),
+ 'contentformat' => new external_format_value('cachedcontent', VALUE_OPTIONAL),
+ 'caneditpage' => new external_value(PARAM_BOOL, 'True if user can edit the page.')
+ ), 'Page'
+ ),
+ 'warnings' => new external_warnings()
+ )
+ );
+ }
+
}
'type' => 'read',
'capabilities' => 'mod/wiki:viewpage',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+ ),
+ 'mod_wiki_get_subwiki_pages' => array(
+ 'classname' => 'mod_wiki_external',
+ 'methodname' => 'get_subwiki_pages',
+ 'description' => 'Returns the list of pages for a specific subwiki.',
+ 'type' => 'read',
+ 'capabilities' => 'mod/wiki:viewpage',
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+ ),
+
+ 'mod_wiki_get_page_contents' => array(
+ 'classname' => 'mod_wiki_external',
+ 'methodname' => 'get_page_contents',
+ 'description' => 'Returns the contents of a page.',
+ 'type' => 'read',
+ 'capabilities' => 'mod/wiki:viewpage',
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
)
);
/**
* Get pages list in wiki
* @param int $swid sub wiki id
+ * @param string $sort How to sort the pages. By default, title ASC.
*/
-function wiki_get_page_list($swid) {
+function wiki_get_page_list($swid, $sort = 'title ASC') {
global $DB;
- $records = $DB->get_records('wiki_pages', array('subwikiid' => $swid), 'title ASC');
+ $records = $DB->get_records('wiki_pages', array('subwikiid' => $swid), $sort);
return $records;
}
// Create users.
$this->student = self::getDataGenerator()->create_user();
+ $this->student2 = self::getDataGenerator()->create_user();
$this->teacher = self::getDataGenerator()->create_user();
// Users enrolments.
$this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
+ $this->getDataGenerator()->enrol_user($this->student2->id, $this->course->id, $this->studentrole->id, 'manual');
$this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
- // Create first page.
+ // Create first pages.
$this->firstpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($this->wiki);
}
+ /**
+ * Create two collaborative wikis (separate/visible groups), 2 groups and a first page for each wiki and group.
+ */
+ private function create_collaborative_wikis_with_groups() {
+ // Create groups and add student to one of them.
+ if (!isset($this->group1)) {
+ $this->group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+ $this->getDataGenerator()->create_group_member(array('userid' => $this->student->id, 'groupid' => $this->group1->id));
+ $this->getDataGenerator()->create_group_member(array('userid' => $this->student2->id, 'groupid' => $this->group1->id));
+ }
+ if (!isset($this->group2)) {
+ $this->group2 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+ }
+
+ // Create two collaborative wikis.
+ $this->wikisep = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'groupmode' => SEPARATEGROUPS));
+ $this->wikivis = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'groupmode' => VISIBLEGROUPS));
+
+ // Create pages.
+ $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki');
+ $this->fpsepg1 = $wikigenerator->create_first_page($this->wikisep, array('group' => $this->group1->id));
+ $this->fpsepg2 = $wikigenerator->create_first_page($this->wikisep, array('group' => $this->group2->id));
+ $this->fpsepall = $wikigenerator->create_first_page($this->wikisep, array('group' => 0)); // All participants.
+ $this->fpvisg1 = $wikigenerator->create_first_page($this->wikivis, array('group' => $this->group1->id));
+ $this->fpvisg2 = $wikigenerator->create_first_page($this->wikivis, array('group' => $this->group2->id));
+ $this->fpvisall = $wikigenerator->create_first_page($this->wikivis, array('group' => 0)); // All participants.
+ }
+
+ /**
+ * Create two individual wikis (separate/visible groups), 2 groups and a first page for each wiki and group.
+ */
+ private function create_individual_wikis_with_groups() {
+ // Create groups and add student to one of them.
+ if (!isset($this->group1)) {
+ $this->group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+ $this->getDataGenerator()->create_group_member(array('userid' => $this->student->id, 'groupid' => $this->group1->id));
+ $this->getDataGenerator()->create_group_member(array('userid' => $this->student2->id, 'groupid' => $this->group1->id));
+ }
+ if (!isset($this->group2)) {
+ $this->group2 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+ }
+
+ // Create two individual wikis.
+ $this->wikisepind = $this->getDataGenerator()->create_module('wiki', array('course' => $this->course->id,
+ 'groupmode' => SEPARATEGROUPS, 'wikimode' => 'individual'));
+ $this->wikivisind = $this->getDataGenerator()->create_module('wiki', array('course' => $this->course->id,
+ 'groupmode' => VISIBLEGROUPS, 'wikimode' => 'individual'));
+
+ // Create pages. Student can only create pages in his groups.
+ $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki');
+ $this->setUser($this->teacher);
+ $this->fpsepg1indt = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+ $this->fpsepg2indt = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group2->id));
+ $this->fpsepallindt = $wikigenerator->create_first_page($this->wikisepind, array('group' => 0)); // All participants.
+ $this->fpvisg1indt = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+ $this->fpvisg2indt = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group2->id));
+ $this->fpvisallindt = $wikigenerator->create_first_page($this->wikivisind, array('group' => 0)); // All participants.
+
+ $this->setUser($this->student);
+ $this->fpsepg1indstu = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+ $this->fpvisg1indstu = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+
+ $this->setUser($this->student2);
+ $this->fpsepg1indstu2 = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+ $this->fpvisg1indstu2 = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+
+ }
+
/*
* Test get wikis by courses
*/
}
+ /**
+ * Test get_subwiki_pages using an invalid wiki instance.
+ */
+ public function test_get_subwiki_pages_invalid_instance() {
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages(0);
+ }
+
+ /**
+ * Test get_subwiki_pages using a user not enrolled in the course.
+ */
+ public function test_get_subwiki_pages_unenrolled_user() {
+ // Create and use the user.
+ $usernotenrolled = self::getDataGenerator()->create_user();
+ $this->setUser($usernotenrolled);
+
+ $this->setExpectedException('require_login_exception');
+ mod_wiki_external::get_subwiki_pages($this->wiki->id);
+ }
+
+ /**
+ * Test get_subwiki_pages using a hidden wiki as student.
+ */
+ public function test_get_subwiki_pages_hidden_wiki_as_student() {
+ // Create a hidden wiki and try to get the list of pages.
+ $hiddenwiki = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'visible' => false));
+
+ $this->setUser($this->student);
+ $this->setExpectedException('require_login_exception');
+ mod_wiki_external::get_subwiki_pages($hiddenwiki->id);
+ }
+
+ /**
+ * Test get_subwiki_pages without the viewpage capability.
+ */
+ public function test_get_subwiki_pages_without_viewpage_capability() {
+ // Prohibit capability = mod/wiki:viewpage on the course for students.
+ $contextcourse = context_course::instance($this->course->id);
+ assign_capability('mod/wiki:viewpage', CAP_PROHIBIT, $this->studentrole->id, $contextcourse->id);
+ accesslib_clear_all_caches_for_unit_testing();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wiki->id);
+ }
+
+ /**
+ * Test get_subwiki_pages using an invalid userid.
+ */
+ public function test_get_subwiki_pages_invalid_userid() {
+ // Create an individual wiki.
+ $indwiki = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($indwiki->id, 0, -10);
+ }
+
+ /**
+ * Test get_subwiki_pages using an invalid groupid.
+ */
+ public function test_get_subwiki_pages_invalid_groupid() {
+ // Create testing data.
+ $this->create_collaborative_wikis_with_groups();
+
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wikisep->id, -111);
+ }
+
+ /**
+ * Test get_subwiki_pages, check that a student can't see another user pages in an individual wiki without groups.
+ */
+ public function test_get_subwiki_pages_individual_student_see_other_user() {
+ // Create an individual wiki.
+ $indwiki = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->teacher->id);
+ }
+
+ /**
+ * Test get_subwiki_pages, check that a student can't get the pages from another group in
+ * a collaborative wiki using separate groups.
+ */
+ public function test_get_subwiki_pages_collaborative_separate_groups_student_see_other_group() {
+ // Create testing data.
+ $this->create_collaborative_wikis_with_groups();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group2->id);
+ }
+
+ /**
+ * Test get_subwiki_pages, check that a student can't get the pages from another group in
+ * an individual wiki using separate groups.
+ */
+ public function test_get_subwiki_pages_individual_separate_groups_student_see_other_group() {
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group2->id, $this->teacher->id);
+ }
+
+ /**
+ * Test get_subwiki_pages, check that a student can't get the pages from all participants in
+ * a collaborative wiki using separate groups.
+ */
+ public function test_get_subwiki_pages_collaborative_separate_groups_student_see_all_participants() {
+ // Create testing data.
+ $this->create_collaborative_wikis_with_groups();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wikisep->id, 0);
+ }
+
+ /**
+ * Test get_subwiki_pages, check that a student can't get the pages from all participants in
+ * an individual wiki using separate groups.
+ */
+ public function test_get_subwiki_pages_individual_separate_groups_student_see_all_participants() {
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_subwiki_pages($this->wikisepind->id, 0, $this->teacher->id);
+ }
+
+ /**
+ * Test get_subwiki_pages without groups and collaborative wiki.
+ */
+ public function test_get_subwiki_pages_collaborative() {
+
+ // Test user with full capabilities.
+ $this->setUser($this->student);
+
+ // Set expected result: first page.
+ $expectedpages = array();
+ $expectedfirstpage = (array) $this->firstpage;
+ $expectedfirstpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
+ $expectedfirstpage['firstpage'] = true;
+ $expectedfirstpage['contentformat'] = 1;
+ $expectedpages[] = $expectedfirstpage;
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that groupid param is ignored since the wiki isn't using groups.
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 1234);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that userid param is ignored since the wiki is collaborative.
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 1234, 1234);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Add a new page to the wiki and test again. We'll use a custom title so it's returned first if sorted by title.
+ $newpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_page(
+ $this->wiki, array('title' => 'AAA'));
+
+ $expectednewpage = (array) $newpage;
+ $expectednewpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
+ $expectednewpage['firstpage'] = false;
+ $expectednewpage['contentformat'] = 1;
+ array_unshift($expectedpages, $expectednewpage); // Add page to the beginning since it orders by title by default.
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Now we'll order by ID. Since first page was created first it'll have a lower ID.
+ $expectedpages = array($expectedfirstpage, $expectednewpage);
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 0, 0, array('sortby' => 'id'));
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that WS doesn't return page content if includecontent is false.
+ unset($expectedpages[0]['cachedcontent']);
+ unset($expectedpages[0]['contentformat']);
+ unset($expectedpages[1]['cachedcontent']);
+ unset($expectedpages[1]['contentformat']);
+ $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 0, 0, array('sortby' => 'id', 'includecontent' => 0));
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_subwiki_pages without groups.
+ */
+ public function test_get_subwiki_pages_individual() {
+
+ // Create an individual wiki to test userid param.
+ $indwiki = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+ // Perform a request before creating any page to check that an empty array is returned if subwiki doesn't exist.
+ $result = mod_wiki_external::get_subwiki_pages($indwiki->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals(array(), $result['pages']);
+
+ // Create first pages as student and teacher.
+ $this->setUser($this->student);
+ $indfirstpagestudent = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($indwiki);
+ $this->setUser($this->teacher);
+ $indfirstpageteacher = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($indwiki);
+
+ // Check that teacher can get his pages.
+ $expectedteacherpage = (array) $indfirstpageteacher;
+ $expectedteacherpage['caneditpage'] = true;
+ $expectedteacherpage['firstpage'] = true;
+ $expectedteacherpage['contentformat'] = 1;
+ $expectedpages = array($expectedteacherpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->teacher->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that the teacher can see the student's pages.
+ $expectedstudentpage = (array) $indfirstpagestudent;
+ $expectedstudentpage['caneditpage'] = true;
+ $expectedstudentpage['firstpage'] = true;
+ $expectedstudentpage['contentformat'] = 1;
+ $expectedpages = array($expectedstudentpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->student->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Now check that student can get his pages.
+ $this->setUser($this->student);
+
+ $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->student->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that not using userid uses current user.
+ $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_subwiki_pages with groups and collaborative wikis.
+ */
+ public function test_get_subwiki_pages_separate_groups_collaborative() {
+
+ // Create testing data.
+ $this->create_collaborative_wikis_with_groups();
+
+ $this->setUser($this->student);
+
+ // Try to get pages from a valid group in separate groups wiki.
+
+ $expectedpage = (array) $this->fpsepg1;
+ $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group1->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Let's check that not using groupid returns the same result (current group).
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that teacher can view a group pages without belonging to it.
+ $this->setUser($this->teacher);
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group1->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that teacher can get the pages from all participants.
+ $expectedpage = (array) $this->fpsepall;
+ $expectedpage['caneditpage'] = true;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, 0);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_subwiki_pages with groups and collaborative wikis.
+ */
+ public function test_get_subwiki_pages_visible_groups_collaborative() {
+
+ // Create testing data.
+ $this->create_collaborative_wikis_with_groups();
+
+ $this->setUser($this->student);
+
+ // Try to get pages from a valid group in visible groups wiki.
+
+ $expectedpage = (array) $this->fpvisg1;
+ $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group1->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that with visible groups a student can get the pages of groups he doesn't belong to.
+ $expectedpage = (array) $this->fpvisg2;
+ $expectedpage['caneditpage'] = false; // User doesn't belong to group so he can't edit the page.
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group2->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that with visible groups a student can get the pages of all participants.
+ $expectedpage = (array) $this->fpvisall;
+ $expectedpage['caneditpage'] = false;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, 0);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_subwiki_pages with groups and individual wikis.
+ */
+ public function test_get_subwiki_pages_separate_groups_individual() {
+
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ $this->setUser($this->student);
+
+ // Check that student can retrieve his pages from separate wiki.
+ $expectedpage = (array) $this->fpsepg1indstu;
+ $expectedpage['caneditpage'] = true;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that not using userid uses current user.
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that the teacher can see the student pages.
+ $this->setUser($this->teacher);
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that a student can see pages from another user that belongs to his groups.
+ $this->setUser($this->student);
+ $expectedpage = (array) $this->fpsepg1indstu2;
+ $expectedpage['caneditpage'] = false;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student2->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_subwiki_pages with groups and individual wikis.
+ */
+ public function test_get_subwiki_pages_visible_groups_individual() {
+
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ $this->setUser($this->student);
+
+ // Check that student can retrieve his pages from visible wiki.
+ $expectedpage = (array) $this->fpvisg1indstu;
+ $expectedpage['caneditpage'] = true;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group1->id, $this->student->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that student can see teacher pages in visible groups, even if the user doesn't belong to the group.
+ $expectedpage = (array) $this->fpvisg2indt;
+ $expectedpage['caneditpage'] = false;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group2->id, $this->teacher->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+
+ // Check that with visible groups a student can get the pages of all participants.
+ $expectedpage = (array) $this->fpvisallindt;
+ $expectedpage['caneditpage'] = false;
+ $expectedpage['firstpage'] = true;
+ $expectedpage['contentformat'] = 1;
+ $expectedpages = array($expectedpage);
+
+ $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, 0, $this->teacher->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+ $this->assertEquals($expectedpages, $result['pages']);
+ }
+
+ /**
+ * Test get_page_contents using an invalid pageid.
+ */
+ public function test_get_page_contents_invalid_pageid() {
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_page_contents(0);
+ }
+
+ /**
+ * Test get_page_contents using a user not enrolled in the course.
+ */
+ public function test_get_page_contents_unenrolled_user() {
+ // Create and use the user.
+ $usernotenrolled = self::getDataGenerator()->create_user();
+ $this->setUser($usernotenrolled);
+
+ $this->setExpectedException('require_login_exception');
+ mod_wiki_external::get_page_contents($this->firstpage->id);
+ }
+
+ /**
+ * Test get_page_contents using a hidden wiki as student.
+ */
+ public function test_get_page_contents_hidden_wiki_as_student() {
+ // Create a hidden wiki and try to get a page contents.
+ $hiddenwiki = $this->getDataGenerator()->create_module('wiki',
+ array('course' => $this->course->id, 'visible' => false));
+ $hiddenpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_page($hiddenwiki);
+
+ $this->setUser($this->student);
+ $this->setExpectedException('require_login_exception');
+ mod_wiki_external::get_page_contents($hiddenpage->id);
+ }
+
+ /**
+ * Test get_page_contents without the viewpage capability.
+ */
+ public function test_get_page_contents_without_viewpage_capability() {
+ // Prohibit capability = mod/wiki:viewpage on the course for students.
+ $contextcourse = context_course::instance($this->course->id);
+ assign_capability('mod/wiki:viewpage', CAP_PROHIBIT, $this->studentrole->id, $contextcourse->id);
+ accesslib_clear_all_caches_for_unit_testing();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_page_contents($this->firstpage->id);
+ }
+
+ /**
+ * Test get_page_contents, check that a student can't get a page from another group when
+ * using separate groups.
+ */
+ public function test_get_page_contents_separate_groups_student_see_other_group() {
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ $this->setUser($this->student);
+ $this->setExpectedException('moodle_exception');
+ mod_wiki_external::get_page_contents($this->fpsepg2indt->id);
+ }
+
+ /**
+ * Test get_page_contents without groups. We won't test all the possible cases because that's already
+ * done in the tests for get_subwiki_pages.
+ */
+ public function test_get_page_contents() {
+
+ // Test user with full capabilities.
+ $this->setUser($this->student);
+
+ // Set expected result: first page.
+ $expectedpage = array(
+ 'id' => $this->firstpage->id,
+ 'wikiid' => $this->wiki->id,
+ 'subwikiid' => $this->firstpage->subwikiid,
+ 'groupid' => 0, // No groups.
+ 'userid' => 0, // Collaborative.
+ 'title' => $this->firstpage->title,
+ 'cachedcontent' => $this->firstpage->cachedcontent,
+ 'contentformat' => 1,
+ 'caneditpage' => true
+ );
+
+ $result = mod_wiki_external::get_page_contents($this->firstpage->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
+ $this->assertEquals($expectedpage, $result['page']);
+
+ // Add a new page to the wiki and test with it.
+ $newpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_page($this->wiki);
+
+ $expectedpage['id'] = $newpage->id;
+ $expectedpage['title'] = $newpage->title;
+ $expectedpage['cachedcontent'] = $newpage->cachedcontent;
+
+ $result = mod_wiki_external::get_page_contents($newpage->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
+ $this->assertEquals($expectedpage, $result['page']);
+ }
+
+ /**
+ * Test get_page_contents with groups. We won't test all the possible cases because that's already
+ * done in the tests for get_subwiki_pages.
+ */
+ public function test_get_page_contents_with_groups() {
+
+ // Create testing data.
+ $this->create_individual_wikis_with_groups();
+
+ // Try to get page from a valid group in separate groups wiki.
+ $this->setUser($this->student);
+
+ $expectedfpsepg1indstu = array(
+ 'id' => $this->fpsepg1indstu->id,
+ 'wikiid' => $this->wikisepind->id,
+ 'subwikiid' => $this->fpsepg1indstu->subwikiid,
+ 'groupid' => $this->group1->id,
+ 'userid' => $this->student->id,
+ 'title' => $this->fpsepg1indstu->title,
+ 'cachedcontent' => $this->fpsepg1indstu->cachedcontent,
+ 'contentformat' => 1,
+ 'caneditpage' => true
+ );
+
+ $result = mod_wiki_external::get_page_contents($this->fpsepg1indstu->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
+ $this->assertEquals($expectedfpsepg1indstu, $result['page']);
+
+ // Check that teacher can view a group pages without belonging to it.
+ $this->setUser($this->teacher);
+ $result = mod_wiki_external::get_page_contents($this->fpsepg1indstu->id);
+ $result = external_api::clean_returnvalue(mod_wiki_external::get_page_contents_returns(), $result);
+ $this->assertEquals($expectedfpsepg1indstu, $result['page']);
+ }
+
}
--- /dev/null
+This files describes API changes in /mod/wiki/*,
+information provided here is intended especially for developers.
+
+=== 3.1 ===
+ * Added a new param $sort to wiki_get_page_list function. Default value behaves exactly like before (sort by title ASC).
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2016011102; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2016011103; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_wiki'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../lib/behat/behat_field_manager.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementTextException as ElementTextException;
/**
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
$this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mark_summary(2),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation());
}
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(4.00);
$this->check_current_output(
$this->get_contains_mark_summary(4.00),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(1.0);
$this->check_current_output(
$this->get_contains_mark_summary(1.0),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(0.9);
$this->check_current_output(
$this->get_contains_mark_summary(0.9),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation(),
$this->get_does_not_contain_disregarded_info_expectation());
$this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
$this->check_current_output(
$this->get_contains_mark_summary(8.00),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
$this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mark_summary(2),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_correct_expectation());
}
$this->check_current_mark(1.0);
$this->check_current_output(
$this->get_contains_mark_summary(1.0),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
*/
class qbehaviour_interactive_renderer extends qbehaviour_renderer {
public function controls(question_attempt $qa, question_display_options $options) {
+ if ($options->readonly === qbehaviour_interactive::READONLY_EXCEPT_TRY_AGAIN) {
+ return '';
+ }
return $this->submit_button($qa, $options);
}
$this->get_contains_mc_radio_expectation($wrongindex, false, true),
$this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
$this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
- $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_submit_button_expectation(),
&