Update to Drupal 8.1.5. For more information, see https://www.drupal.org/project/drupal/releases/8.1.5

This commit is contained in:
Pantheon Automation 2016-07-07 09:44:38 -07:00 committed by Greg Anderson
parent 13b6ca7cc2
commit 38ba7c357d
342 changed files with 7814 additions and 1534 deletions

View file

@ -6,6 +6,8 @@ use Behat\Mink\Driver\GoutteDriver;
use Behat\Mink\Element\Element;
use Behat\Mink\Mink;
use Behat\Mink\Session;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
@ -20,9 +22,13 @@ use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\Core\Url;
use Drupal\Core\Test\TestDatabase;
use Drupal\FunctionalTests\AssertLegacyTrait;
use Drupal\simpletest\AssertHelperTrait;
use Drupal\simpletest\ContentTypeCreationTrait;
use Drupal\simpletest\BlockCreationTrait;
use Drupal\simpletest\NodeCreationTrait;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\HttpFoundation\Request;
@ -31,13 +37,25 @@ use Symfony\Component\HttpFoundation\Request;
*
* Tests extending BrowserTestBase must exist in the
* Drupal\Tests\yourmodule\Functional namespace and live in the
* modules/yourmodule/Tests/Functional directory.
* modules/yourmodule/tests/src/Functional directory.
*
* @ingroup testing
*/
abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
use AssertHelperTrait;
use BlockCreationTrait {
placeBlock as drupalPlaceBlock;
}
use AssertLegacyTrait;
use RandomGeneratorTrait;
use SessionTestTrait;
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
}
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
/**
* Class loader.
@ -273,6 +291,13 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $baseUrl;
/**
* The original array of shutdown function callbacks.
*
* @var array
*/
protected $originalShutdownCallbacks = [];
/**
* Initializes Mink sessions.
*/
@ -505,6 +530,12 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
if ($this->mink) {
$this->mink->stopSessions();
}
// Restore original shutdown callbacks.
if (function_exists('drupal_register_shutdown_function')) {
$callbacks = &drupal_register_shutdown_function();
$callbacks = $this->originalShutdownCallbacks;
}
}
/**
@ -530,7 +561,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* A new web-assert option for asserting the presence of elements with.
*/
public function assertSession($name = NULL) {
return new WebAssert($this->getSession($name));
return new WebAssert($this->getSession($name), $this->baseUrl);
}
/**
@ -546,6 +577,46 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
}
/**
* Builds an a absolute URL from a system path or a URL object.
*
* @param string|\Drupal\Core\Url $path
* A system path or a URL.
* @param array $options
* Options to be passed to Url::fromUri().
*
* @return string
* An absolute URL stsring.
*/
protected function buildUrl($path, array $options = array()) {
if ($path instanceof Url) {
$url_options = $path->getOptions();
$options = $url_options + $options;
$path->setOptions($options);
return $path->setAbsolute()->toString();
}
// The URL generator service is not necessarily available yet; e.g., in
// interactive installer tests.
elseif ($this->container->has('url_generator')) {
$force_internal = isset($options['external']) && $options['external'] == FALSE;
if (!$force_internal && UrlHelper::isExternal($path)) {
return Url::fromUri($path, $options)->toString();
}
else {
$uri = $path === '<front>' ? 'base:/' : 'base:/' . $path;
// Path processing is needed for language prefixing. Skip it when a
// path that may look like an external URL is being used as internal.
$options['path_processing'] = !$force_internal;
return Url::fromUri($uri, $options)
->setAbsolute()
->toString();
}
}
else {
return $this->getAbsoluteUrl($path);
}
}
/**
* Retrieves a Drupal path or an absolute path.
*
@ -559,28 +630,8 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected function drupalGet($path, array $options = array()) {
$options['absolute'] = TRUE;
$url = $this->buildUrl($path, $options);
if ($path instanceof Url) {
$url_options = $path->getOptions();
$options = $url_options + $options;
$path->setOptions($options);
$url = $path->setAbsolute()->toString();
}
// The URL generator service is not necessarily available yet; e.g., in
// interactive installer tests.
elseif ($this->container->has('url_generator')) {
if (UrlHelper::isExternal($path)) {
$url = Url::fromUri($path, $options)->toString();
}
else {
// This is needed for language prefixing.
$options['path_processing'] = TRUE;
$url = Url::fromUri('base:/' . $path, $options)->toString();
}
}
else {
$url = $this->getAbsoluteUrl($path);
}
$session = $this->getSession();
$this->prepareRequest();
@ -874,6 +925,16 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// Edit the form values.
foreach ($edit as $name => $value) {
$field = $assert_session->fieldExists($name, $form);
// Provide support for the values '1' and '0' for checkboxes instead of
// TRUE and FALSE.
// @todo Get rid of supporting 1/0 by converting all tests cases using
// this to boolean values.
$field_type = $field->getAttribute('type');
if ($field_type === 'checkbox') {
$value = (bool) $value;
}
$field->setValue($value);
}
@ -893,6 +954,90 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
}
}
/**
* Executes a form submission.
*
* It will be done as usual POST request with Mink.
*
* @param \Drupal\Core\Url|string $path
* Location of the post form. Either a Drupal path or an absolute path or
* NULL to post to the current page. For multi-stage forms you can set the
* path to NULL and have it post to the last received page. Example:
*
* @code
* // First step in form.
* $edit = array(...);
* $this->drupalPostForm('some_url', $edit, t('Save'));
*
* // Second step in form.
* $edit = array(...);
* $this->drupalPostForm(NULL, $edit, t('Save'));
* @endcode
* @param array $edit
* Field data in an associative array. Changes the current input fields
* (where possible) to the values indicated.
*
* When working with form tests, the keys for an $edit element should match
* the 'name' parameter of the HTML of the form. For example, the 'body'
* field for a node has the following HTML:
* @code
* <textarea id="edit-body-und-0-value" class="text-full form-textarea
* resize-vertical" placeholder="" cols="60" rows="9"
* name="body[0][value]"></textarea>
* @endcode
* When testing this field using an $edit parameter, the code becomes:
* @code
* $edit["body[0][value]"] = 'My test value';
* @endcode
*
* A checkbox can be set to TRUE to be checked and should be set to FALSE to
* be unchecked. Multiple select fields can be tested using 'name[]' and
* setting each of the desired values in an array:
* @code
* $edit = array();
* $edit['name[]'] = array('value1', 'value2');
* @endcode
* @param string $submit
* Value of the submit button whose click is to be emulated. For example,
* t('Save'). The processing of the request depends on this value. For
* example, a form may have one button with the value t('Save') and another
* button with the value t('Delete'), and execute different code depending
* on which one is clicked.
*
* This function can also be called to emulate an Ajax submission. In this
* case, this value needs to be an array with the following keys:
* - path: A path to submit the form values to for Ajax-specific processing.
* - triggering_element: If the value for the 'path' key is a generic Ajax
* processing path, this needs to be set to the name of the element. If
* the name doesn't identify the element uniquely, then this should
* instead be an array with a single key/value pair, corresponding to the
* element name and value. The \Drupal\Core\Form\FormAjaxResponseBuilder
* uses this to find the #ajax information for the element, including
* which specific callback to use for processing the request.
*
* This can also be set to NULL in order to emulate an Internet Explorer
* submission of a form with a single text field, and pressing ENTER in that
* textfield: under these conditions, no button information is added to the
* POST data.
* @param array $options
* Options to be forwarded to the url generator.
*/
protected function drupalPostForm($path, array $edit, $submit, array $options = array()) {
if (is_object($submit)) {
// Cast MarkupInterface objects to string.
$submit = (string) $submit;
}
if (is_array($edit)) {
$edit = $this->castSafeStrings($edit);
}
if (isset($path)) {
$this->drupalGet($path, $options);
}
$this->submitForm($edit, $submit);
}
/**
* Helper function to get the options of select field.
*
@ -951,6 +1096,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
'value' => $this->publicFilesDirectory,
'required' => TRUE,
);
$settings['settings']['file_private_path'] = (object) [
'value' => $this->privateFilesDirectory,
'required' => TRUE,
];
$this->writeSettings($settings);
// Allow for test-specific overrides.
$settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/settings.testing.php';
@ -961,11 +1110,14 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// testing specific overrides.
file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
}
$settings_services_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/testing.services.yml';
if (file_exists($settings_services_file)) {
// Copy the testing-specific service overrides in place.
copy($settings_services_file, $directory . '/services.yml');
if (!file_exists($settings_services_file)) {
// Otherwise, use the default services as a starting point for overrides.
$settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
}
// Copy the testing-specific service overrides in place.
copy($settings_services_file, $directory . '/services.yml');
// Since Drupal is bootstrapped already, install_begin_request() will not
// bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
@ -990,6 +1142,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// using File API; a potential error must trigger a PHP warning.
chmod($directory, 0777);
// During tests, cacheable responses should get the debugging cacheability
// headers by default.
$this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
$request = \Drupal::request();
$this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
$this->kernel->prepareLegacyRequest($request);
@ -1000,13 +1156,13 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$config = $container->get('config.factory');
// Manually create and configure private and temporary files directories.
// While these could be preset/enforced in settings.php like the public
// files directory above, some tests expect them to be configurable in the
// UI. If declared in settings.php, they would no longer be configurable.
file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
// While the temporary files path could be preset/enforced in settings.php
// like the public files directory above, some tests expect it to be
// configurable in the UI. If declared in settings.php, it would no longer
// be configurable.
$config->getEditable('system.file')
->set('path.private', $this->privateFilesDirectory)
->set('path.temporary', $this->tempFilesDirectory)
->save();
@ -1241,6 +1397,14 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
));
drupal_set_time_limit($this->timeLimit);
// Save and clean the shutdown callbacks array because it is static cached
// and will be changed by the test run. Otherwise it will contain callbacks
// from both environments and the testing environment will try to call the
// handlers defined by the original one.
$callbacks = &drupal_register_shutdown_function();
$this->originalShutdownCallbacks = $callbacks;
$callbacks = [];
}
/**
@ -1383,6 +1547,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected function refreshVariables() {
// Clear the tag cache.
$this->container->get('cache_tags.invalidator')->resetChecksums();
// @todo Replace drupal_static() usage within classes and provide a
// proper interface for invoking reset() on a cache backend:
// https://www.drupal.org/node/2311945.
@ -1402,13 +1567,13 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
/**
* Returns whether a given user account is logged in.
*
* @param \Drupal\user\UserInterface $account
* @param \Drupal\Core\Session\AccountInterface $account
* The user account object to check.
*
* @return bool
* Return TRUE if the user is logged in, FALSE otherwise.
*/
protected function drupalUserIsLoggedIn(UserInterface $account) {
protected function drupalUserIsLoggedIn(AccountInterface $account) {
$logged_in = FALSE;
if (isset($account->sessionId)) {
@ -1419,30 +1584,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
return $logged_in;
}
/**
* Asserts that the element with the given CSS selector is present.
*
* @param string $css_selector
* The CSS selector identifying the element to check.
* @param string $message
* Optional message to show alongside the assertion.
*/
protected function assertElementPresent($css_selector, $message = '') {
$this->assertNotEmpty($this->getSession()->getDriver()->find($this->cssSelectToXpath($css_selector)), $message);
}
/**
* Asserts that the element with the given CSS selector is not present.
*
* @param string $css_selector
* The CSS selector identifying the element to check.
* @param string $message
* Optional message to show alongside the assertion.
*/
protected function assertElementNotPresent($css_selector, $message = '') {
$this->assertEmpty($this->getSession()->getDriver()->find($this->cssSelectToXpath($css_selector)), $message);
}
/**
* Clicks the element with the given CSS selector.
*
@ -1529,4 +1670,134 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
return (new CssSelectorConverter($html))->toXPath($selector, $prefix);
}
/**
* Searches elements using a CSS selector in the raw content.
*
* The search is relative to the root element (HTML tag normally) of the page.
*
* @param string $selector
* CSS selector to use in the search.
*
* @return \Behat\Mink\Element\NodeElement[]
* The list of elements on the page that match the selector.
*/
protected function cssSelect($selector) {
return $this->getSession()->getPage()->findAll('css', $selector);
}
/**
* Follows a link by complete name.
*
* Will click the first link found with this link text.
*
* If the link is discovered and clicked, the test passes. Fail otherwise.
*
* @param string|\Drupal\Component\Render\MarkupInterface $label
* Text between the anchor tags.
*/
protected function clickLink($label) {
$label = (string) $label;
$this->getSession()->getPage()->clickLink($label);
}
/**
* Retrieves the plain-text content from the current page.
*/
protected function getTextContent() {
return $this->getSession()->getPage()->getContent();
}
/**
* Performs an xpath search on the contents of the internal browser.
*
* The search is relative to the root element (HTML tag normally) of the page.
*
* @param string $xpath
* The xpath string to use in the search.
* @param array $arguments
* An array of arguments with keys in the form ':name' matching the
* placeholders in the query. The values may be either strings or numeric
* values.
*
* @return \Behat\Mink\Element\NodeElement[]
* The list of elements matching the xpath expression.
*/
protected function xpath($xpath, array $arguments = []) {
$xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments);
return $this->getSession()->getPage()->findAll('xpath', $xpath);
}
/**
* Configuration accessor for tests. Returns non-overridden configuration.
*
* @param string $name
* Configuration name.
*
* @return \Drupal\Core\Config\Config
* The configuration object with original configuration data.
*/
protected function config($name) {
return $this->container->get('config.factory')->getEditable($name);
}
/**
* Gets the value of an HTTP response header.
*
* If multiple requests were required to retrieve the page, only the headers
* from the last request will be checked by default.
*
* @param string $name
* The name of the header to retrieve. Names are case-insensitive (see RFC
* 2616 section 4.2).
*
* @return string|null
* The HTTP header value or NULL if not found.
*/
protected function drupalGetHeader($name) {
return $this->getSession()->getResponseHeader($name);
}
/**
* Get the current URL from the browser.
*
* @return string
* The current URL.
*/
protected function getUrl() {
return $this->getSession()->getCurrentUrl();
}
/**
* {@inheritdoc}
*/
public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
// Cast objects implementing MarkupInterface to string instead of
// relying on PHP casting them to string depending on what they are being
// comparing with.
$expected = static::castSafeStrings($expected);
$actual = static::castSafeStrings($actual);
parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
}
/**
* Changes parameters in the services.yml file.
*
* @param string $name
* The name of the parameter.
* @param mixed $value
* The value of the parameter.
*/
protected function setContainerParameter($name, $value) {
$filename = $this->siteDirectory . '/services.yml';
chmod($filename, 0666);
$services = Yaml::decode(file_get_contents($filename));
$services['parameters'][$name] = $value;
file_put_contents($filename, Yaml::encode($services));
// Ensure that the cache is deleted for the yaml file loader.
$file_cache = FileCacheFactory::get('container_yaml_loader');
$file_cache->delete($filename);
}
}