diff --git a/core/modules/language/src/Tests/AdminPathEntityConverterLanguageTest.php b/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php similarity index 89% rename from core/modules/language/src/Tests/AdminPathEntityConverterLanguageTest.php rename to core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php index 3cc00f9..bd46a40 100644 --- a/core/modules/language/src/Tests/AdminPathEntityConverterLanguageTest.php +++ b/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php @@ -1,16 +1,16 @@ drupalLogin($admin_user); $this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'hu'), t('Add language')); $this->drupalGet('admin/structure/block/add/system_menu_block:admin/stark'); - $this->assertNoFieldByXPath('//input[@id="edit-visibility-language-langcodes-und"]', NULL, '\'Not specified\' option does not appear at block config, language settings section.'); - $this->assertNoFieldByXpath('//input[@id="edit-visibility-language-langcodes-zxx"]', NULL, '\'Not applicable\' option does not appear at block config, language settings section.'); - $this->assertFieldByXPath('//input[@id="edit-visibility-language-langcodes-en"]', NULL, '\'English\' option appears at block config, language settings section.'); - $this->assertFieldByXpath('//input[@id="edit-visibility-language-langcodes-hu"]', NULL, '\'Hungarian\' option appears at block config, language settings section.'); + + // 'Not specified' option does not appear at block config, language settings + // section. + $this->assertSession()->fieldNotExists('edit-visibility-language-langcodes-und'); + // 'Not applicable' option does not appear. + $this->assertSession()->fieldNotExists('edit-visibility-language-langcodes-zxx'); + + // // 'English' option appears.. + $this->assertSession()->fieldExists('edit-visibility-language-langcodes-en'); + // // 'Hungarian' option appears.. + $this->assertSession()->fieldExists('edit-visibility-language-langcodes-hu'); } } diff --git a/core/modules/language/src/Tests/LanguageBrowserDetectionTest.php b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php similarity index 96% rename from core/modules/language/src/Tests/LanguageBrowserDetectionTest.php rename to core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php index 3991b2a..1b1382a 100644 --- a/core/modules/language/src/Tests/LanguageBrowserDetectionTest.php +++ b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php @@ -1,15 +1,15 @@ $http_header_browser_fallback, 'message' => 'SELECTED: UI language is switched based on selected language.', ); - $this->runTest($test); + $this->doTest($test); // An invalid language is selected. $this->config('language.negotiation')->set('selected_langcode', NULL)->save(); @@ -148,7 +148,7 @@ function testUILanguageNegotiation() { 'http_header' => $http_header_browser_fallback, 'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.', ); - $this->runTest($test); + $this->doTest($test); // No selected language is available. $this->config('language.negotiation')->set('selected_langcode', $langcode_unknown)->save(); @@ -160,7 +160,7 @@ function testUILanguageNegotiation() { 'http_header' => $http_header_browser_fallback, 'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.', ); - $this->runTest($test); + $this->doTest($test); $tests = array( // Default, browser preference should have no influence. @@ -211,7 +211,7 @@ function testUILanguageNegotiation() { ); foreach ($tests as $test) { - $this->runTest($test); + $this->doTest($test); } // Unknown language prefix should return 404. @@ -240,7 +240,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER > DEFAULT: no preferred user language setting, the UI language is default', ); - $this->runTest($test); + $this->doTest($test); // Set preferred langcode for user to unknown language. $account = $this->loggedInUser; @@ -255,7 +255,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER > DEFAULT: invalid preferred user language setting, the UI language is default', ); - $this->runTest($test); + $this->doTest($test); // Set preferred langcode for user to non default. $account->preferred_langcode = $langcode; @@ -269,7 +269,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER > DEFAULT: defined preferred user language setting, the UI language is based on user setting', ); - $this->runTest($test); + $this->doTest($test); // Set preferred admin langcode for user to NULL. $account->preferred_admin_langcode = NULL; @@ -283,7 +283,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER ADMIN > DEFAULT: no preferred user admin language setting, the UI language is default', ); - $this->runTest($test); + $this->doTest($test); // Set preferred admin langcode for user to unknown language. $account->preferred_admin_langcode = $langcode_unknown; @@ -297,7 +297,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER ADMIN > DEFAULT: invalid preferred user admin language setting, the UI language is default', ); - $this->runTest($test); + $this->doTest($test); // Set preferred admin langcode for user to non default. $account->preferred_admin_langcode = $langcode; @@ -311,7 +311,7 @@ function testUILanguageNegotiation() { 'http_header' => array(), 'message' => 'USER ADMIN > DEFAULT: defined preferred user admin language setting, the UI language is based on user setting', ); - $this->runTest($test); + $this->doTest($test); // Go by session preference. $language_negotiation_session_param = $this->randomMachineName(); @@ -337,11 +337,11 @@ function testUILanguageNegotiation() { ), ); foreach ($tests as $test) { - $this->runTest($test); + $this->doTest($test); } } - protected function runTest($test) { + protected function doTest($test) { $test += array('path_options' => []); if (!empty($test['language_negotiation'])) { $method_weights = array_flip($test['language_negotiation']); diff --git a/core/modules/language/src/Tests/LanguageUrlRewritingTest.php b/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php similarity index 96% rename from core/modules/language/src/Tests/LanguageUrlRewritingTest.php rename to core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php index 5cd80dc..e08eb0a 100644 --- a/core/modules/language/src/Tests/LanguageUrlRewritingTest.php +++ b/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php @@ -1,12 +1,12 @@ getId()]) ? $prefixes[$language->getId()] : $this->randomMachineName(); - if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) { - $prefix = $stored_prefix; - } + $this->assertNotEqual($stored_prefix, $prefix, $message1); + $prefix = $stored_prefix; $this->drupalGet("$prefix/$path"); $this->assertResponse(404, $message2); diff --git a/core/modules/language/src/Tests/Migrate/MigrateLanguageTest.php b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageTest.php similarity index 96% rename from core/modules/language/src/Tests/Migrate/MigrateLanguageTest.php rename to core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageTest.php index 6bdd1f2..72d7ab8 100644 --- a/core/modules/language/src/Tests/Migrate/MigrateLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageTest.php @@ -1,6 +1,6 @@ configImporter) { - // Set up the ConfigImporter object for testing. - $storage_comparer = new StorageComparer( - $this->container->get('config.storage.sync'), - $this->container->get('config.storage'), - $this->container->get('config.manager') - ); - $this->configImporter = new ConfigImporter( - $storage_comparer, - $this->container->get('event_dispatcher'), - $this->container->get('config.manager'), - $this->container->get('lock'), - $this->container->get('config.typed'), - $this->container->get('module_handler'), - $this->container->get('module_installer'), - $this->container->get('theme_handler'), - $this->container->get('string_translation') - ); - } - // Always recalculate the changelist when called. - return $this->configImporter->reset(); - } - - /** - * Copies configuration objects from source storage to target storage. - * - * @param \Drupal\Core\Config\StorageInterface $source_storage - * The source config storage service. - * @param \Drupal\Core\Config\StorageInterface $target_storage - * The target config storage service. - */ - public function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) { - $target_storage->deleteAll(); - foreach ($source_storage->listAll() as $name) { - $target_storage->write($name, $source_storage->read($name)); - } - } - - /** * Configuration accessor for tests. Returns non-overridden configuration. * * @param $name diff --git a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php index 0789fc8..09cc466 100644 --- a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php +++ b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php @@ -124,7 +124,7 @@ protected function assertResponse($code) { protected function assertFieldByName($name, $value = NULL) { $this->assertSession()->fieldExists($name); if ($value !== NULL) { - $this->assertSession()->fieldValueEquals($name, $value); + $this->assertSession()->fieldValueEquals($name, (string) $value); } } @@ -148,6 +148,32 @@ protected function assertFieldById($id, $value = NULL) { } /** + * Asserts that a field exists with the given name or ID. + * + * @param string $field + * Name or ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldExists() instead. + */ + protected function assertField($field) { + $this->assertSession()->fieldExists($field); + } + + /** + * Asserts that a field exists with the given name or ID does NOT exist. + * + * @param string $field + * Name or ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldNotExists() instead. + */ + protected function assertNoField($field) { + $this->assertSession()->fieldNotExists($field); + } + + /** * Passes if the raw text IS found on the loaded page, fail otherwise. * * Raw text refers to the raw HTML that the page generated. @@ -163,12 +189,32 @@ protected function assertRaw($raw) { } /** + * Passes if the raw text IS not found on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->responseNotContains() instead. + */ + protected function assertNoRaw($raw) { + $this->assertSession()->responseNotContains($raw); + } + + /** * Pass if the page title is the given string. * * @param string $expected_title * The string the page title should be. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->titleEquals() instead. */ protected function assertTitle($expected_title) { + // Cast MarkupInterface to string. + $expected_title = (string) $expected_title; return $this->assertSession()->titleEquals($expected_title); } @@ -181,12 +227,93 @@ protected function assertTitle($expected_title) { * Text between the anchor tags. * @param int $index * Link position counting from zero. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkExists() instead. */ protected function assertLink($label, $index = 0) { return $this->assertSession()->linkExists($label, $index); } /** + * Passes if a link with the specified label is not found. + * + * @param string|\Drupal\Component\Render\MarkupInterface $label + * Text between the anchor tags. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkNotExists() instead. + */ + protected function assertNoLink($label) { + return $this->assertSession()->linkNotExists($label); + } + + /** + * Passes if a link containing a given href (part) is found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param int $index + * Link position counting from zero. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkByHref() instead. + */ + protected function assertLinkByHref($href, $index = 0) { + $this->assertSession()->linkByHrefExists($href, $index); + } + + /** + * Passes if a link containing a given href (part) is not found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkByHrefNotExists() instead. + */ + protected function assertNoLinkByHref($href) { + $this->assertSession()->linkByHrefNotExists($href); + } + + /** + * Asserts that a field does not exist with the given ID and value. + * + * @param string $id + * ID of field to assert. + * @param string $value + * (optional) Value for the field, to assert that the field's value on the + * page doesn't match it. You may pass in NULL to skip checking the value, + * while still checking that the field doesn't exist. However, the default + * value ('') asserts that the field value is not an empty string. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldNotExists() or + * $this->assertSession()->fieldValueNotEquals() instead. + */ + protected function assertNoFieldById($id, $value = '') { + if ($this->getSession()->getPage()->findField($id)) { + $this->assertSession()->fieldValueNotEquals($id, (string) $value); + } + else { + $this->assertSession()->fieldNotExists($id); + } + } + + /** + * Passes if the internal browser's URL matches the given path. + * + * @param \Drupal\Core\Url|string $path + * The expected system path or URL. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->addressEquals() instead. + */ + protected function assertUrl($path) { + $this->assertSession()->addressEquals($path); + } + + /** * Asserts that a select option in the current page exists. * * @param string $id @@ -217,20 +344,63 @@ protected function assertNoOption($id, $option) { } /** - * Passes if the internal browser's URL matches the given path. + * Asserts that a select option in the current page is checked. * - * @param string $path - * The expected system path. + * @param string $id + * ID of select field to assert. + * @param string $option + * Option to assert. * * @deprecated Scheduled for removal in Drupal 9.0.0. - * Use $this->assertSession()->addressEquals() instead. + * Use $this->assertSession()->optionSelected() instead. */ - protected function assertUrl($path) { - $this->assertSession()->addressEquals($path); + protected function assertOptionSelected($id, $option) { + $this->assertSession()->optionSelected($id, $option); + } + + /** + * Asserts that a checkbox field in the current page is checked. + * + * @param string $id + * ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->checkboxChecked() instead. + */ + protected function assertFieldChecked($id) { + $this->assertSession()->checkboxChecked($id); + } + + /** + * Asserts that a checkbox field in the current page is not checked. + * + * @param string $id + * ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->checkboxNotChecked() instead. + */ + protected function assertNoFieldChecked($id) { + $this->assertSession()->checkboxNotChecked($id); } /** - * Passes if the raw text IS NOT found escaped on the loaded page. + * Passes if the raw text IS found escaped on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->assertEscaped() instead. + */ + protected function assertEscaped($raw) { + $this->assertSession()->assertEscaped($raw); + } + + /** + * Passes if the raw text is not found escaped on the loaded page. * * Raw text refers to the raw HTML that the page generated. * @@ -244,4 +414,55 @@ protected function assertNoEscaped($raw) { $this->assertSession()->assertNoEscaped($raw); } + /** + * Asserts whether an expected cache tag was present in the last response. + * + * @param string $expected_cache_tag + * The expected cache tag. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->responseHeaderContains() instead. + */ + protected function assertCacheTag($expected_cache_tag) { + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $expected_cache_tag); + } + + /** + * Returns WebAssert object. + * + * @param string $name + * (optional) Name of the session. Defaults to the active session. + * + * @return \Drupal\Tests\WebAssert + * A new web-assert option for asserting the presence of elements with. + */ + abstract public function assertSession($name = NULL); + + /** + * Builds an XPath query. + * + * Builds an XPath query by replacing placeholders in the query by the value + * of the arguments. + * + * XPath 1.0 (the version supported by libxml2, the underlying XML library + * used by PHP) doesn't support any form of quotation. This function + * simplifies the building of XPath expression. + * + * @param string $xpath + * An XPath query, possibly with placeholders in the form ':name'. + * @param array $args + * 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 string + * An XPath query with arguments replaced. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->buildXPathQuery() instead. + */ + public function buildXPathQuery($xpath, array $args = array()) { + return $this->assertSession()->buildXPathQuery($xpath, $args); + } + } diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index ed3875d..8807cb5 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -7,9 +7,6 @@ use Drupal\Component\FileCache\FileCacheFactory; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Core\Config\ConfigImporter; -use Drupal\Core\Config\StorageComparer; -use Drupal\Core\Config\StorageInterface; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; @@ -20,6 +17,7 @@ use Drupal\Core\Site\Settings; use Drupal\simpletest\AssertContentTrait; use Drupal\simpletest\AssertHelperTrait; +use Drupal\Tests\ConfigTestTrait; use Drupal\Tests\RandomGeneratorTrait; use Drupal\simpletest\TestServiceProvider; use Symfony\Component\DependencyInjection\Reference; @@ -55,6 +53,7 @@ use AssertContentTrait; use AssertHelperTrait; use RandomGeneratorTrait; + use ConfigTestTrait; /** * {@inheritdoc} @@ -1016,54 +1015,6 @@ protected function setSetting($name, $value) { } /** - * Returns a ConfigImporter object to import test configuration. - * - * @return \Drupal\Core\Config\ConfigImporter - * - * @todo Move into Config-specific test base class. - */ - protected function configImporter() { - if (!$this->configImporter) { - // Set up the ConfigImporter object for testing. - $storage_comparer = new StorageComparer( - $this->container->get('config.storage.sync'), - $this->container->get('config.storage'), - $this->container->get('config.manager') - ); - $this->configImporter = new ConfigImporter( - $storage_comparer, - $this->container->get('event_dispatcher'), - $this->container->get('config.manager'), - $this->container->get('lock'), - $this->container->get('config.typed'), - $this->container->get('module_handler'), - $this->container->get('module_installer'), - $this->container->get('theme_handler'), - $this->container->get('string_translation') - ); - } - // Always recalculate the changelist when called. - return $this->configImporter->reset(); - } - - /** - * Copies configuration objects from a source storage to a target storage. - * - * @param \Drupal\Core\Config\StorageInterface $source_storage - * The source config storage. - * @param \Drupal\Core\Config\StorageInterface $target_storage - * The target config storage. - * - * @todo Move into Config-specific test base class. - */ - protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) { - $target_storage->deleteAll(); - foreach ($source_storage->listAll() as $name) { - $target_storage->write($name, $source_storage->read($name)); - } - } - - /** * Stops test execution. */ protected function stop() { diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 70a1840..18e1b25 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -29,6 +29,7 @@ use Drupal\simpletest\NodeCreationTrait; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; +use Drupal\Tests\ConfigTestTrait; use Symfony\Component\CssSelector\CssSelectorConverter; use Symfony\Component\HttpFoundation\Request; @@ -43,16 +44,20 @@ */ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { use AssertHelperTrait; - use BlockCreationTrait; + use BlockCreationTrait { + placeBlock as drupalPlaceBlock; + } use AssertLegacyTrait; use RandomGeneratorTrait; use SessionTestTrait; use NodeCreationTrait { + getNodeByTitle as drupalGetNodeByTitle; createNode as drupalCreateNode; } use ContentTypeCreationTrait { createContentType as drupalCreateContentType; } + use ConfigTestTrait; /** * Class loader. @@ -575,40 +580,60 @@ protected function prepareRequest() { } /** - * Retrieves a Drupal path or an absolute path. + * Builds an a absolute URL from a system path or a URL object. * * @param string|\Drupal\Core\Url $path - * Drupal path or URL to load into Mink controlled browser. + * A system path or a URL. * @param array $options - * (optional) Options to be forwarded to the url generator. + * Options to be passed to Url::fromUri(). * * @return string - * The retrieved HTML string, also available as $this->getRawContent() + * An absolute URL stsring. */ - protected function drupalGet($path, array $options = array()) { - $options['absolute'] = TRUE; - + protected function buildUrl($path, array $options = array()) { if ($path instanceof Url) { $url_options = $path->getOptions(); $options = $url_options + $options; $path->setOptions($options); - $url = $path->setAbsolute()->toString(); + 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')) { - if (UrlHelper::isExternal($path)) { - $url = Url::fromUri($path, $options)->toString(); + $force_internal = isset($options['external']) && $options['external'] == FALSE; + if (!$force_internal && UrlHelper::isExternal($path)) { + return Url::fromUri($path, $options)->toString(); } else { - // This is needed for language prefixing. - $options['path_processing'] = TRUE; - $url = Url::fromUri('base:/' . $path, $options)->toString(); + $uri = $path === '' ? '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 { - $url = $this->getAbsoluteUrl($path); + return $this->getAbsoluteUrl($path); } + } + + /** + * Retrieves a Drupal path or an absolute path. + * + * @param string|\Drupal\Core\Url $path + * Drupal path or URL to load into Mink controlled browser. + * @param array $options + * (optional) Options to be forwarded to the url generator. + * + * @return string + * The retrieved HTML string, also available as $this->getRawContent() + */ + protected function drupalGet($path, array $options = array()) { + $options['absolute'] = TRUE; + $url = $this->buildUrl($path, $options); + $session = $this->getSession(); $this->prepareRequest(); @@ -902,6 +927,16 @@ protected function submitForm(array $edit, $submit, $form_html_id = NULL) { // 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); } @@ -1690,64 +1725,11 @@ protected function getTextContent() { * The list of elements matching the xpath expression. */ protected function xpath($xpath, array $arguments = []) { - $xpath = $this->buildXPathQuery($xpath, $arguments); + $xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments); return $this->getSession()->getPage()->findAll('xpath', $xpath); } /** - * Builds an XPath query. - * - * Builds an XPath query by replacing placeholders in the query by the value - * of the arguments. - * - * XPath 1.0 (the version supported by libxml2, the underlying XML library - * used by PHP) doesn't support any form of quotation. This function - * simplifies the building of XPath expression. - * - * @param string $xpath - * An XPath query, possibly with placeholders in the form ':name'. - * @param array $args - * 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 string - * An XPath query with arguments replaced. - */ - protected function buildXPathQuery($xpath, array $args = array()) { - // Replace placeholders. - foreach ($args as $placeholder => $value) { - // Cast MarkupInterface objects to string. - if (is_object($value)) { - $value = (string) $value; - } - // XPath 1.0 doesn't support a way to escape single or double quotes in a - // string literal. We split double quotes out of the string, and encode - // them separately. - if (is_string($value)) { - // Explode the text at the quote characters. - $parts = explode('"', $value); - - // Quote the parts. - foreach ($parts as &$part) { - $part = '"' . $part . '"'; - } - - // Return the string. - $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0]; - } - - // Use preg_replace_callback() instead of preg_replace() to prevent the - // regular expression engine from trying to substitute backreferences. - $replacement = function ($matches) use ($value) { - return $value; - }; - $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath); - } - return $xpath; - } - - /** * Configuration accessor for tests. Returns non-overridden configuration. * * @param string $name diff --git a/core/tests/Drupal/Tests/ConfigTestTrait.php b/core/tests/Drupal/Tests/ConfigTestTrait.php new file mode 100644 index 0000000..4316827 --- /dev/null +++ b/core/tests/Drupal/Tests/ConfigTestTrait.php @@ -0,0 +1,58 @@ +configImporter) { + // Set up the ConfigImporter object for testing. + $storage_comparer = new StorageComparer( + $this->container->get('config.storage.sync'), + $this->container->get('config.storage'), + $this->container->get('config.manager') + ); + $this->configImporter = new ConfigImporter( + $storage_comparer, + $this->container->get('event_dispatcher'), + $this->container->get('config.manager'), + $this->container->get('lock'), + $this->container->get('config.typed'), + $this->container->get('module_handler'), + $this->container->get('module_installer'), + $this->container->get('theme_handler'), + $this->container->get('string_translation') + ); + } + // Always recalculate the changelist when called. + return $this->configImporter->reset(); + } + + /** + * Copies configuration objects from source storage to target storage. + * + * @param \Drupal\Core\Config\StorageInterface $source_storage + * The source config storage service. + * @param \Drupal\Core\Config\StorageInterface $target_storage + * The target config storage service. + */ + public function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) { + $target_storage->deleteAll(); + foreach ($source_storage->listAll() as $name) { + $target_storage->write($name, $source_storage->read($name)); + } + } + +} diff --git a/core/tests/Drupal/Tests/WebAssert.php b/core/tests/Drupal/Tests/WebAssert.php index ec31fdf..d4cffd8 100644 --- a/core/tests/Drupal/Tests/WebAssert.php +++ b/core/tests/Drupal/Tests/WebAssert.php @@ -8,6 +8,7 @@ use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Session; use Drupal\Component\Utility\Html; +use Drupal\Core\Url; /** * Defines a class with methods for asserting presence of elements during tests. @@ -38,6 +39,9 @@ public function __construct(Session $session, $base_url = '') { * {@inheritdoc} */ protected function cleanUrl($url) { + if ($url instanceof Url) { + $url = $url->setAbsolute()->toString(); + } // Strip the base URL from the beginning for absolute URLs. if ($this->baseUrl !== '' && strpos($url, $this->baseUrl) === 0) { $url = substr($url, strlen($this->baseUrl)); @@ -76,6 +80,24 @@ public function buttonExists($button, TraversableElement $container = NULL) { } /** + * Checks that the specific button does NOT exist on the current page. + * + * @param string $button + * One of id|name|label|value for the button. + * @param \Behat\Mink\Element\TraversableElement $container + * (optional) The document to check against. Defaults to the current page. + * + * @throws \Behat\Mink\Exception\ExpectationException + * When the button exists. + */ + public function buttonNotExists($button, TraversableElement $container = NULL) { + $container = $container ?: $this->session->getPage(); + $node = $container->findButton($button); + + $this->assert(NULL === $node, sprintf('A button "%s" appears on this page, but it should not.', $button)); + } + + /** * Checks that specific select field exists on the current page. * * @param string $select @@ -169,6 +191,29 @@ public function optionNotExists($select, $option, TraversableElement $container } /** + * Checks that a specific option in a select field is selected. + * + * @param string $select + * One of id|name|label|value for the select field. + * @param string $option + * The option value. + * @param \Behat\Mink\Element\TraversableElement $container + * (optional) The document to check against. Defaults to the current page. + * + * @return \Behat\Mink\Element\NodeElement + * The matching option element + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * When the element doesn't exist. + */ + public function optionSelected($select, $option, TraversableElement $container = NULL) { + $option_field = $this->optionExists($select, $option, $container); + $this->assert(!empty($option_field->getAttribute('selected')), sprintf('Option "%s" in select "%s" is not selected as expected.', $option, $select)); + + return $option_field; + } + + /** * Pass if the page title is the given string. * * @param string $expected_title @@ -191,7 +236,7 @@ public function titleEquals($expected_title) { * * An optional link index may be passed. * - * @param string|\Drupal\Component\Render\MarkupInterface $label + * @param string $label * Text between the anchor tags. * @param int $index * Link position counting from zero. @@ -204,14 +249,126 @@ public function titleEquals($expected_title) { * Thrown when element doesn't exist, or the link label is a different one. */ public function linkExists($label, $index = 0, $message = '') { - // Cast MarkupInterface objects to string. - $label = (string) $label; $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label])); $links = $this->session->getPage()->findAll('named', ['link', $label]); - if (empty($links[$index])) { - throw new ExpectationException($message); + $this->assert(!empty($links[$index]), $message); + } + + /** + * Passes if a link with the specified label is not found. + * + * An optional link index may be passed. + * + * @param string $label + * Text between the anchor tags. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use strtr() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkNotExists($label, $message = '') { + $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label])); + $links = $this->session->getPage()->findAll('named', ['link', $label]); + $this->assert(empty($links), $message); + } + + /** + * Passes if a link containing a given href (part) is found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param int $index + * Link position counting from zero. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed + * variables in the message text, not t(). If left blank, a default message + * will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkByHrefExists($href, $index = 0, $message = '') { + $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]); + $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href])); + $links = $this->session->getPage()->findAll('xpath', $xpath); + $this->assert(!empty($links[$index]), $message); + } + + /** + * Passes if a link containing a given href (part) is not found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed + * variables in the message text, not t(). If left blank, a default message + * will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkByHrefNotExists($href, $message = '') { + $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]); + $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href])); + $links = $this->session->getPage()->findAll('xpath', $xpath); + $this->assert(empty($links), $message); + } + + /** + * Builds an XPath query. + * + * Builds an XPath query by replacing placeholders in the query by the value + * of the arguments. + * + * XPath 1.0 (the version supported by libxml2, the underlying XML library + * used by PHP) doesn't support any form of quotation. This function + * simplifies the building of XPath expression. + * + * @param string $xpath + * An XPath query, possibly with placeholders in the form ':name'. + * @param array $args + * 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 string + * An XPath query with arguments replaced. + */ + public function buildXPathQuery($xpath, array $args = array()) { + // Replace placeholders. + foreach ($args as $placeholder => $value) { + if (is_object($value)) { + throw new \InvalidArgumentException('Just pass in scalar values.'); + } + // XPath 1.0 doesn't support a way to escape single or double quotes in a + // string literal. We split double quotes out of the string, and encode + // them separately. + if (is_string($value)) { + // Explode the text at the quote characters. + $parts = explode('"', $value); + + // Quote the parts. + foreach ($parts as &$part) { + $part = '"' . $part . '"'; + } + + // Return the string. + $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0]; + } + + // Use preg_replace_callback() instead of preg_replace() to prevent the + // regular expression engine from trying to substitute backreferences. + $replacement = function ($matches) use ($value) { + return $value; + }; + $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath); } - $this->assert($links[$index] !== NULL, $message); + return $xpath; } /** @@ -223,7 +380,19 @@ public function linkExists($label, $index = 0, $message = '') { * Raw (HTML) string to look for. */ public function assertNoEscaped($raw) { - $this->pageTextNotContains(Html::escape($raw)); + $this->responseNotContains(Html::escape($raw)); + } + + /** + * Passes if the raw text IS found escaped on the loaded page. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + public function assertEscaped($raw) { + $this->responseContains(Html::escape($raw)); } /** @@ -247,4 +416,33 @@ public function assert($condition, $message) { throw new ExpectationException($message, $this->session->getDriver()); } + /** + * Checks that a given form field element is disabled. + * + * @param string $field + * One of id|name|label|value for the field. + * @param \Behat\Mink\Element\TraversableElement $container + * (optional) The document to check against. Defaults to the current page. + * + * @return \Behat\Mink\Element\NodeElement + * The matching element. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function fieldDisabled($field, TraversableElement $container = NULL) { + $container = $container ?: $this->session->getPage(); + $node = $container->findField($field); + + if ($node === NULL) { + throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field); + } + + if (!$node->hasAttribute('disabled')) { + throw new ExpectationException("Field $field is disabled", $this->session->getDriver()); + } + + return $node; + } + }