Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Ajax;
|
||||
|
||||
use Drupal\ajax_test\Controller\AjaxTestController;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Performs tests on opening and manipulating dialogs via AJAX commands.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class OffCanvasDialogTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['ajax_test'];
|
||||
|
||||
/**
|
||||
* Test sending AJAX requests to open and manipulate off-canvas dialog.
|
||||
*
|
||||
* @dataProvider dialogPosition
|
||||
*/
|
||||
public function testDialog($position) {
|
||||
// Ensure the elements render without notices or exceptions.
|
||||
$this->drupalGet('ajax-test/dialog');
|
||||
|
||||
// Set up variables for this test.
|
||||
$dialog_renderable = AjaxTestController::dialogContents();
|
||||
$dialog_contents = \Drupal::service('renderer')->renderRoot($dialog_renderable);
|
||||
|
||||
$off_canvas_expected_response = [
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-off-canvas',
|
||||
'settings' => NULL,
|
||||
'data' => $dialog_contents,
|
||||
'dialogOptions' =>
|
||||
[
|
||||
'title' => 'AJAX Dialog & contents',
|
||||
'modal' => FALSE,
|
||||
'autoResize' => FALSE,
|
||||
'resizable' => 'w',
|
||||
'draggable' => FALSE,
|
||||
'drupalAutoButtons' => FALSE,
|
||||
'drupalOffCanvasPosition' => $position ?: 'side',
|
||||
'buttons' => [],
|
||||
'dialogClass' => 'ui-dialog-off-canvas ui-dialog-position-' . ($position ?: 'side'),
|
||||
'width' => 300,
|
||||
],
|
||||
'effect' => 'fade',
|
||||
'speed' => 1000,
|
||||
];
|
||||
|
||||
// Emulate going to the JS version of the page and check the JSON response.
|
||||
$wrapper_format = $position && ($position !== 'side') ? 'drupal_dialog.off_canvas_' . $position : 'drupal_dialog.off_canvas';
|
||||
$ajax_result = $this->drupalGet('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => $wrapper_format]]);
|
||||
$ajax_result = Json::decode($ajax_result);
|
||||
$this->assertEquals($off_canvas_expected_response, $ajax_result[3], 'off-canvas dialog JSON response matches.');
|
||||
}
|
||||
|
||||
/**
|
||||
* The data provider for potential dialog positions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function dialogPosition() {
|
||||
return [
|
||||
[NULL],
|
||||
['side'],
|
||||
['top'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -50,7 +50,15 @@ class PageTest extends BrowserTestBase {
|
|||
// Visit an administrative page that runs a test batch, and check that the
|
||||
// title shown during batch execution (which the batch callback function
|
||||
// saved as a variable) matches the theme used on the administrative page.
|
||||
// Run initial step only first.
|
||||
$this->maximumMetaRefreshCount = 0;
|
||||
$this->drupalGet('batch-test/test-title');
|
||||
$this->assertText('Batch Test', 'The test is in the html output.');
|
||||
|
||||
// Leave the batch process running.
|
||||
$this->maximumMetaRefreshCount = NULL;
|
||||
$this->drupalGet('batch-test/test-title');
|
||||
|
||||
// The stack should contain the title shown on the progress page.
|
||||
$this->assertEqual(batch_test_stack(), ['Batch Test'], 'The batch title is shown on the batch page.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
|
|
|
|||
|
|
@ -175,7 +175,6 @@ class ProcessingTest extends BrowserTestBase {
|
|||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers a pass if the texts were found in order in the raw content.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
|
||||
namespace Drupal\Tests\system\Functional\Bootstrap;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests drupal_set_message() and related functions.
|
||||
* Tests the Messenger service.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class DrupalSetMessageTest extends BrowserTestBase {
|
||||
class DrupalMessengerServiceTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
|
|
@ -19,12 +20,12 @@ class DrupalSetMessageTest extends BrowserTestBase {
|
|||
public static $modules = ['system_test'];
|
||||
|
||||
/**
|
||||
* Tests drupal_set_message().
|
||||
* Tests Messenger service.
|
||||
*/
|
||||
public function testDrupalSetMessage() {
|
||||
// The page at system-test/drupal-set-message sets two messages and then
|
||||
// removes the first before it is displayed.
|
||||
$this->drupalGet('system-test/drupal-set-message');
|
||||
public function testDrupalMessengerService() {
|
||||
// The page at system_test.messenger_service route sets two messages and
|
||||
// then removes the first before it is displayed.
|
||||
$this->drupalGet(Url::fromRoute('system_test.messenger_service'));
|
||||
$this->assertNoText('First message (removed).');
|
||||
$this->assertRaw(t('Second message with <em>markup!</em> (not removed).'));
|
||||
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides test assertions for testing page-level cache contexts & tags.
|
||||
*
|
||||
* Can be used by test classes that extend \Drupal\Tests\BrowserTestBase.
|
||||
*/
|
||||
trait AssertPageCacheContextsAndTagsTrait {
|
||||
|
||||
/**
|
||||
* Enables page caching.
|
||||
*/
|
||||
protected function enablePageCaching() {
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific header value as array.
|
||||
*
|
||||
* @param string $header_name
|
||||
* The header name.
|
||||
*
|
||||
* @return string[]
|
||||
* The header value, potentially exploded by spaces.
|
||||
*/
|
||||
protected function getCacheHeaderValues($header_name) {
|
||||
$header_value = $this->drupalGetHeader($header_name);
|
||||
if (empty($header_value)) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
return explode(' ', $header_value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts whether an expected cache context was present in the last response.
|
||||
*
|
||||
* @param string $expected_cache_context
|
||||
* The expected cache context.
|
||||
*/
|
||||
protected function assertCacheContext($expected_cache_context) {
|
||||
$cache_contexts = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Contexts'));
|
||||
$this->assertTrue(in_array($expected_cache_context, $cache_contexts), "'" . $expected_cache_context . "' is present in the X-Drupal-Cache-Contexts header.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a cache context was not present in the last response.
|
||||
*
|
||||
* @param string $not_expected_cache_context
|
||||
* The expected cache context.
|
||||
*/
|
||||
protected function assertNoCacheContext($not_expected_cache_context) {
|
||||
$cache_contexts = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Contexts'));
|
||||
$this->assertFalse(in_array($not_expected_cache_context, $cache_contexts), "'" . $not_expected_cache_context . "' is not present in the X-Drupal-Cache-Contexts header.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts page cache miss, then hit for the given URL; checks cache headers.
|
||||
*
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The URL to test.
|
||||
* @param string[] $expected_contexts
|
||||
* The expected cache contexts for the given URL.
|
||||
* @param string[] $expected_tags
|
||||
* The expected cache tags for the given URL.
|
||||
*/
|
||||
protected function assertPageCacheContextsAndTags(Url $url, array $expected_contexts, array $expected_tags) {
|
||||
$absolute_url = $url->setAbsolute()->toString();
|
||||
sort($expected_contexts);
|
||||
sort($expected_tags);
|
||||
|
||||
// Assert cache miss + expected cache contexts + tags.
|
||||
$this->drupalGet($absolute_url);
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
$this->assertCacheTags($expected_tags);
|
||||
$this->assertCacheContexts($expected_contexts);
|
||||
|
||||
// Assert cache hit + expected cache contexts + tags.
|
||||
$this->drupalGet($absolute_url);
|
||||
$this->assertCacheTags($expected_tags);
|
||||
$this->assertCacheContexts($expected_contexts);
|
||||
|
||||
// Assert page cache item + expected cache tags.
|
||||
$cid_parts = [$url->setAbsolute()->toString(), 'html'];
|
||||
$cid = implode(':', $cid_parts);
|
||||
$cache_entry = \Drupal::cache('page')->get($cid);
|
||||
sort($cache_entry->tags);
|
||||
$this->assertEqual($cache_entry->tags, $expected_tags);
|
||||
$this->debugCacheTags($cache_entry->tags, $expected_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides debug information for cache tags.
|
||||
*
|
||||
* @param string[] $actual_tags
|
||||
* The actual cache tags.
|
||||
* @param string[] $expected_tags
|
||||
* The expected cache tags.
|
||||
*/
|
||||
protected function debugCacheTags(array $actual_tags, array $expected_tags) {
|
||||
if ($actual_tags !== $expected_tags) {
|
||||
debug('Unwanted cache tags in response: ' . implode(',', array_diff($actual_tags, $expected_tags)));
|
||||
debug('Missing cache tags in response: ' . implode(',', array_diff($expected_tags, $actual_tags)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some cache tags are present in the current response.
|
||||
*
|
||||
* @param string[] $expected_tags
|
||||
* The expected tags.
|
||||
* @param bool $include_default_tags
|
||||
* (optional) Whether the default cache tags should be included.
|
||||
*/
|
||||
protected function assertCacheTags(array $expected_tags, $include_default_tags = TRUE) {
|
||||
// The anonymous role cache tag is only added if the user is anonymous.
|
||||
if ($include_default_tags) {
|
||||
if (\Drupal::currentUser()->isAnonymous()) {
|
||||
$expected_tags = Cache::mergeTags($expected_tags, ['config:user.role.anonymous']);
|
||||
}
|
||||
$expected_tags[] = 'http_response';
|
||||
}
|
||||
$actual_tags = $this->getCacheHeaderValues('X-Drupal-Cache-Tags');
|
||||
$expected_tags = array_unique($expected_tags);
|
||||
sort($expected_tags);
|
||||
sort($actual_tags);
|
||||
$this->assertIdentical($actual_tags, $expected_tags);
|
||||
$this->debugCacheTags($actual_tags, $expected_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some cache contexts are present in the current response.
|
||||
*
|
||||
* @param string[] $expected_contexts
|
||||
* The expected cache contexts.
|
||||
* @param string $message
|
||||
* (optional) A verbose message to output.
|
||||
* @param bool $include_default_contexts
|
||||
* (optional) Whether the default contexts should automatically be included.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertCacheContexts(array $expected_contexts, $message = NULL, $include_default_contexts = TRUE) {
|
||||
if ($include_default_contexts) {
|
||||
$default_contexts = ['languages:language_interface', 'theme'];
|
||||
// Add the user.permission context to the list of default contexts except
|
||||
// when user is already there.
|
||||
if (!in_array('user', $expected_contexts)) {
|
||||
$default_contexts[] = 'user.permissions';
|
||||
}
|
||||
$expected_contexts = Cache::mergeContexts($expected_contexts, $default_contexts);
|
||||
}
|
||||
|
||||
$actual_contexts = $this->getCacheHeaderValues('X-Drupal-Cache-Contexts');
|
||||
sort($expected_contexts);
|
||||
sort($actual_contexts);
|
||||
$match = $actual_contexts === $expected_contexts;
|
||||
if (!$match) {
|
||||
debug('Unwanted cache contexts in response: ' . implode(',', array_diff($actual_contexts, $expected_contexts)));
|
||||
debug('Missing cache contexts in response: ' . implode(',', array_diff($expected_contexts, $actual_contexts)));
|
||||
}
|
||||
|
||||
$this->assertIdentical($actual_contexts, $expected_contexts, $message);
|
||||
|
||||
// For compatibility with both BrowserTestBase and WebTestBase always return
|
||||
// a boolean.
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the max age header.
|
||||
*
|
||||
* @param int $max_age
|
||||
*/
|
||||
protected function assertCacheMaxAge($max_age) {
|
||||
$cache_control_header = $this->drupalGetHeader('Cache-Control');
|
||||
if (strpos($cache_control_header, 'max-age:' . $max_age) === FALSE) {
|
||||
debug('Expected max-age:' . $max_age . '; Response max-age:' . $cache_control_header);
|
||||
}
|
||||
$this->assertTrue(strpos($cache_control_header, 'max-age:' . $max_age));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ namespace Drupal\Tests\system\Functional\Cache;
|
|||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
|
||||
/**
|
||||
* Provides helper methods for page cache tags tests.
|
||||
|
|
@ -44,14 +44,14 @@ abstract class PageCacheTagsTestBase extends BrowserTestBase {
|
|||
*/
|
||||
protected function verifyPageCache(Url $url, $hit_or_miss, $tags = FALSE) {
|
||||
$this->drupalGet($url);
|
||||
$message = SafeMarkup::format('Page cache @hit_or_miss for %path.', ['@hit_or_miss' => $hit_or_miss, '%path' => $url->toString()]);
|
||||
$message = new FormattableMarkup('Page cache @hit_or_miss for %path.', ['@hit_or_miss' => $hit_or_miss, '%path' => $url->toString()]);
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
|
||||
|
||||
if ($hit_or_miss === 'HIT' && is_array($tags)) {
|
||||
$absolute_url = $url->setAbsolute()->toString();
|
||||
$cid_parts = [$absolute_url, 'html'];
|
||||
$cid = implode(':', $cid_parts);
|
||||
$cache_entry = \Drupal::cache('render')->get($cid);
|
||||
$cache_entry = \Drupal::cache('page')->get($cid);
|
||||
sort($cache_entry->tags);
|
||||
$tags = array_unique($tags);
|
||||
sort($tags);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Cache;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the 'session.exists' cache context service.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class SessionExistsCacheContextTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['session_exists_cache_context_test'];
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Cache\Context\SessionExistsCacheContext::getContext().
|
||||
*/
|
||||
public function testCacheContext() {
|
||||
$this->dumpHeaders = TRUE;
|
||||
|
||||
// 1. No session (anonymous).
|
||||
$this->assertSessionCookieOnClient(FALSE);
|
||||
$this->drupalGet(Url::fromRoute('<front>'));
|
||||
$this->assertSessionCookieOnClient(FALSE);
|
||||
$this->assertRaw('Session does not exist!');
|
||||
$this->assertRaw('[session.exists]=0');
|
||||
|
||||
// 2. Session (authenticated).
|
||||
$this->assertSessionCookieOnClient(FALSE);
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->assertSessionCookieOnClient(TRUE);
|
||||
$this->assertRaw('Session exists!');
|
||||
$this->assertRaw('[session.exists]=1');
|
||||
$this->drupalLogout();
|
||||
$this->assertSessionCookieOnClient(FALSE);
|
||||
$this->assertRaw('Session does not exist!');
|
||||
$this->assertRaw('[session.exists]=0');
|
||||
|
||||
// 3. Session (anonymous).
|
||||
$this->assertSessionCookieOnClient(FALSE);
|
||||
$this->drupalGet(Url::fromRoute('<front>', [], ['query' => ['trigger_session' => 1]]));
|
||||
$this->assertSessionCookieOnClient(TRUE);
|
||||
$this->assertRaw('Session does not exist!');
|
||||
$this->assertRaw('[session.exists]=0');
|
||||
$this->drupalGet(Url::fromRoute('<front>'));
|
||||
$this->assertSessionCookieOnClient(TRUE);
|
||||
$this->assertRaw('Session exists!');
|
||||
$this->assertRaw('[session.exists]=1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts whether a session cookie is present on the client or not.
|
||||
*/
|
||||
public function assertSessionCookieOnClient($expected_present) {
|
||||
$this->assertEqual($expected_present, (bool) $this->getSession()->getCookie($this->getSessionName()), 'Session cookie exists.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests alteration of arguments passed to \Drupal::moduleHandler->alter().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class AlterTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'common_test'];
|
||||
|
||||
/**
|
||||
* Tests if the theme has been altered.
|
||||
*/
|
||||
public function testDrupalAlter() {
|
||||
// This test depends on Bartik, so make sure that it is always the current
|
||||
// active theme.
|
||||
\Drupal::service('theme_handler')->install(['bartik']);
|
||||
\Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('bartik'));
|
||||
|
||||
$array = ['foo' => 'bar'];
|
||||
$entity = new \stdClass();
|
||||
$entity->foo = 'bar';
|
||||
|
||||
// Verify alteration of a single argument.
|
||||
$array_copy = $array;
|
||||
$array_expected = ['foo' => 'Drupal theme'];
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $array_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $array_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'Single array was altered.');
|
||||
|
||||
$entity_copy = clone $entity;
|
||||
$entity_expected = clone $entity;
|
||||
$entity_expected->foo = 'Drupal theme';
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $entity_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $entity_copy);
|
||||
$this->assertEqual($entity_copy, $entity_expected, 'Single object was altered.');
|
||||
|
||||
// Verify alteration of multiple arguments.
|
||||
$array_copy = $array;
|
||||
$array_expected = ['foo' => 'Drupal theme'];
|
||||
$entity_copy = clone $entity;
|
||||
$entity_expected = clone $entity;
|
||||
$entity_expected->foo = 'Drupal theme';
|
||||
$array2_copy = $array;
|
||||
$array2_expected = ['foo' => 'Drupal theme'];
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'First argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
$this->assertEqual($entity_copy, $entity_expected, 'Second argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
$this->assertEqual($array2_copy, $array2_expected, 'Third argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
|
||||
// Verify alteration order when passing an array of types to \Drupal::moduleHandler->alter().
|
||||
// common_test_module_implements_alter() places 'block' implementation after
|
||||
// other modules.
|
||||
$array_copy = $array;
|
||||
$array_expected = ['foo' => 'Drupal block theme'];
|
||||
\Drupal::moduleHandler()->alter(['drupal_alter', 'drupal_alter_foo'], $array_copy);
|
||||
\Drupal::theme()->alter(['drupal_alter', 'drupal_alter_foo'], $array_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verifies that bubbleable metadata of early rendering is not lost.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class EarlyRenderingControllerTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'early_rendering_controller_test'];
|
||||
|
||||
/**
|
||||
* Tests theme preprocess functions being able to attach assets.
|
||||
*/
|
||||
public function testEarlyRendering() {
|
||||
// Render array: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertCacheTag('foo');
|
||||
|
||||
// AjaxResponse: non-early & early.
|
||||
// @todo Add cache tags assertion when AjaxResponse is made cacheable in
|
||||
// https://www.drupal.org/node/956186.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.ajax_response'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.ajax_response.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
|
||||
// Basic Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
|
||||
// Response object with attachments: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestResponse.');
|
||||
|
||||
// Cacheable Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\CacheableTestResponse.');
|
||||
|
||||
// Basic domain object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('TestDomainObject');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('TestDomainObject');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
|
||||
// Basic domain object with attachments: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('AttachmentsTestDomainObject');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.');
|
||||
|
||||
// Cacheable Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('CacheableTestDomainObject');
|
||||
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\CacheableTestDomainObject.');
|
||||
|
||||
// The exceptions are expected. Do not interpret them as a test failure.
|
||||
// Not using File API; a potential error must trigger a PHP warning.
|
||||
unlink($this->root . '/' . $this->siteDirectory . '/error.log');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the format_date() function.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class FormatDateTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Tests admin-defined formats in format_date().
|
||||
*/
|
||||
public function testAdminDefinedFormatDate() {
|
||||
// Create and log in an admin user.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
|
||||
|
||||
// Add new date format.
|
||||
$edit = [
|
||||
'id' => 'example_style',
|
||||
'label' => 'Example Style',
|
||||
'date_format_pattern' => 'j M y',
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
|
||||
|
||||
// Add a second date format with a different case than the first.
|
||||
$edit = [
|
||||
'id' => 'example_style_uppercase',
|
||||
'label' => 'Example Style Uppercase',
|
||||
'date_format_pattern' => 'j M Y',
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
|
||||
$this->assertText(t('Custom date format added.'));
|
||||
|
||||
$timestamp = strtotime('2007-03-10T00:00:00+00:00');
|
||||
$this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07');
|
||||
$this->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007');
|
||||
$this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'fallback'), 'Test format_date() defaulting to `fallback` when $type not found.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that anonymous users are not served any JavaScript in the Standard
|
||||
* installation profile.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class NoJavaScriptAnonymousTest extends BrowserTestBase {
|
||||
|
||||
protected $profile = 'standard';
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Grant the anonymous user the permission to look at user profiles.
|
||||
user_role_grant_permissions('anonymous', ['access user profiles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that anonymous users are not served any JavaScript.
|
||||
*/
|
||||
public function testNoJavaScript() {
|
||||
// Create a node that is listed on the frontpage.
|
||||
$this->drupalCreateNode([
|
||||
'promote' => NodeInterface::PROMOTED,
|
||||
]);
|
||||
$user = $this->drupalCreateUser();
|
||||
|
||||
// Test frontpage.
|
||||
$this->drupalGet('');
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
|
||||
// Test node page.
|
||||
$this->drupalGet('node/1');
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
|
||||
// Test user profile page.
|
||||
$this->drupalGet('user/' . $user->id());
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if no JavaScript is found on the page except the HTML5 shiv.
|
||||
*
|
||||
* The HTML5 shiv is necessary for e.g. the <article> tag which Drupal 8 uses
|
||||
* to work in older browsers like Internet Explorer 8.
|
||||
*/
|
||||
protected function assertNoJavaScriptExceptHtml5Shiv() {
|
||||
// Ensure drupalSettings is not set.
|
||||
$settings = $this->getDrupalSettings();
|
||||
$this->assertTrue(empty($settings), 'drupalSettings is not set.');
|
||||
|
||||
// Ensure the HTML5 shiv exists.
|
||||
$this->assertRaw('html5shiv/html5shiv.min.js', 'HTML5 shiv JavaScript exists.');
|
||||
|
||||
// Ensure no other JavaScript file exists on the page, while ignoring the
|
||||
// HTML5 shiv.
|
||||
$this->assertNoPattern('/(?<!html5shiv\.min)\.js/', "No other JavaScript exists.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Performs integration tests on drupal_render().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class RenderWebTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['common_test'];
|
||||
|
||||
/**
|
||||
* Asserts the cache context for the wrapper format is always present.
|
||||
*/
|
||||
public function testWrapperFormatCacheContext() {
|
||||
$this->drupalGet('common-test/type-link-active-class');
|
||||
$this->assertIdentical(0, strpos($this->getSession()->getPage()->getContent(), "<!DOCTYPE html>\n<html"));
|
||||
$this->assertIdentical('text/html; charset=UTF-8', $this->drupalGetHeader('Content-Type'));
|
||||
$this->assertTitle('Test active link class | Drupal');
|
||||
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
|
||||
|
||||
$this->drupalGet('common-test/type-link-active-class', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'json']]);
|
||||
$this->assertIdentical('application/json', $this->drupalGetHeader('Content-Type'));
|
||||
$json = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual(['content', 'title'], array_keys($json));
|
||||
$this->assertIdentical('Test active link class', $json['title']);
|
||||
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
|
||||
}
|
||||
|
||||
}
|
||||
320
web/core/modules/system/tests/src/Functional/Common/UrlTest.php
Normal file
320
web/core/modules/system/tests/src/Functional/Common/UrlTest.php
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Common;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Confirm that \Drupal\Core\Url,
|
||||
* \Drupal\Component\Utility\UrlHelper::filterQueryParameters(),
|
||||
* \Drupal\Component\Utility\UrlHelper::buildQuery(), and
|
||||
* \Drupal\Core\Utility\LinkGeneratorInterface::generate()
|
||||
* work correctly with various input.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class UrlTest extends BrowserTestBase {
|
||||
|
||||
public static $modules = ['common_test', 'url_alter_test'];
|
||||
|
||||
/**
|
||||
* Confirms that invalid URLs are filtered in link generating functions.
|
||||
*/
|
||||
public function testLinkXSS() {
|
||||
// Test \Drupal::l().
|
||||
$text = $this->randomMachineName();
|
||||
$path = "<SCRIPT>alert('XSS')</SCRIPT>";
|
||||
$encoded_path = "3CSCRIPT%3Ealert%28%27XSS%27%29%3C/SCRIPT%3E";
|
||||
|
||||
$link = \Drupal::l($text, Url::fromUserInput('/' . $path));
|
||||
$this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by \Drupal\Core\Utility\LinkGeneratorInterface::generate().', ['@path' => $path]));
|
||||
|
||||
// Test \Drupal\Core\Url.
|
||||
$link = Url::fromUri('base:' . $path)->toString();
|
||||
$this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that #type=link bubbles outbound route/path processors' metadata.
|
||||
*/
|
||||
public function testLinkBubbleableMetadata() {
|
||||
$cases = [
|
||||
['Regular link', 'internal:/user', [], ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT], []],
|
||||
['Regular link, absolute', 'internal:/user', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => [], 'max-age' => Cache::PERMANENT], []],
|
||||
['Route processor link', 'route:system.run_cron', [], ['contexts' => ['session'], 'tags' => [], 'max-age' => Cache::PERMANENT], ['placeholders' => []]],
|
||||
['Route processor link, absolute', 'route:system.run_cron', ['absolute' => TRUE], ['contexts' => ['url.site', 'session'], 'tags' => [], 'max-age' => Cache::PERMANENT], ['placeholders' => []]],
|
||||
['Path processor link', 'internal:/user/1', [], ['contexts' => [], 'tags' => ['user:1'], 'max-age' => Cache::PERMANENT], []],
|
||||
['Path processor link, absolute', 'internal:/user/1', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => ['user:1'], 'max-age' => Cache::PERMANENT], []],
|
||||
];
|
||||
|
||||
foreach ($cases as $case) {
|
||||
list($title, $uri, $options, $expected_cacheability, $expected_attachments) = $case;
|
||||
$expected_cacheability['contexts'] = Cache::mergeContexts($expected_cacheability['contexts'], ['languages:language_interface', 'theme', 'user.permissions']);
|
||||
$link = [
|
||||
'#type' => 'link',
|
||||
'#title' => $title,
|
||||
'#options' => $options,
|
||||
'#url' => Url::fromUri($uri),
|
||||
];
|
||||
\Drupal::service('renderer')->renderRoot($link);
|
||||
$this->pass($title);
|
||||
$this->assertEqual($expected_cacheability, $link['#cache']);
|
||||
$this->assertEqual($expected_attachments, $link['#attached']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default and custom attributes are handled correctly on links.
|
||||
*/
|
||||
public function testLinkAttributes() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Test that hreflang is added when a link has a known language.
|
||||
$language = new Language(['id' => 'fr', 'name' => 'French']);
|
||||
$hreflang_link = [
|
||||
'#type' => 'link',
|
||||
'#options' => [
|
||||
'language' => $language,
|
||||
],
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
'#title' => 'bar',
|
||||
];
|
||||
$langcode = $language->getId();
|
||||
|
||||
// Test that the default hreflang handling for links does not override a
|
||||
// hreflang attribute explicitly set in the render array.
|
||||
$hreflang_override_link = $hreflang_link;
|
||||
$hreflang_override_link['#options']['attributes']['hreflang'] = 'foo';
|
||||
|
||||
$rendered = $renderer->renderRoot($hreflang_link);
|
||||
$this->assertTrue($this->hasAttribute('hreflang', $rendered, $langcode), format_string('hreflang attribute with value @langcode is present on a rendered link when langcode is provided in the render array.', ['@langcode' => $langcode]));
|
||||
|
||||
$rendered = $renderer->renderRoot($hreflang_override_link);
|
||||
$this->assertTrue($this->hasAttribute('hreflang', $rendered, 'foo'), format_string('hreflang attribute with value @hreflang is present on a rendered link when @hreflang is provided in the render array.', ['@hreflang' => 'foo']));
|
||||
|
||||
// Test the active class in links produced by
|
||||
// \Drupal\Core\Utility\LinkGeneratorInterface::generate() and #type 'link'.
|
||||
$options_no_query = [];
|
||||
$options_query = [
|
||||
'query' => [
|
||||
'foo' => 'bar',
|
||||
'one' => 'two',
|
||||
],
|
||||
];
|
||||
$options_query_reverse = [
|
||||
'query' => [
|
||||
'one' => 'two',
|
||||
'foo' => 'bar',
|
||||
],
|
||||
];
|
||||
|
||||
// Test #type link.
|
||||
$path = 'common-test/type-link-active-class';
|
||||
|
||||
$this->drupalGet($path, $options_no_query);
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', [':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'is-active']);
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by the link generator to the current page is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', [':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'is-active']);
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by the link generator to the current page with a query string when the current page has no query string is not marked active.');
|
||||
|
||||
$this->drupalGet($path, $options_query);
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', [':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'is-active']);
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by the link generator to the current page with a query string that matches the current query string is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', [':href' => Url::fromRoute('common_test.l_active_class', [], $options_query_reverse)->toString(), ':class' => 'is-active']);
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by the link generator to the current page with a query string that has matching parameters to the current query string but in a different order is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', [':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'is-active']);
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by the link generator to the current page without a query string when the current page has a query string is not marked active.');
|
||||
|
||||
// Test adding a custom class in links produced by
|
||||
// \Drupal\Core\Utility\LinkGeneratorInterface::generate() and #type 'link'.
|
||||
// Test the link generator.
|
||||
$class_l = $this->randomMachineName();
|
||||
$link_l = \Drupal::l($this->randomMachineName(), new Url('<current>', [], ['attributes' => ['class' => [$class_l]]]));
|
||||
$this->assertTrue($this->hasAttribute('class', $link_l, $class_l), format_string('Custom class @class is present on link when requested by l()', ['@class' => $class_l]));
|
||||
|
||||
// Test #type.
|
||||
$class_theme = $this->randomMachineName();
|
||||
$type_link = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#url' => Url::fromRoute('<current>'),
|
||||
'#options' => [
|
||||
'attributes' => [
|
||||
'class' => [$class_theme],
|
||||
],
|
||||
],
|
||||
];
|
||||
$link_theme = $renderer->renderRoot($type_link);
|
||||
$this->assertTrue($this->hasAttribute('class', $link_theme, $class_theme), format_string('Custom class @class is present on link when requested by #type', ['@class' => $class_theme]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that link functions support render arrays as 'text'.
|
||||
*/
|
||||
public function testLinkRenderArrayText() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Build a link with the link generator for reference.
|
||||
$l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org'));
|
||||
|
||||
// Test a renderable array passed to the link generator.
|
||||
$renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, $l) {
|
||||
$renderable_text = ['#markup' => 'foo'];
|
||||
$l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
|
||||
$this->assertEqual($l_renderable_text, $l);
|
||||
});
|
||||
|
||||
// Test a themed link with plain text 'text'.
|
||||
$type_link_plain_array = [
|
||||
'#type' => 'link',
|
||||
'#title' => 'foo',
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
];
|
||||
$type_link_plain = $renderer->renderRoot($type_link_plain_array);
|
||||
$this->assertEqual($type_link_plain, $l);
|
||||
|
||||
// Build a themed link with renderable 'text'.
|
||||
$type_link_nested_array = [
|
||||
'#type' => 'link',
|
||||
'#title' => ['#markup' => 'foo'],
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
];
|
||||
$type_link_nested = $renderer->renderRoot($type_link_nested_array);
|
||||
$this->assertEqual($type_link_nested, $l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for class existence in link.
|
||||
*
|
||||
* @param $link
|
||||
* URL to search.
|
||||
* @param $class
|
||||
* Element class to search for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the class is found, FALSE otherwise.
|
||||
*/
|
||||
private function hasAttribute($attribute, $link, $class) {
|
||||
return preg_match('|' . $attribute . '="([^\"\s]+\s+)*' . $class . '|', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UrlHelper::filterQueryParameters().
|
||||
*/
|
||||
public function testDrupalGetQueryParameters() {
|
||||
$original = [
|
||||
'a' => 1,
|
||||
'b' => [
|
||||
'd' => 4,
|
||||
'e' => [
|
||||
'f' => 5,
|
||||
],
|
||||
],
|
||||
'c' => 3,
|
||||
];
|
||||
|
||||
// First-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, ['b']), $result, "'b' was removed.");
|
||||
|
||||
// Second-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']['d']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, ['b[d]']), $result, "'b[d]' was removed.");
|
||||
|
||||
// Third-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']['e']['f']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, ['b[e][f]']), $result, "'b[e][f]' was removed.");
|
||||
|
||||
// Multiple exclusions.
|
||||
$result = $original;
|
||||
unset($result['a'], $result['b']['e'], $result['c']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, ['a', 'b[e]', 'c']), $result, "'a', 'b[e]', 'c' were removed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UrlHelper::parse().
|
||||
*/
|
||||
public function testDrupalParseUrl() {
|
||||
// Relative, absolute, and external URLs, without/with explicit script path,
|
||||
// without/with Drupal path.
|
||||
foreach (['', '/', 'https://www.drupal.org/'] as $absolute) {
|
||||
foreach (['', 'index.php/'] as $script) {
|
||||
foreach (['', 'foo/bar'] as $path) {
|
||||
$url = $absolute . $script . $path . '?foo=bar&bar=baz&baz#foo';
|
||||
$expected = [
|
||||
'path' => $absolute . $script . $path,
|
||||
'query' => ['foo' => 'bar', 'bar' => 'baz', 'baz' => ''],
|
||||
'fragment' => 'foo',
|
||||
];
|
||||
$this->assertEqual(UrlHelper::parse($url), $expected, 'URL parsed correctly.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relative URL that is known to confuse parse_url().
|
||||
$url = 'foo/bar:1';
|
||||
$result = [
|
||||
'path' => 'foo/bar:1',
|
||||
'query' => [],
|
||||
'fragment' => '',
|
||||
];
|
||||
$this->assertEqual(UrlHelper::parse($url), $result, 'Relative URL parsed correctly.');
|
||||
|
||||
// Test that drupal can recognize an absolute URL. Used to prevent attack vectors.
|
||||
$url = 'https://www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
|
||||
$this->assertTrue(UrlHelper::isExternal($url), 'Correctly identified an external URL.');
|
||||
|
||||
// Test that UrlHelper::parse() does not allow spoofing a URL to force a malicious redirect.
|
||||
$parts = UrlHelper::parse('forged:http://cwe.mitre.org/data/definitions/601.html');
|
||||
$this->assertFalse(UrlHelper::isValid($parts['path'], TRUE), '\Drupal\Component\Utility\UrlHelper::isValid() correctly parsed a forged URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests external URL handling.
|
||||
*/
|
||||
public function testExternalUrls() {
|
||||
$test_url = 'https://www.drupal.org/';
|
||||
|
||||
// Verify external URL can contain a fragment.
|
||||
$url = $test_url . '#drupal';
|
||||
$result = Url::fromUri($url)->toString();
|
||||
$this->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.');
|
||||
|
||||
// Verify fragment can be overridden in an external URL.
|
||||
$url = $test_url . '#drupal';
|
||||
$fragment = $this->randomMachineName(10);
|
||||
$result = Url::fromUri($url, ['fragment' => $fragment])->toString();
|
||||
$this->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overridden with a custom fragment in $options.');
|
||||
|
||||
// Verify external URL can contain a query string.
|
||||
$url = $test_url . '?drupal=awesome';
|
||||
$result = Url::fromUri($url)->toString();
|
||||
$this->assertEqual($url, $result);
|
||||
|
||||
// Verify external URL can be extended with a query string.
|
||||
$url = $test_url;
|
||||
$query = ['awesome' => 'drupal'];
|
||||
$result = Url::fromUri($url, ['query' => $query])->toString();
|
||||
$this->assertSame('https://www.drupal.org/?awesome=drupal', $result);
|
||||
|
||||
// Verify query string can be extended in an external URL.
|
||||
$url = $test_url . '?drupal=awesome';
|
||||
$query = ['awesome' => 'drupal'];
|
||||
$result = Url::fromUri($url, ['query' => $query])->toString();
|
||||
$this->assertEqual('https://www.drupal.org/?drupal=awesome&awesome=drupal', $result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Condition;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that condition plugins basic form handling is working.
|
||||
*
|
||||
* Checks condition forms and submission and gives a very cursory check to make
|
||||
* sure the configuration that was submitted actually causes the condition to
|
||||
* validate correctly.
|
||||
*
|
||||
* @group Condition
|
||||
*/
|
||||
class ConditionFormTest extends BrowserTestBase {
|
||||
|
||||
public static $modules = ['node', 'condition_test'];
|
||||
|
||||
/**
|
||||
* Submit the condition_node_type_test_form to test condition forms.
|
||||
*/
|
||||
public function testConfigForm() {
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
|
||||
$article = Node::create([
|
||||
'type' => 'article',
|
||||
'title' => $this->randomMachineName(),
|
||||
]);
|
||||
$article->save();
|
||||
|
||||
$this->drupalGet('condition_test');
|
||||
$this->assertField('bundles[article]', 'There is an article bundle selector.');
|
||||
$this->assertField('bundles[page]', 'There is a page bundle selector.');
|
||||
$this->drupalPostForm(NULL, ['bundles[page]' => 'page', 'bundles[article]' => 'article'], t('Submit'));
|
||||
// @see \Drupal\condition_test\FormController::submitForm()
|
||||
$this->assertText('Bundle: page');
|
||||
$this->assertText('Bundle: article');
|
||||
$this->assertText('Executed successfully.', 'The form configured condition executed properly.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ namespace Drupal\Tests\system\Functional;
|
|||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
|
||||
/**
|
||||
* Tests protecting routes by requiring CSRF token in the request header.
|
||||
|
|
@ -27,7 +26,7 @@ class CsrfRequestHeaderTest extends BrowserTestBase {
|
|||
* uses the deprecated _access_rest_csrf.
|
||||
*/
|
||||
public function testRouteAccess() {
|
||||
$client = \Drupal::httpClient();
|
||||
$client = $this->getHttpClient();
|
||||
$csrf_token_paths = ['deprecated/session/token', 'session/token'];
|
||||
// Test using the both the current path and a test path that returns
|
||||
// a token using the deprecated 'rest' value.
|
||||
|
|
@ -44,11 +43,6 @@ class CsrfRequestHeaderTest extends BrowserTestBase {
|
|||
$url = Url::fromRoute($route_name)
|
||||
->setAbsolute(TRUE)
|
||||
->toString();
|
||||
$domain = parse_url($url, PHP_URL_HOST);
|
||||
|
||||
$session_id = $this->getSession()->getCookie($this->getSessionName());
|
||||
/** @var \GuzzleHttp\Cookie\CookieJar $cookies */
|
||||
$cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
|
||||
$post_options = [
|
||||
'headers' => ['Accept' => 'text/plain'],
|
||||
'http_errors' => FALSE,
|
||||
|
|
@ -60,7 +54,7 @@ class CsrfRequestHeaderTest extends BrowserTestBase {
|
|||
|
||||
// Add cookies to POST options so that all other requests are for the
|
||||
// authenticated user.
|
||||
$post_options['cookies'] = $cookies;
|
||||
$post_options['cookies'] = $this->getSessionCookies();
|
||||
|
||||
// Test that access is denied with no token in header.
|
||||
$result = $client->post($url, $post_options);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Database;
|
||||
|
||||
use Drupal\KernelTests\Core\Database\DatabaseTestBase as DatabaseKernelTestBase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Base class for databases database tests.
|
||||
*/
|
||||
abstract class DatabaseTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['database_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
DatabaseKernelTestBase::addSampleData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Database;
|
||||
|
||||
/**
|
||||
* Fetches into a class.
|
||||
*
|
||||
* PDO supports using a new instance of an arbitrary class for records
|
||||
* rather than just a stdClass or array. This class is for testing that
|
||||
* functionality. (See testQueryFetchClass() below)
|
||||
*/
|
||||
class FakeRecord {}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Database;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the pager query select extender.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectPagerDefaultTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that a pager query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
public function testEvenPagerQuery() {
|
||||
// To keep the test from being too brittle, we determine up front
|
||||
// what the page count should be dynamically, and pass the control
|
||||
// information forward to the actual query on the other side of the
|
||||
// HTTP request.
|
||||
$limit = 2;
|
||||
$count = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$correct_number = $limit;
|
||||
$num_pages = floor($count / $limit);
|
||||
|
||||
// If there is no remainder from rounding, subtract 1 since we index from 0.
|
||||
if (!($num_pages * $limit < $count)) {
|
||||
$num_pages--;
|
||||
}
|
||||
|
||||
for ($page = 0; $page <= $num_pages; ++$page) {
|
||||
$this->drupalGet('database_test/pager_query_even/' . $limit, ['query' => ['page' => $page]]);
|
||||
$data = json_decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
if ($page == $num_pages) {
|
||||
$correct_number = $count - ($limit * $page);
|
||||
}
|
||||
|
||||
$this->assertCount($correct_number, $data->names, format_string('Correct number of records returned by pager: @number', ['@number' => $correct_number]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a pager query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
public function testOddPagerQuery() {
|
||||
// To keep the test from being too brittle, we determine up front
|
||||
// what the page count should be dynamically, and pass the control
|
||||
// information forward to the actual query on the other side of the
|
||||
// HTTP request.
|
||||
$limit = 2;
|
||||
$count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
|
||||
|
||||
$correct_number = $limit;
|
||||
$num_pages = floor($count / $limit);
|
||||
|
||||
// If there is no remainder from rounding, subtract 1 since we index from 0.
|
||||
if (!($num_pages * $limit < $count)) {
|
||||
$num_pages--;
|
||||
}
|
||||
|
||||
for ($page = 0; $page <= $num_pages; ++$page) {
|
||||
$this->drupalGet('database_test/pager_query_odd/' . $limit, ['query' => ['page' => $page]]);
|
||||
$data = json_decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
if ($page == $num_pages) {
|
||||
$correct_number = $count - ($limit * $page);
|
||||
}
|
||||
|
||||
$this->assertCount($correct_number, $data->names, format_string('Correct number of records returned by pager: @number', ['@number' => $correct_number]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a pager query results with an inner pager query are valid.
|
||||
*
|
||||
* This is a regression test for #467984.
|
||||
*/
|
||||
public function testInnerPagerQuery() {
|
||||
$query = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
|
||||
$query
|
||||
->fields('t', ['age'])
|
||||
->orderBy('age')
|
||||
->limit(5);
|
||||
|
||||
$outer_query = db_select($query);
|
||||
$outer_query->addField('subquery', 'age');
|
||||
|
||||
$ages = $outer_query
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertEqual($ages, [25, 26, 27, 28], 'Inner pager query returned the correct ages.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a paging query results with a having expression are valid.
|
||||
*
|
||||
* This is a regression test for #467984.
|
||||
*/
|
||||
public function testHavingPagerQuery() {
|
||||
$query = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
|
||||
$query
|
||||
->fields('t', ['name'])
|
||||
->orderBy('name')
|
||||
->groupBy('name')
|
||||
->having('MAX(age) > :count', [':count' => 26])
|
||||
->limit(5);
|
||||
|
||||
$ages = $query
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertEqual($ages, ['George', 'Ringo'], 'Pager query with having expression returned the correct ages.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that every pager gets a valid, non-overlapping element ID.
|
||||
*/
|
||||
public function testElementNumbers() {
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace([
|
||||
'page' => '3, 2, 1, 0',
|
||||
]);
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->element(2)
|
||||
->fields('t', ['name'])
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
|
||||
|
||||
// Setting an element smaller than the previous one
|
||||
// should not overwrite the pager $maxElement with a smaller value.
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->element(1)
|
||||
->fields('t', ['name'])
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.');
|
||||
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->fields('t', ['name'])
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Database;
|
||||
|
||||
/**
|
||||
* Tests the tablesort query extender.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectTableSortDefaultTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that a tablesort query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
public function testTableSortQuery() {
|
||||
$sorts = [
|
||||
['field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'],
|
||||
['field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'],
|
||||
['field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'],
|
||||
['field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'],
|
||||
// more elements here
|
||||
|
||||
];
|
||||
|
||||
foreach ($sorts as $sort) {
|
||||
$this->drupalGet('database_test/tablesort/', ['query' => ['order' => $sort['field'], 'sort' => $sort['sort']]]);
|
||||
$data = json_decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
$first = array_shift($data->tasks);
|
||||
$last = array_pop($data->tasks);
|
||||
|
||||
$this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
|
||||
$this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms precedence of tablesorts headers.
|
||||
*
|
||||
* If a tablesort's orderByHeader is called before another orderBy, then its
|
||||
* header happens first.
|
||||
*/
|
||||
public function testTableSortQueryFirst() {
|
||||
$sorts = [
|
||||
['field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'],
|
||||
['field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'],
|
||||
['field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'],
|
||||
['field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'],
|
||||
// more elements here
|
||||
|
||||
];
|
||||
|
||||
foreach ($sorts as $sort) {
|
||||
$this->drupalGet('database_test/tablesort_first/', ['query' => ['order' => $sort['field'], 'sort' => $sort['sort']]]);
|
||||
$data = json_decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
$first = array_shift($data->tasks);
|
||||
$last = array_pop($data->tasks);
|
||||
|
||||
$this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']]));
|
||||
$this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that tableselect is rendered without error.
|
||||
*
|
||||
* Specifically that no sort is set in a tableselect, and that header links
|
||||
* are correct.
|
||||
*/
|
||||
public function testTableSortDefaultSort() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$this->drupalGet('database_test/tablesort_default_sort');
|
||||
|
||||
// Verify that the table was displayed. Just the header is checked for
|
||||
// because if there were any fatal errors or exceptions in displaying the
|
||||
// sorted table, it would not print the table.
|
||||
$assert->pageTextContains(t('Username'));
|
||||
|
||||
// Verify that the header links are built properly.
|
||||
$assert->linkByHrefExists('database_test/tablesort_default_sort');
|
||||
$assert->responseMatches('/\<a.*title\=\"' . t('sort by Username') . '\".*\>/');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Tests the temporary query functionality.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class TemporaryQueryTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['database_test'];
|
||||
|
||||
/**
|
||||
* Returns the number of rows of a table.
|
||||
*/
|
||||
public function countTableRows($table_name) {
|
||||
return db_select($table_name)->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that temporary tables work and are limited to one request.
|
||||
*/
|
||||
public function testTemporaryQuery() {
|
||||
$this->drupalGet('database_test/db_query_temporary');
|
||||
$data = json_decode($this->getSession()->getPage()->getContent());
|
||||
if ($data) {
|
||||
$this->assertEqual($this->countTableRows('test'), $data->row_count, 'The temporary table contains the correct amount of rows.');
|
||||
$this->assertFalse(Database::getConnection()->schema()->tableExists($data->table_name), 'The temporary table is, indeed, temporary.');
|
||||
}
|
||||
else {
|
||||
$this->fail('The creation of the temporary table failed.');
|
||||
}
|
||||
|
||||
// Now try to run two db_query_temporary() in the same request.
|
||||
$table_name_test = db_query_temporary('SELECT name FROM {test}', []);
|
||||
$table_name_task = db_query_temporary('SELECT pid FROM {test_task}', []);
|
||||
|
||||
$this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'A temporary table was created successfully in this request.');
|
||||
$this->assertEqual($this->countTableRows($table_name_task), $this->countTableRows('test_task'), 'A second temporary table was created successfully in this request.');
|
||||
|
||||
// Check that leading whitespace and comments do not cause problems
|
||||
// in the modified query.
|
||||
$sql = "
|
||||
-- Let's select some rows into a temporary table
|
||||
SELECT name FROM {test}
|
||||
";
|
||||
$table_name_test = db_query_temporary($sql, []);
|
||||
$this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ class DrupalDateTimeTest extends BrowserTestBase {
|
|||
$options = [
|
||||
'query' => [
|
||||
'date' => 'Tue+Sep+17+2013+21%3A35%3A31+GMT%2B0100+(BST)#',
|
||||
]
|
||||
],
|
||||
];
|
||||
// Query the AJAX Timezone Callback with a long-format date.
|
||||
$response = $this->drupalGet('system/timezone/BST/3600/1', $options);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\DrupalKernel;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Ensures that the container rebuild works as expected.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class ContainerRebuildWebTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['service_provider_test'];
|
||||
|
||||
/**
|
||||
* Sets a different deployment identifier.
|
||||
*/
|
||||
public function testSetContainerRebuildWithDifferentDeploymentIdentifier() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Ensure the parameter is not set.
|
||||
$this->drupalGet('<front>');
|
||||
$assert->responseHeaderEquals('container_rebuild_indicator', NULL);
|
||||
|
||||
$this->writeSettings(['settings' => ['deployment_identifier' => (object) ['value' => 'new-identifier', 'required' => TRUE]]]);
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
$assert->responseHeaderEquals('container_rebuild_indicator', 'new-identifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests container invalidation.
|
||||
*/
|
||||
public function testContainerInvalidation() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Ensure that parameter is not set.
|
||||
$this->drupalGet('<front>');
|
||||
$assert->responseHeaderEquals('container_rebuild_test_parameter', NULL);
|
||||
|
||||
// Ensure that after setting the parameter, without a container rebuild the
|
||||
// parameter is still not set.
|
||||
$this->writeSettings(['settings' => ['container_rebuild_test_parameter' => (object) ['value' => 'rebuild_me_please', 'required' => TRUE]]]);
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$assert->responseHeaderEquals('container_rebuild_test_parameter', NULL);
|
||||
|
||||
// Ensure that after container invalidation the parameter is set.
|
||||
\Drupal::service('kernel')->invalidateContainer();
|
||||
$this->drupalGet('<front>');
|
||||
$assert->responseHeaderEquals('container_rebuild_test_parameter', 'rebuild_me_please');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ class ConfigEntityImportTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['action', 'block', 'filter', 'image', 'search', 'search_extra_type'];
|
||||
public static $modules = ['action', 'block', 'filter', 'image', 'search', 'search_extra_type', 'config_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
@ -41,7 +41,9 @@ class ConfigEntityImportTest extends BrowserTestBase {
|
|||
$this->doFilterFormatUpdate();
|
||||
$this->doImageStyleUpdate();
|
||||
$this->doSearchPageUpdate();
|
||||
$this->doThirdPartySettingsUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a action during import.
|
||||
*/
|
||||
|
|
@ -171,6 +173,34 @@ class ConfigEntityImportTest extends BrowserTestBase {
|
|||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating of third party settings.
|
||||
*/
|
||||
protected function doThirdPartySettingsUpdate() {
|
||||
// Create a test action with a known label.
|
||||
$name = 'system.action.third_party_settings_test';
|
||||
|
||||
/** @var \Drupal\config_test\Entity\ConfigTest $entity */
|
||||
$entity = Action::create([
|
||||
'id' => 'third_party_settings_test',
|
||||
'plugin' => 'action_message_action',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->assertIdentical([], $entity->getThirdPartyProviders());
|
||||
// Get a copy of the configuration before the third party setting is added.
|
||||
$no_third_part_setting_config = $this->container->get('config.storage')->read($name);
|
||||
|
||||
// Add a third party setting.
|
||||
$entity->setThirdPartySetting('config_test', 'integer', 1);
|
||||
$entity->save();
|
||||
$this->assertIdentical(1, $entity->getThirdPartySetting('config_test', 'integer'));
|
||||
$has_third_part_setting_config = $this->container->get('config.storage')->read($name);
|
||||
|
||||
// Ensure configuration imports can completely remove third party settings.
|
||||
$this->assertConfigUpdateImport($name, $has_third_part_setting_config, $no_third_part_setting_config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a single set of plugin config stays in sync.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Drupal\Core\Language\LanguageInterface;
|
|||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
* @return string[]
|
||||
* An array of the additional cache contexts.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
* @see \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
*/
|
||||
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
|
||||
return [];
|
||||
|
|
@ -167,7 +167,7 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
* @return array
|
||||
* An array of the additional cache tags.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
* @see \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
*/
|
||||
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
|
||||
return [];
|
||||
|
|
@ -412,7 +412,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
|
||||
$this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
|
||||
|
||||
|
||||
$this->pass("Test listing of referencing entities.", 'Debug');
|
||||
// Prime the page cache for the listing of referencing entities.
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
|
|
@ -431,7 +430,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
|
||||
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
|
||||
|
||||
|
||||
$this->pass("Test listing containing referenced entity.", 'Debug');
|
||||
// Prime the page cache for the listing containing the referenced entity.
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
|
||||
|
|
@ -441,7 +439,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
|
||||
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
|
||||
|
||||
|
||||
// Verify that after modifying the referenced entity, there is a cache miss
|
||||
// for every route except the one for the non-referencing entity.
|
||||
$this->pass("Test modification of referenced entity.", 'Debug');
|
||||
|
|
@ -458,7 +455,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying the referencing entity, there is a cache miss
|
||||
// for every route except the ones for the non-referencing entity and the
|
||||
// empty entity listing.
|
||||
|
|
@ -475,7 +471,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying the non-referencing entity, there is a cache
|
||||
// miss only for the non-referencing entity route.
|
||||
$this->pass("Test modification of non-referencing entity.", 'Debug');
|
||||
|
|
@ -489,7 +484,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
// Verify cache hits.
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
|
||||
if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
|
||||
// Verify that after modifying the entity's display, there is a cache miss
|
||||
// for both the referencing entity, and the listing of referencing
|
||||
|
|
@ -509,7 +503,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
}
|
||||
|
||||
|
||||
if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
|
||||
// Verify that after modifying the corresponding bundle entity, there is a
|
||||
// cache miss for both the referencing entity, and the listing of
|
||||
|
|
@ -543,7 +536,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->entity->getEntityType()->get('field_ui_base_route')) {
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
|
|
@ -561,7 +553,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
$this->pass("Test modification of referenced entity's configurable field.", 'Debug');
|
||||
|
|
@ -579,7 +570,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
}
|
||||
|
||||
|
||||
// Verify that after invalidating the entity's cache tag directly, there is
|
||||
// a cache miss for every route except the ones for the non-referencing
|
||||
// entity and the empty entity listing.
|
||||
|
|
@ -611,7 +601,6 @@ abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
|||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
if (!empty($view_cache_tag)) {
|
||||
// Verify that after invalidating the generic entity type's view cache tag
|
||||
// directly, there is a cache miss for both the referencing entity, and the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the entity form.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['entity_test', 'language'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$web_user = $this->drupalCreateUser(['administer entity_test content', 'view test entity']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Add a language.
|
||||
ConfigurableLanguage::createFromLangcode('ro')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic form CRUD functionality.
|
||||
*/
|
||||
public function testFormCRUD() {
|
||||
// All entity variations have to have the same results.
|
||||
foreach (entity_test_entity_types() as $entity_type) {
|
||||
$this->doTestFormCRUD($entity_type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic multilingual form CRUD functionality.
|
||||
*/
|
||||
public function testMultilingualFormCRUD() {
|
||||
// All entity variations have to have the same results.
|
||||
foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) {
|
||||
$this->doTestMultilingualFormCRUD($entity_type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_entity_form_display_alter().
|
||||
*
|
||||
* @see entity_test_entity_form_display_alter()
|
||||
*/
|
||||
public function testEntityFormDisplayAlter() {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$altered_field = $this->xpath('//input[@name="field_test_text[0][value]" and @size="42"]');
|
||||
$this->assertTrue(count($altered_field) === 1, 'The altered field has the correct size value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the form CRUD tests for the given entity type.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to run the tests with.
|
||||
*/
|
||||
protected function doTestFormCRUD($entity_type) {
|
||||
$name1 = $this->randomMachineName(8);
|
||||
$name2 = $this->randomMachineName(10);
|
||||
|
||||
$edit = [
|
||||
'name[0][value]' => $name1,
|
||||
'field_test_text[0][value]' => $this->randomMachineName(16),
|
||||
];
|
||||
|
||||
$this->drupalPostForm($entity_type . '/add', $edit, t('Save'));
|
||||
$entity = $this->loadEntityByName($entity_type, $name1);
|
||||
$this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type]));
|
||||
|
||||
$edit['name[0][value]'] = $name2;
|
||||
$this->drupalPostForm($entity_type . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
|
||||
$entity = $this->loadEntityByName($entity_type, $name1);
|
||||
$this->assertFalse($entity, format_string('%entity_type: The entity has been modified.', ['%entity_type' => $entity_type]));
|
||||
$entity = $this->loadEntityByName($entity_type, $name2);
|
||||
$this->assertTrue($entity, format_string('%entity_type: Modified entity found in the database.', ['%entity_type' => $entity_type]));
|
||||
$this->assertNotEqual($entity->name->value, $name1, format_string('%entity_type: The entity name has been modified.', ['%entity_type' => $entity_type]));
|
||||
|
||||
$this->drupalGet($entity_type . '/manage/' . $entity->id() . '/edit');
|
||||
$this->clickLink(t('Delete'));
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$entity = $this->loadEntityByName($entity_type, $name2);
|
||||
$this->assertFalse($entity, format_string('%entity_type: Entity not found in the database.', ['%entity_type' => $entity_type]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the multilingual form CRUD tests for the given entity type ID.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of entity type to run the tests with.
|
||||
*/
|
||||
protected function doTestMultilingualFormCRUD($entity_type_id) {
|
||||
$name1 = $this->randomMachineName(8);
|
||||
$name1_ro = $this->randomMachineName(9);
|
||||
$name2_ro = $this->randomMachineName(11);
|
||||
|
||||
$edit = [
|
||||
'name[0][value]' => $name1,
|
||||
'field_test_text[0][value]' => $this->randomMachineName(16),
|
||||
];
|
||||
|
||||
$this->drupalPostForm($entity_type_id . '/add', $edit, t('Save'));
|
||||
$entity = $this->loadEntityByName($entity_type_id, $name1);
|
||||
$this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type_id]));
|
||||
|
||||
// Add a translation to the newly created entity without using the Content
|
||||
// translation module.
|
||||
$entity->addTranslation('ro', ['name' => $name1_ro])->save();
|
||||
$translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro');
|
||||
$this->assertEqual($translated_entity->name->value, $name1_ro, format_string('%entity_type: The translation has been added.', ['%entity_type' => $entity_type_id]));
|
||||
|
||||
$edit['name[0][value]'] = $name2_ro;
|
||||
$this->drupalPostForm('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
|
||||
$translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro');
|
||||
$this->assertTrue($translated_entity, format_string('%entity_type: Modified translation found in the database.', ['%entity_type' => $entity_type_id]));
|
||||
$this->assertEqual($translated_entity->name->value, $name2_ro, format_string('%entity_type: The name of the translation has been modified.', ['%entity_type' => $entity_type_id]));
|
||||
|
||||
$this->drupalGet('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit');
|
||||
$this->clickLink(t('Delete'));
|
||||
$this->drupalPostForm(NULL, [], t('Delete Romanian translation'));
|
||||
$entity = $this->loadEntityByName($entity_type_id, $name1);
|
||||
$this->assertNotNull($entity, format_string('%entity_type: The original entity still exists.', ['%entity_type' => $entity_type_id]));
|
||||
$this->assertFalse($entity->hasTranslation('ro'), format_string('%entity_type: Entity translation does not exist anymore.', ['%entity_type' => $entity_type_id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a test entity by name always resetting the storage cache.
|
||||
*/
|
||||
protected function loadEntityByName($entity_type, $name) {
|
||||
// Always load the entity from the database to ensure that changes are
|
||||
// correctly picked up.
|
||||
$entity_storage = $this->container->get('entity.manager')->getStorage($entity_type);
|
||||
$entity_storage->resetCache();
|
||||
$entities = $entity_storage->loadByProperties(['name' => $name]);
|
||||
return $entities ? current($entities) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that validation handlers works as expected.
|
||||
*/
|
||||
public function testValidationHandlers() {
|
||||
/** @var \Drupal\Core\State\StateInterface $state */
|
||||
$state = $this->container->get('state');
|
||||
|
||||
// Check that from-level validation handlers can be defined and can alter
|
||||
// the form array.
|
||||
$state->set('entity_test.form.validate.test', 'form-level');
|
||||
$this->drupalPostForm('entity_test/add', [], 'Save');
|
||||
$this->assertTrue($state->get('entity_test.form.validate.result'), 'Form-level validation handlers behave correctly.');
|
||||
|
||||
// Check that defining a button-level validation handler causes an exception
|
||||
// to be thrown.
|
||||
$state->set('entity_test.form.validate.test', 'button-level');
|
||||
$this->drupalPostForm('entity_test/add', [], 'Save');
|
||||
$this->assertEqual($state->get('entity_test.form.save.exception'), 'Drupal\Core\Entity\EntityStorageException: Entity validation was skipped.', 'Button-level validation handlers behave correctly.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,9 +6,15 @@ use Drupal\comment\Tests\CommentTestTrait;
|
|||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\comment\CommentInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\media\Entity\Media;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\comment\Entity\Comment;
|
||||
|
||||
|
|
@ -17,22 +23,51 @@ use Drupal\comment\Entity\Comment;
|
|||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
||||
class EntityReferenceSelectionAccessTest extends KernelTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
use ContentTypeCreationTrait;
|
||||
use MediaTypeCreationTrait;
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node', 'comment'];
|
||||
public static $modules = ['comment', 'field', 'file', 'image', 'node', 'media', 'system', 'taxonomy', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create an Article node type.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
$this->installSchema('system', 'sequences');
|
||||
$this->installSchema('comment', ['comment_entity_statistics']);
|
||||
$this->installSchema('file', ['file_usage']);
|
||||
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('media');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$this->installConfig(['comment', 'field', 'media', 'node', 'taxonomy', 'user']);
|
||||
|
||||
// Create the anonymous and the admin users.
|
||||
$anonymous_user = User::create([
|
||||
'uid' => 0,
|
||||
'name' => '',
|
||||
]);
|
||||
$anonymous_user->save();
|
||||
$admin_user = User::create([
|
||||
'uid' => 1,
|
||||
'name' => 'admin',
|
||||
'status' => 1,
|
||||
]);
|
||||
$admin_user->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,9 +109,7 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$selection_options = [
|
||||
'target_type' => 'node',
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'target_bundles' => NULL,
|
||||
],
|
||||
'target_bundles' => NULL,
|
||||
];
|
||||
|
||||
// Build a set of test data.
|
||||
|
|
@ -112,8 +145,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
}
|
||||
|
||||
// Test as a non-admin.
|
||||
$normal_user = $this->drupalCreateUser(['access content']);
|
||||
\Drupal::currentUser()->setAccount($normal_user);
|
||||
$normal_user = $this->createUser(['access content']);
|
||||
$this->setCurrentUser($normal_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -164,8 +197,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Node handler');
|
||||
|
||||
// Test as an admin.
|
||||
$admin_user = $this->drupalCreateUser(['access content', 'bypass node access']);
|
||||
\Drupal::currentUser()->setAccount($admin_user);
|
||||
$content_admin = $this->createUser(['access content', 'bypass node access']);
|
||||
$this->setCurrentUser($content_admin);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -200,10 +233,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$selection_options = [
|
||||
'target_type' => 'user',
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'target_bundles' => NULL,
|
||||
'include_anonymous' => TRUE,
|
||||
],
|
||||
'target_bundles' => NULL,
|
||||
'include_anonymous' => TRUE,
|
||||
];
|
||||
|
||||
// Build a set of test data.
|
||||
|
|
@ -243,7 +274,7 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
}
|
||||
|
||||
// Test as a non-admin.
|
||||
\Drupal::currentUser()->setAccount($users['non_admin']);
|
||||
$this->setCurrentUser($users['non_admin']);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -282,7 +313,7 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
];
|
||||
$this->assertReferenceable($selection_options, $referenceable_tests, 'User handler');
|
||||
|
||||
\Drupal::currentUser()->setAccount($users['admin']);
|
||||
$this->setCurrentUser($users['admin']);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -322,7 +353,7 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$this->assertReferenceable($selection_options, $referenceable_tests, 'User handler (admin)');
|
||||
|
||||
// Test the 'include_anonymous' option.
|
||||
$selection_options['handler_settings']['include_anonymous'] = FALSE;
|
||||
$selection_options['include_anonymous'] = FALSE;
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -361,12 +392,11 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$selection_options = [
|
||||
'target_type' => 'comment',
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'target_bundles' => NULL,
|
||||
],
|
||||
'target_bundles' => NULL,
|
||||
];
|
||||
|
||||
// Build a set of test data.
|
||||
$this->createContentType(['type' => 'article', 'name' => 'Article']);
|
||||
$node_values = [
|
||||
'published' => [
|
||||
'type' => 'article',
|
||||
|
|
@ -437,8 +467,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
}
|
||||
|
||||
// Test as a non-admin.
|
||||
$normal_user = $this->drupalCreateUser(['access content', 'access comments']);
|
||||
\Drupal::currentUser()->setAccount($normal_user);
|
||||
$normal_user = $this->createUser(['access content', 'access comments']);
|
||||
$this->setCurrentUser($normal_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -476,8 +506,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Comment handler');
|
||||
|
||||
// Test as a comment admin.
|
||||
$admin_user = $this->drupalCreateUser(['access content', 'access comments', 'administer comments']);
|
||||
\Drupal::currentUser()->setAccount($admin_user);
|
||||
$admin_user = $this->createUser(['access content', 'access comments', 'administer comments']);
|
||||
$this->setCurrentUser($admin_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -494,8 +524,8 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Comment handler (comment admin)');
|
||||
|
||||
// Test as a node and comment admin.
|
||||
$admin_user = $this->drupalCreateUser(['access content', 'access comments', 'administer comments', 'bypass node access']);
|
||||
\Drupal::currentUser()->setAccount($admin_user);
|
||||
$admin_user = $this->createUser(['access content', 'access comments', 'administer comments', 'bypass node access']);
|
||||
$this->setCurrentUser($admin_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
|
|
@ -513,4 +543,236 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase {
|
|||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Comment handler (comment + node admin)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the term-specific overrides of the selection handler.
|
||||
*/
|
||||
public function testTermHandler() {
|
||||
// Create a 'Tags' vocabulary.
|
||||
Vocabulary::create([
|
||||
'name' => 'Tags',
|
||||
'description' => $this->randomMachineName(),
|
||||
'vid' => 'tags',
|
||||
])->save();
|
||||
|
||||
$selection_options = [
|
||||
'target_type' => 'taxonomy_term',
|
||||
'handler' => 'default',
|
||||
'target_bundles' => NULL,
|
||||
];
|
||||
|
||||
// Build a set of test data.
|
||||
$term_values = [
|
||||
'published1' => [
|
||||
'vid' => 'tags',
|
||||
'status' => 1,
|
||||
'name' => 'Term published1',
|
||||
],
|
||||
'published2' => [
|
||||
'vid' => 'tags',
|
||||
'status' => 1,
|
||||
'name' => 'Term published2',
|
||||
],
|
||||
'unpublished' => [
|
||||
'vid' => 'tags',
|
||||
'status' => 0,
|
||||
'name' => 'Term unpublished',
|
||||
],
|
||||
'published3' => [
|
||||
'vid' => 'tags',
|
||||
'status' => 1,
|
||||
'name' => 'Term published3',
|
||||
'parent' => 'unpublished',
|
||||
],
|
||||
'published4' => [
|
||||
'vid' => 'tags',
|
||||
'status' => 1,
|
||||
'name' => 'Term published4',
|
||||
'parent' => 'published3',
|
||||
],
|
||||
];
|
||||
|
||||
$terms = [];
|
||||
$term_labels = [];
|
||||
foreach ($term_values as $key => $values) {
|
||||
$term = Term::create($values);
|
||||
if (isset($values['parent'])) {
|
||||
$term->parent->entity = $terms[$values['parent']];
|
||||
}
|
||||
$term->save();
|
||||
$terms[$key] = $term;
|
||||
$term_labels[$key] = Html::escape($term->label());
|
||||
}
|
||||
|
||||
// Test as a non-admin.
|
||||
$normal_user = $this->createUser(['access content']);
|
||||
$this->setCurrentUser($normal_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
[NULL, 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
'tags' => [
|
||||
$terms['published1']->id() => $term_labels['published1'],
|
||||
$terms['published2']->id() => $term_labels['published2'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['published1', 'CONTAINS'],
|
||||
['Published1', 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
'tags' => [
|
||||
$terms['published1']->id() => $term_labels['published1'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['published2', 'CONTAINS'],
|
||||
['Published2', 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
'tags' => [
|
||||
$terms['published2']->id() => $term_labels['published2'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['invalid term', 'CONTAINS'],
|
||||
],
|
||||
'result' => [],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['Term unpublished', 'CONTAINS'],
|
||||
],
|
||||
'result' => [],
|
||||
],
|
||||
];
|
||||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Term handler');
|
||||
|
||||
// Test as an admin.
|
||||
$admin_user = $this->createUser(['access content', 'administer taxonomy']);
|
||||
$this->setCurrentUser($admin_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
[NULL, 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
'tags' => [
|
||||
$terms['published1']->id() => $term_labels['published1'],
|
||||
$terms['published2']->id() => $term_labels['published2'],
|
||||
$terms['unpublished']->id() => $term_labels['unpublished'],
|
||||
$terms['published3']->id() => '-' . $term_labels['published3'],
|
||||
$terms['published4']->id() => '--' . $term_labels['published4'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['Term unpublished', 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
'tags' => [
|
||||
$terms['unpublished']->id() => $term_labels['unpublished'],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Term handler (admin)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the selection handler for the media entity type.
|
||||
*/
|
||||
public function testMediaHandler() {
|
||||
$selection_options = [
|
||||
'target_type' => 'media',
|
||||
'handler' => 'default',
|
||||
'target_bundles' => NULL,
|
||||
];
|
||||
|
||||
// Build a set of test data.
|
||||
$media_type = $this->createMediaType('file');
|
||||
$media_values = [
|
||||
'published' => [
|
||||
'bundle' => $media_type->id(),
|
||||
'status' => 1,
|
||||
'name' => 'Media published',
|
||||
'uid' => 1,
|
||||
],
|
||||
'unpublished' => [
|
||||
'bundle' => $media_type->id(),
|
||||
'status' => 0,
|
||||
'name' => 'Media unpublished',
|
||||
'uid' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$media_entities = [];
|
||||
$media_labels = [];
|
||||
foreach ($media_values as $key => $values) {
|
||||
$media = Media::create($values);
|
||||
$media->save();
|
||||
$media_entities[$key] = $media;
|
||||
$media_labels[$key] = Html::escape($media->label());
|
||||
}
|
||||
|
||||
// Test as a non-admin.
|
||||
$normal_user = $this->createUser(['view media']);
|
||||
$this->setCurrentUser($normal_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
[NULL, 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
$media_type->id() => [
|
||||
$media_entities['published']->id() => $media_labels['published'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['Media unpublished', 'CONTAINS'],
|
||||
],
|
||||
'result' => [],
|
||||
],
|
||||
];
|
||||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Media handler');
|
||||
|
||||
// Test as an admin.
|
||||
$admin_user = $this->createUser(['view media', 'administer media']);
|
||||
$this->setCurrentUser($admin_user);
|
||||
$referenceable_tests = [
|
||||
[
|
||||
'arguments' => [
|
||||
[NULL, 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
$media_type->id() => [
|
||||
$media_entities['published']->id() => $media_labels['published'],
|
||||
$media_entities['unpublished']->id() => $media_labels['unpublished'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'arguments' => [
|
||||
['Media unpublished', 'CONTAINS'],
|
||||
],
|
||||
'result' => [
|
||||
$media_type->id() => [
|
||||
$media_entities['unpublished']->id() => $media_labels['unpublished'],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertReferenceable($selection_options, $referenceable_tests, 'Media handler (admin)');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace Drupal\Tests\system\Functional\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestMulRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
|
|
@ -60,20 +62,42 @@ class EntityRevisionsTest extends BrowserTestBase {
|
|||
* The entity type to run the tests with.
|
||||
*/
|
||||
protected function runRevisionsTests($entity_type) {
|
||||
// Create a translatable test field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => 'translatable_test_field',
|
||||
'type' => 'text',
|
||||
'cardinality' => 2,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'label' => $this->randomMachineName(),
|
||||
'bundle' => $entity_type,
|
||||
'translatable' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
entity_get_form_display($entity_type, $entity_type, 'default')
|
||||
->setComponent('translatable_test_field')
|
||||
->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity_type);
|
||||
|
||||
// Create initial entity.
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $storage
|
||||
->create([
|
||||
'name' => 'foo',
|
||||
'user_id' => $this->webUser->id(),
|
||||
]);
|
||||
$entity->field_test_text->value = 'bar';
|
||||
$entity->translatable_test_field->value = 'bar';
|
||||
$entity->addTranslation('de', ['name' => 'foo - de']);
|
||||
$entity->save();
|
||||
|
||||
$names = [];
|
||||
$texts = [];
|
||||
$created = [];
|
||||
$values = [];
|
||||
$revision_ids = [];
|
||||
|
||||
// Create three revisions.
|
||||
|
|
@ -81,45 +105,74 @@ class EntityRevisionsTest extends BrowserTestBase {
|
|||
for ($i = 0; $i < $revision_count; $i++) {
|
||||
$legacy_revision_id = $entity->revision_id->value;
|
||||
$legacy_name = $entity->name->value;
|
||||
$legacy_text = $entity->field_test_text->value;
|
||||
$legacy_text = $entity->translatable_test_field->value;
|
||||
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)->load($entity->id->value);
|
||||
$entity = $storage->load($entity->id->value);
|
||||
$entity->setNewRevision(TRUE);
|
||||
$names[] = $entity->name->value = $this->randomMachineName(32);
|
||||
$texts[] = $entity->field_test_text->value = $this->randomMachineName(32);
|
||||
$created[] = $entity->created->value = time() + $i + 1;
|
||||
$values['en'][$i] = [
|
||||
'name' => $this->randomMachineName(32),
|
||||
'translatable_test_field' => [
|
||||
$this->randomMachineName(32),
|
||||
$this->randomMachineName(32),
|
||||
],
|
||||
'created' => time() + $i + 1,
|
||||
];
|
||||
$entity->set('name', $values['en'][$i]['name']);
|
||||
$entity->set('translatable_test_field', $values['en'][$i]['translatable_test_field']);
|
||||
$entity->set('created', $values['en'][$i]['created']);
|
||||
$entity->save();
|
||||
$revision_ids[] = $entity->revision_id->value;
|
||||
|
||||
// Add some values for a translation of this revision.
|
||||
if ($entity->getEntityType()->isTranslatable()) {
|
||||
$values['de'][$i] = [
|
||||
'name' => $this->randomMachineName(32),
|
||||
'translatable_test_field' => [
|
||||
$this->randomMachineName(32),
|
||||
$this->randomMachineName(32),
|
||||
],
|
||||
];
|
||||
$translation = $entity->getTranslation('de');
|
||||
$translation->set('name', $values['de'][$i]['name']);
|
||||
$translation->set('translatable_test_field', $values['de'][$i]['translatable_test_field']);
|
||||
$translation->save();
|
||||
}
|
||||
|
||||
// Check that the fields and properties contain new content.
|
||||
$this->assertTrue($entity->revision_id->value > $legacy_revision_id, format_string('%entity_type: Revision ID changed.', ['%entity_type' => $entity_type]));
|
||||
$this->assertNotEqual($entity->name->value, $legacy_name, format_string('%entity_type: Name changed.', ['%entity_type' => $entity_type]));
|
||||
$this->assertNotEqual($entity->field_test_text->value, $legacy_text, format_string('%entity_type: Text changed.', ['%entity_type' => $entity_type]));
|
||||
$this->assertNotEqual($entity->translatable_test_field->value, $legacy_text, format_string('%entity_type: Text changed.', ['%entity_type' => $entity_type]));
|
||||
}
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
|
||||
$revisions = $storage->loadMultipleRevisions($revision_ids);
|
||||
for ($i = 0; $i < $revision_count; $i++) {
|
||||
// Load specific revision.
|
||||
$entity_revision = $storage->loadRevision($revision_ids[$i]);
|
||||
$entity_revision = $revisions[$revision_ids[$i]];
|
||||
|
||||
// Check if properties and fields contain the revision specific content.
|
||||
$this->assertEqual($entity_revision->revision_id->value, $revision_ids[$i], format_string('%entity_type: Revision ID matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->name->value, $names[$i], format_string('%entity_type: Name matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->field_test_text->value, $texts[$i], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->name->value, $values['en'][$i]['name'], format_string('%entity_type: Name matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->translatable_test_field[0]->value, $values['en'][$i]['translatable_test_field'][0], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->translatable_test_field[1]->value, $values['en'][$i]['translatable_test_field'][1], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type]));
|
||||
|
||||
// Check the translated values.
|
||||
if ($entity->getEntityType()->isTranslatable()) {
|
||||
$revision_translation = $entity_revision->getTranslation('de');
|
||||
$this->assertEqual($revision_translation->name->value, $values['de'][$i]['name'], format_string('%entity_type: Name matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($revision_translation->translatable_test_field[0]->value, $values['de'][$i]['translatable_test_field'][0], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($revision_translation->translatable_test_field[1]->value, $values['de'][$i]['translatable_test_field'][1], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type]));
|
||||
}
|
||||
|
||||
// Check non-revisioned values are loaded.
|
||||
$this->assertTrue(isset($entity_revision->created->value), format_string('%entity_type: Non-revisioned field is loaded.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->created->value, $created[2], format_string('%entity_type: Non-revisioned field value is the same between revisions.', ['%entity_type' => $entity_type]));
|
||||
$this->assertEqual($entity_revision->created->value, $values['en'][2]['created'], format_string('%entity_type: Non-revisioned field value is the same between revisions.', ['%entity_type' => $entity_type]));
|
||||
}
|
||||
|
||||
// Confirm the correct revision text appears in the edit form.
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->load($entity->id->value);
|
||||
$entity = $storage->load($entity->id->value);
|
||||
$this->drupalGet($entity_type . '/manage/' . $entity->id->value . '/edit');
|
||||
$this->assertFieldById('edit-name-0-value', $entity->name->value, format_string('%entity_type: Name matches in UI.', ['%entity_type' => $entity_type]));
|
||||
$this->assertFieldById('edit-field-test-text-0-value', $entity->field_test_text->value, format_string('%entity_type: Text matches in UI.', ['%entity_type' => $entity_type]));
|
||||
$this->assertFieldById('edit-translatable-test-field-0-value', $entity->translatable_test_field->value, format_string('%entity_type: Text matches in UI.', ['%entity_type' => $entity_type]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -135,28 +188,84 @@ class EntityRevisionsTest extends BrowserTestBase {
|
|||
$entity->addTranslation('de', ['name' => 'default revision - de']);
|
||||
$entity->save();
|
||||
|
||||
$forward_revision = \Drupal::entityTypeManager()->getStorage('entity_test_mulrev')->loadUnchanged($entity->id());
|
||||
$pending_revision = \Drupal::entityTypeManager()->getStorage('entity_test_mulrev')->loadUnchanged($entity->id());
|
||||
|
||||
$forward_revision->setNewRevision();
|
||||
$forward_revision->isDefaultRevision(FALSE);
|
||||
$pending_revision->setNewRevision();
|
||||
$pending_revision->isDefaultRevision(FALSE);
|
||||
|
||||
$forward_revision->name = 'forward revision - en';
|
||||
$forward_revision->save();
|
||||
$pending_revision->name = 'pending revision - en';
|
||||
$pending_revision->save();
|
||||
|
||||
$forward_revision_translation = $forward_revision->getTranslation('de');
|
||||
$forward_revision_translation->name = 'forward revision - de';
|
||||
$forward_revision_translation->save();
|
||||
$pending_revision_translation = $pending_revision->getTranslation('de');
|
||||
$pending_revision_translation->name = 'pending revision - de';
|
||||
$pending_revision_translation->save();
|
||||
|
||||
// Check that the entity revision is upcasted in the correct language.
|
||||
$revision_url = 'entity_test_mulrev/' . $entity->id() . '/revision/' . $forward_revision->getRevisionId() . '/view';
|
||||
$revision_url = 'entity_test_mulrev/' . $entity->id() . '/revision/' . $pending_revision->getRevisionId() . '/view';
|
||||
|
||||
$this->drupalGet($revision_url);
|
||||
$this->assertText('forward revision - en');
|
||||
$this->assertNoText('forward revision - de');
|
||||
$this->assertText('pending revision - en');
|
||||
$this->assertNoText('pending revision - de');
|
||||
|
||||
$this->drupalGet('de/' . $revision_url);
|
||||
$this->assertText('forward revision - de');
|
||||
$this->assertNoText('forward revision - en');
|
||||
$this->assertText('pending revision - de');
|
||||
$this->assertNoText('pending revision - en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests manual revert of the revision ID value.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\ContentEntityBase::getRevisionId
|
||||
* @covers \Drupal\Core\Entity\ContentEntityBase::getLoadedRevisionId
|
||||
* @covers \Drupal\Core\Entity\ContentEntityBase::setNewRevision
|
||||
* @covers \Drupal\Core\Entity\ContentEntityBase::isNewRevision
|
||||
*/
|
||||
public function testNewRevisionRevert() {
|
||||
$entity = EntityTestMulRev::create(['name' => 'EntityLoadedRevisionTest']);
|
||||
$entity->save();
|
||||
|
||||
// Check that revision ID field is reset while the loaded revision ID is
|
||||
// preserved when flagging a new revision.
|
||||
$revision_id = $entity->getRevisionId();
|
||||
$entity->setNewRevision();
|
||||
$this->assertNull($entity->getRevisionId());
|
||||
$this->assertEquals($revision_id, $entity->getLoadedRevisionId());
|
||||
$this->assertTrue($entity->isNewRevision());
|
||||
|
||||
// Check that after manually restoring the original revision ID, the entity
|
||||
// is stored without creating a new revision.
|
||||
$key = $entity->getEntityType()->getKey('revision');
|
||||
$entity->set($key, $revision_id);
|
||||
$entity->save();
|
||||
$this->assertEquals($revision_id, $entity->getRevisionId());
|
||||
$this->assertEquals($revision_id, $entity->getLoadedRevisionId());
|
||||
|
||||
// Check that manually restoring the original revision ID causes the "new
|
||||
// revision" state to be reverted.
|
||||
$entity->setNewRevision();
|
||||
$this->assertNull($entity->getRevisionId());
|
||||
$this->assertEquals($revision_id, $entity->getLoadedRevisionId());
|
||||
$this->assertTrue($entity->isNewRevision());
|
||||
$entity->set($key, $revision_id);
|
||||
$this->assertFalse($entity->isNewRevision());
|
||||
$this->assertEquals($revision_id, $entity->getRevisionId());
|
||||
$this->assertEquals($revision_id, $entity->getLoadedRevisionId());
|
||||
|
||||
// Check that flagging a new revision again works correctly.
|
||||
$entity->setNewRevision();
|
||||
$this->assertNull($entity->getRevisionId());
|
||||
$this->assertEquals($revision_id, $entity->getLoadedRevisionId());
|
||||
$this->assertTrue($entity->isNewRevision());
|
||||
|
||||
// Check that calling setNewRevision() on a new entity without a revision ID
|
||||
// and then with a revision ID does not unset the revision ID.
|
||||
$entity = EntityTestMulRev::create(['name' => 'EntityLoadedRevisionTest']);
|
||||
$entity->set('revision_id', NULL);
|
||||
$entity->set('revision_id', 5);
|
||||
$this->assertTrue($entity->isNewRevision());
|
||||
$entity->setNewRevision();
|
||||
$this->assertEquals(5, $entity->get('revision_id')->value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests entity translation form.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityTranslationFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['entity_test', 'language', 'node'];
|
||||
|
||||
protected $langcodes;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Enable translations for the test entity type.
|
||||
\Drupal::state()->set('entity_test.translation', TRUE);
|
||||
|
||||
// Create test languages.
|
||||
$this->langcodes = [];
|
||||
for ($i = 0; $i < 2; ++$i) {
|
||||
$language = ConfigurableLanguage::create([
|
||||
'id' => 'l' . $i,
|
||||
'label' => $this->randomString(),
|
||||
]);
|
||||
$this->langcodes[$i] = $language->id();
|
||||
$language->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity form language.
|
||||
*/
|
||||
public function testEntityFormLanguage() {
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
|
||||
$web_user = $this->drupalCreateUser(['create page content', 'edit own page content', 'administer content types']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create a node with language LanguageInterface::LANGCODE_NOT_SPECIFIED.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName(8);
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->drupalGet('node/add/page');
|
||||
$form_langcode = \Drupal::state()->get('entity_test.form_langcode');
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
|
||||
$this->assertTrue($node->language()->getId() == $form_langcode, 'Form language is the same as the entity language.');
|
||||
|
||||
// Edit the node and test the form language.
|
||||
$this->drupalGet($this->langcodes[0] . '/node/' . $node->id() . '/edit');
|
||||
$form_langcode = \Drupal::state()->get('entity_test.form_langcode');
|
||||
$this->assertTrue($node->language()->getId() == $form_langcode, 'Form language is the same as the entity language.');
|
||||
|
||||
// Explicitly set form langcode.
|
||||
$langcode = $this->langcodes[0];
|
||||
$form_state_additions['langcode'] = $langcode;
|
||||
\Drupal::service('entity.form_builder')->getForm($node, 'default', $form_state_additions);
|
||||
$form_langcode = \Drupal::state()->get('entity_test.form_langcode');
|
||||
$this->assertTrue($langcode == $form_langcode, 'Form language is the same as the language parameter.');
|
||||
|
||||
// Enable language selector.
|
||||
$this->drupalGet('admin/structure/types/manage/page');
|
||||
$edit = ['language_configuration[language_alterable]' => TRUE, 'language_configuration[langcode]' => LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', ['%type' => 'Basic page']), 'Basic page content type has been updated.');
|
||||
|
||||
// Create a node with language.
|
||||
$edit = [];
|
||||
$langcode = $this->langcodes[0];
|
||||
$edit['title[0][value]'] = $this->randomMachineName(8);
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$edit['langcode[0][value]'] = $langcode;
|
||||
$this->drupalPostForm('node/add/page', $edit, t('Save'));
|
||||
$this->assertText(t('Basic page @title has been created.', ['@title' => $edit['title[0][value]']]), 'Basic page created.');
|
||||
|
||||
// Verify that the creation message contains a link to a node.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'node/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a node');
|
||||
|
||||
// Check to make sure the node was created.
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$this->assertTrue($node, 'Node found in database.');
|
||||
|
||||
// Make body translatable.
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'body');
|
||||
$field_storage->setTranslatable(TRUE);
|
||||
$field_storage->save();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'body');
|
||||
$this->assertTrue($field_storage->isTranslatable(), 'Field body is translatable.');
|
||||
|
||||
// Create a body translation and check the form language.
|
||||
$langcode2 = $this->langcodes[1];
|
||||
$translation = $node->addTranslation($langcode2);
|
||||
$translation->title->value = $this->randomString();
|
||||
$translation->body->value = $this->randomMachineName(16);
|
||||
$translation->setOwnerId($web_user->id());
|
||||
$node->save();
|
||||
$this->drupalGet($langcode2 . '/node/' . $node->id() . '/edit');
|
||||
$form_langcode = \Drupal::state()->get('entity_test.form_langcode');
|
||||
$this->assertTrue($langcode2 == $form_langcode, "Node edit form language is $langcode2.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ class EntityViewControllerTest extends BrowserTestBase {
|
|||
* Tests EntityViewController.
|
||||
*/
|
||||
public function testEntityViewController() {
|
||||
$get_label_markup = function($label) {
|
||||
$get_label_markup = function ($label) {
|
||||
return '<h1 class="page-title">
|
||||
<div class="field field--name-name field--type-string field--label-hidden field__item">' . $label . '</div>
|
||||
</h1>';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
/**
|
||||
* Provides helper methods for Entity cache tags tests; for entities with URIs.
|
||||
*/
|
||||
abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* Tests cache tags presence and invalidation of the entity at its URI.
|
||||
*
|
||||
* Tests the following cache tags:
|
||||
* - "<entity type>_view"
|
||||
* - "<entity_type>:<entity ID>"
|
||||
*/
|
||||
public function testEntityUri() {
|
||||
$entity_url = $this->entity->urlInfo();
|
||||
$entity_type = $this->entity->getEntityTypeId();
|
||||
|
||||
// Selects the view mode that will be used.
|
||||
$view_mode = $this->selectViewMode($entity_type);
|
||||
|
||||
// The default cache contexts for rendered entities.
|
||||
$entity_cache_contexts = $this->getDefaultCacheContexts();
|
||||
|
||||
// Generate the standardized entity cache tags.
|
||||
$cache_tag = $this->entity->getCacheTags();
|
||||
$view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)->getCacheTags();
|
||||
$render_cache_tag = 'rendered';
|
||||
|
||||
$this->pass("Test entity.", 'Debug');
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
// Also verify the existence of an entity render cache entry, if this entity
|
||||
// type supports render caching.
|
||||
if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) {
|
||||
$cache_keys = ['entity_view', $entity_type, $this->entity->id(), $view_mode];
|
||||
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
|
||||
$redirected_cid = NULL;
|
||||
$additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->entity);
|
||||
if (count($additional_cache_contexts)) {
|
||||
$redirected_cid = $this->createCacheId($cache_keys, Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts));
|
||||
}
|
||||
$expected_cache_tags = Cache::mergeTags($cache_tag, $view_cache_tag);
|
||||
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
|
||||
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, [$render_cache_tag]);
|
||||
$this->verifyRenderCache($cid, $expected_cache_tags, $redirected_cid);
|
||||
}
|
||||
|
||||
// Verify that after modifying the entity, there is a cache miss.
|
||||
$this->pass("Test modification of entity.", 'Debug');
|
||||
$this->entity->save();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
// Verify that after modifying the entity's display, there is a cache miss.
|
||||
$this->pass("Test modification of entity's '$view_mode' display.", 'Debug');
|
||||
$entity_display = entity_get_display($entity_type, $this->entity->bundle(), $view_mode);
|
||||
$entity_display->save();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
|
||||
// Verify that after modifying the corresponding bundle entity, there is a
|
||||
// cache miss.
|
||||
$this->pass("Test modification of entity's bundle entity.", 'Debug');
|
||||
$bundle_entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($bundle_entity_type_id)
|
||||
->load($this->entity->bundle());
|
||||
$bundle_entity->save();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
}
|
||||
|
||||
if ($this->entity->getEntityType()->get('field_ui_base_route')) {
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
$this->pass("Test modification of entity's configurable field.", 'Debug');
|
||||
$field_storage_name = $this->entity->getEntityTypeId() . '.configurable_field';
|
||||
$field_storage = FieldStorageConfig::load($field_storage_name);
|
||||
$field_storage->save();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
$this->pass("Test modification of entity's configurable field.", 'Debug');
|
||||
$field_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
|
||||
$field = FieldConfig::load($field_name);
|
||||
$field->save();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
}
|
||||
|
||||
// Verify that after invalidating the entity's cache tag directly, there is
|
||||
// a cache miss.
|
||||
$this->pass("Test invalidation of entity's cache tag.", 'Debug');
|
||||
Cache::invalidateTags($this->entity->getCacheTagsToInvalidate());
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
// Verify that after invalidating the generic entity type's view cache tag
|
||||
// directly, there is a cache miss.
|
||||
$this->pass("Test invalidation of entity's 'view' cache tag.", 'Debug');
|
||||
Cache::invalidateTags($view_cache_tag);
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($entity_url, 'HIT');
|
||||
|
||||
// Verify that after deleting the entity, there is a cache miss.
|
||||
$this->pass('Test deletion of entity.', 'Debug');
|
||||
$this->entity->delete();
|
||||
$this->verifyPageCache($entity_url, 'MISS');
|
||||
$this->assertResponse(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default cache contexts for rendered entities.
|
||||
*
|
||||
* @return array
|
||||
* The default cache contexts for rendered entities.
|
||||
*/
|
||||
protected function getDefaultCacheContexts() {
|
||||
return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Traits;
|
||||
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\entity_test\FieldStorageDefinition;
|
||||
|
||||
/**
|
||||
* Provides some test methods used to update existing entity definitions.
|
||||
*/
|
||||
trait EntityDefinitionTestTrait {
|
||||
|
||||
/**
|
||||
* Enables a new entity type definition.
|
||||
*/
|
||||
protected function enableNewEntityType() {
|
||||
$this->state->set('entity_test_new', TRUE);
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityDefinitionUpdateManager->applyUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the entity type definition.
|
||||
*/
|
||||
protected function resetEntityType() {
|
||||
$this->state->set('entity_test_update.entity_type', NULL);
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityDefinitionUpdateManager->applyUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'entity_test_update' entity type to revisionable.
|
||||
*/
|
||||
protected function updateEntityTypeToRevisionable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$keys = $entity_type->getKeys();
|
||||
$keys['revision'] = 'revision_id';
|
||||
$entity_type->set('entity_keys', $keys);
|
||||
$entity_type->set('revision_table', 'entity_test_update_revision');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'entity_test_update' entity type not revisionable.
|
||||
*/
|
||||
protected function updateEntityTypeToNotRevisionable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$keys = $entity_type->getKeys();
|
||||
unset($keys['revision']);
|
||||
$entity_type->set('entity_keys', $keys);
|
||||
$entity_type->set('revision_table', NULL);
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'entity_test_update' entity type to translatable.
|
||||
*/
|
||||
protected function updateEntityTypeToTranslatable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('translatable', TRUE);
|
||||
$entity_type->set('data_table', 'entity_test_update_data');
|
||||
|
||||
if ($entity_type->isRevisionable()) {
|
||||
$entity_type->set('revision_data_table', 'entity_test_update_revision_data');
|
||||
}
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'entity_test_update' entity type to not translatable.
|
||||
*/
|
||||
protected function updateEntityTypeToNotTranslatable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('translatable', FALSE);
|
||||
$entity_type->set('data_table', NULL);
|
||||
|
||||
if ($entity_type->isRevisionable()) {
|
||||
$entity_type->set('revision_data_table', NULL);
|
||||
}
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 'entity_test_update' entity type to revisionable and
|
||||
* translatable.
|
||||
*/
|
||||
protected function updateEntityTypeToRevisionableAndTranslatable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$keys = $entity_type->getKeys();
|
||||
$keys['revision'] = 'revision_id';
|
||||
$entity_type->set('entity_keys', $keys);
|
||||
$entity_type->set('translatable', TRUE);
|
||||
$entity_type->set('data_table', 'entity_test_update_data');
|
||||
$entity_type->set('revision_table', 'entity_test_update_revision');
|
||||
$entity_type->set('revision_data_table', 'entity_test_update_revision_data');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new base field to the 'entity_test_update' entity type.
|
||||
*
|
||||
* @param string $type
|
||||
* (optional) The field type for the new field. Defaults to 'string'.
|
||||
* @param string $entity_type_id
|
||||
* (optional) The entity type ID the base field should be attached to.
|
||||
* Defaults to 'entity_test_update'.
|
||||
* @param bool $is_revisionable
|
||||
* (optional) If the base field should be revisionable or not. Defaults to
|
||||
* FALSE.
|
||||
*/
|
||||
protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE) {
|
||||
$definitions['new_base_field'] = BaseFieldDefinition::create($type)
|
||||
->setName('new_base_field')
|
||||
->setRevisionable($is_revisionable)
|
||||
->setLabel(t('A new base field'));
|
||||
$this->state->set($entity_type_id . '.additional_base_field_definitions', $definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a long-named base field to the 'entity_test_update' entity type.
|
||||
*/
|
||||
protected function addLongNameBaseField() {
|
||||
$key = 'entity_test_update.additional_base_field_definitions';
|
||||
$definitions = $this->state->get($key, []);
|
||||
$definitions['new_long_named_entity_reference_base_field'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setName('new_long_named_entity_reference_base_field')
|
||||
->setLabel(t('A new long-named base field'))
|
||||
->setSetting('target_type', 'user')
|
||||
->setSetting('handler', 'default');
|
||||
$this->state->set($key, $definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new revisionable base field to the 'entity_test_update' entity type.
|
||||
*
|
||||
* @param string $type
|
||||
* (optional) The field type for the new field. Defaults to 'string'.
|
||||
*/
|
||||
protected function addRevisionableBaseField($type = 'string') {
|
||||
$definitions['new_base_field'] = BaseFieldDefinition::create($type)
|
||||
->setName('new_base_field')
|
||||
->setLabel(t('A new revisionable base field'))
|
||||
->setRevisionable(TRUE);
|
||||
$this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the new base field from 'string' to 'text'.
|
||||
*/
|
||||
protected function modifyBaseField() {
|
||||
$this->addBaseField('text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Promotes a field to an entity key.
|
||||
*/
|
||||
protected function makeBaseFieldEntityKey() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
$entity_keys = $entity_type->getKeys();
|
||||
$entity_keys['new_base_field'] = 'new_base_field';
|
||||
$entity_type->set('entity_keys', $entity_keys);
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the new base field from the 'entity_test_update' entity type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* (optional) The entity type ID the base field should be attached to.
|
||||
*/
|
||||
protected function removeBaseField($entity_type_id = 'entity_test_update') {
|
||||
$this->state->delete($entity_type_id . '.additional_base_field_definitions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single-field index to the base field.
|
||||
*/
|
||||
protected function addBaseFieldIndex() {
|
||||
$this->state->set('entity_test_update.additional_field_index.entity_test_update.new_base_field', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the index added in addBaseFieldIndex().
|
||||
*/
|
||||
protected function removeBaseFieldIndex() {
|
||||
$this->state->delete('entity_test_update.additional_field_index.entity_test_update.new_base_field');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new bundle field to the 'entity_test_update' entity type.
|
||||
*
|
||||
* @param string $type
|
||||
* (optional) The field type for the new field. Defaults to 'string'.
|
||||
*/
|
||||
protected function addBundleField($type = 'string') {
|
||||
$definitions['new_bundle_field'] = FieldStorageDefinition::create($type)
|
||||
->setName('new_bundle_field')
|
||||
->setLabel(t('A new bundle field'))
|
||||
->setTargetEntityTypeId('entity_test_update');
|
||||
$this->state->set('entity_test_update.additional_field_storage_definitions', $definitions);
|
||||
$this->state->set('entity_test_update.additional_bundle_field_definitions.test_bundle', $definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the new bundle field from 'string' to 'text'.
|
||||
*/
|
||||
protected function modifyBundleField() {
|
||||
$this->addBundleField('text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the new bundle field from the 'entity_test_update' entity type.
|
||||
*/
|
||||
protected function removeBundleField() {
|
||||
$this->state->delete('entity_test_update.additional_field_storage_definitions');
|
||||
$this->state->delete('entity_test_update.additional_bundle_field_definitions.test_bundle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an index to the 'entity_test_update' entity type's base table.
|
||||
*
|
||||
* @see \Drupal\entity_test\EntityTestStorageSchema::getEntitySchema()
|
||||
*/
|
||||
protected function addEntityIndex() {
|
||||
$indexes = [
|
||||
'entity_test_update__new_index' => ['name', 'test_single_property'],
|
||||
];
|
||||
$this->state->set('entity_test_update.additional_entity_indexes', $indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the index added in addEntityIndex().
|
||||
*/
|
||||
protected function removeEntityIndex() {
|
||||
$this->state->delete('entity_test_update.additional_entity_indexes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the base table to 'entity_test_update_new'.
|
||||
*/
|
||||
protected function renameBaseTable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('base_table', 'entity_test_update_new');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the data table to 'entity_test_update_data_new'.
|
||||
*/
|
||||
protected function renameDataTable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('data_table', 'entity_test_update_data_new');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the revision table to 'entity_test_update_revision_new'.
|
||||
*/
|
||||
protected function renameRevisionBaseTable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('revision_table', 'entity_test_update_revision_new');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the revision data table to 'entity_test_update_revision_data_new'.
|
||||
*/
|
||||
protected function renameRevisionDataTable() {
|
||||
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
|
||||
|
||||
$entity_type->set('revision_data_table', 'entity_test_update_revision_data_new');
|
||||
|
||||
$this->state->set('entity_test_update.entity_type', $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entity type.
|
||||
*/
|
||||
protected function deleteEntityType() {
|
||||
$this->state->set('entity_test_update.entity_type', 'null');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
/**
|
||||
* Runs LangcodeToAsciiUpdateTest with a dump filled with content.
|
||||
*
|
||||
* @group Entity
|
||||
* @group legacy
|
||||
*/
|
||||
class LangcodeToAsciiUpdateFilledTest extends LangcodeToAsciiUpdateTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.filled.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the entity langcode fields have been updated to varchar_ascii.
|
||||
*
|
||||
* @group Entity
|
||||
* @group legacy
|
||||
*/
|
||||
class LangcodeToAsciiUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the column collation has been updated on MySQL.
|
||||
*/
|
||||
public function testLangcodeColumnCollation() {
|
||||
// Only testable on MySQL.
|
||||
// @see https://www.drupal.org/node/301038
|
||||
if (Database::getConnection()->databaseType() !== 'mysql') {
|
||||
$this->pass('This test can only run on MySQL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check a few different tables.
|
||||
$tables = [
|
||||
'node_field_data' => ['langcode'],
|
||||
'users_field_data' => ['langcode', 'preferred_langcode', 'preferred_admin_langcode'],
|
||||
];
|
||||
foreach ($tables as $table => $columns) {
|
||||
foreach ($columns as $column) {
|
||||
// Depending on MYSQL versions you get different collations.
|
||||
$this->assertContains($this->getColumnCollation($table, $column), ['utf8mb4_0900_ai_ci', 'utf8mb4_general_ci'], 'Found correct starting collation for ' . $table . '.' . $column);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply updates.
|
||||
$this->runUpdates();
|
||||
|
||||
foreach ($tables as $table => $columns) {
|
||||
foreach ($columns as $column) {
|
||||
$this->assertEqual('ascii_general_ci', $this->getColumnCollation($table, $column), 'Found correct updated collation for ' . $table . '.' . $column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the column collation.
|
||||
*
|
||||
* @param string $table
|
||||
* The table name.
|
||||
* @param string $column
|
||||
* The column name.
|
||||
*/
|
||||
protected function getColumnCollation($table, $column) {
|
||||
$query = Database::getConnection()->query("SHOW FULL COLUMNS FROM {" . $table . "}");
|
||||
while ($row = $query->fetchAssoc()) {
|
||||
if ($row['Field'] === $column) {
|
||||
return $row['Collation'];
|
||||
}
|
||||
}
|
||||
$this->fail('No collation found for ' . $table . '.' . $column);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityType;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for moving the revision metadata fields.
|
||||
*
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class MoveRevisionMetadataFieldsUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../tests/fixtures/update/drupal-8.2.0.bare.standard_with_entity_test_revlog_enabled.php.gz',
|
||||
__DIR__ . '/../../../../../tests/fixtures/update/drupal-8.entity-data-revision-metadata-fields-2248983.php',
|
||||
__DIR__ . '/../../../../../tests/fixtures/update/drupal-8.views-revision-metadata-fields-2248983.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the revision metadata fields are moved correctly.
|
||||
*/
|
||||
public function testSystemUpdate8400() {
|
||||
$this->runUpdates();
|
||||
|
||||
foreach (['entity_test_revlog', 'entity_test_mul_revlog'] as $entity_type_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
|
||||
$entity_type = $storage->getEntityType();
|
||||
$revision_metadata_field_names = $entity_type->getRevisionMetadataKeys();
|
||||
|
||||
$database_schema = \Drupal::database()->schema();
|
||||
|
||||
// Test that the revision metadata fields are present only in the
|
||||
// revision table.
|
||||
foreach ($revision_metadata_field_names as $revision_metadata_field_name) {
|
||||
if ($entity_type->isTranslatable()) {
|
||||
$this->assertFalse($database_schema->fieldExists($entity_type->getDataTable(), $revision_metadata_field_name));
|
||||
$this->assertFalse($database_schema->fieldExists($entity_type->getRevisionDataTable(), $revision_metadata_field_name));
|
||||
}
|
||||
else {
|
||||
$this->assertFalse($database_schema->fieldExists($entity_type->getBaseTable(), $revision_metadata_field_name));
|
||||
}
|
||||
$this->assertTrue($database_schema->fieldExists($entity_type->getRevisionTable(), $revision_metadata_field_name));
|
||||
}
|
||||
|
||||
// Test that the revision metadata values have been transferred correctly
|
||||
// and that the moved fields are accessible.
|
||||
/** @var \Drupal\Core\Entity\RevisionLogInterface $entity_rev_first */
|
||||
$entity_rev_first = $storage->loadRevision(1);
|
||||
$this->assertEqual($entity_rev_first->getRevisionUserId(), '1');
|
||||
$this->assertEqual($entity_rev_first->getRevisionLogMessage(), 'first revision');
|
||||
$this->assertEqual($entity_rev_first->getRevisionCreationTime(), '1476268517');
|
||||
|
||||
/** @var \Drupal\Core\Entity\RevisionLogInterface $entity_rev_second */
|
||||
$entity_rev_second = $storage->loadRevision(2);
|
||||
$this->assertEqual($entity_rev_second->getRevisionUserId(), '1');
|
||||
$this->assertEqual($entity_rev_second->getRevisionLogMessage(), 'second revision');
|
||||
$this->assertEqual($entity_rev_second->getRevisionCreationTime(), '1476268518');
|
||||
|
||||
// Test that the views using revision metadata fields are updated
|
||||
// properly.
|
||||
$view = View::load($entity_type_id . '_for_2248983');
|
||||
$displays = $view->get('display');
|
||||
foreach ($displays as $display => $display_data) {
|
||||
foreach ($display_data['display_options']['fields'] as $property_data) {
|
||||
if (in_array($property_data['field'], $revision_metadata_field_names)) {
|
||||
$this->assertEqual($property_data['table'], $entity_type->getRevisionTable());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the addition of required revision metadata keys.
|
||||
*
|
||||
* This test ensures that already cached entity instances will only return the
|
||||
* required revision metadata keys they have been cached with and only new
|
||||
* instances will return all the new required revision metadata keys.
|
||||
*/
|
||||
public function testAddingRequiredRevisionMetadataKeys() {
|
||||
// Ensure that cached entity types without required revision metadata keys
|
||||
// will not return any of the newly added required revision metadata keys.
|
||||
// Contains no revision metadata keys and the property holding the required
|
||||
// metadata keys is empty, the entity type id is "entity_test_mul_revlog".
|
||||
$cached_with_no_metadata_keys = 'Tzo4MjoiRHJ1cGFsXFRlc3RzXHN5c3RlbVxGdW5jdGlvbmFsXEVudGl0eVxVcGRhdGVcVGVzdFJldmlzaW9uTWV0YWRhdGFCY0xheWVyRW50aXR5VHlwZSI6Mzk6e3M6MjU6IgAqAHJldmlzaW9uX21ldGFkYXRhX2tleXMiO2E6MDp7fXM6MzE6IgAqAHJlcXVpcmVkUmV2aXNpb25NZXRhZGF0YUtleXMiO2E6MDp7fXM6MTU6IgAqAHN0YXRpY19jYWNoZSI7YjoxO3M6MTU6IgAqAHJlbmRlcl9jYWNoZSI7YjoxO3M6MTk6IgAqAHBlcnNpc3RlbnRfY2FjaGUiO2I6MTtzOjE0OiIAKgBlbnRpdHlfa2V5cyI7YTo1OntzOjg6InJldmlzaW9uIjtzOjA6IiI7czo2OiJidW5kbGUiO3M6MDoiIjtzOjg6Imxhbmdjb2RlIjtzOjA6IiI7czoxNjoiZGVmYXVsdF9sYW5nY29kZSI7czoxNjoiZGVmYXVsdF9sYW5nY29kZSI7czoyOToicmV2aXNpb25fdHJhbnNsYXRpb25fYWZmZWN0ZWQiO3M6Mjk6InJldmlzaW9uX3RyYW5zbGF0aW9uX2FmZmVjdGVkIjt9czo1OiIAKgBpZCI7czoyMjoiZW50aXR5X3Rlc3RfbXVsX3JldmxvZyI7czoxNjoiACoAb3JpZ2luYWxDbGFzcyI7TjtzOjExOiIAKgBoYW5kbGVycyI7YTozOntzOjY6ImFjY2VzcyI7czo0NToiRHJ1cGFsXENvcmVcRW50aXR5XEVudGl0eUFjY2Vzc0NvbnRyb2xIYW5kbGVyIjtzOjc6InN0b3JhZ2UiO3M6NDY6IkRydXBhbFxDb3JlXEVudGl0eVxTcWxcU3FsQ29udGVudEVudGl0eVN0b3JhZ2UiO3M6MTI6InZpZXdfYnVpbGRlciI7czozNjoiRHJ1cGFsXENvcmVcRW50aXR5XEVudGl0eVZpZXdCdWlsZGVyIjt9czoxOToiACoAYWRtaW5fcGVybWlzc2lvbiI7TjtzOjI1OiIAKgBwZXJtaXNzaW9uX2dyYW51bGFyaXR5IjtzOjExOiJlbnRpdHlfdHlwZSI7czo4OiIAKgBsaW5rcyI7YTowOnt9czoxNzoiACoAbGFiZWxfY2FsbGJhY2siO047czoyMToiACoAYnVuZGxlX2VudGl0eV90eXBlIjtOO3M6MTI6IgAqAGJ1bmRsZV9vZiI7TjtzOjE1OiIAKgBidW5kbGVfbGFiZWwiO047czoxMzoiACoAYmFzZV90YWJsZSI7TjtzOjIyOiIAKgByZXZpc2lvbl9kYXRhX3RhYmxlIjtOO3M6MTc6IgAqAHJldmlzaW9uX3RhYmxlIjtOO3M6MTM6IgAqAGRhdGFfdGFibGUiO047czoxNToiACoAdHJhbnNsYXRhYmxlIjtiOjA7czoxOToiACoAc2hvd19yZXZpc2lvbl91aSI7YjowO3M6ODoiACoAbGFiZWwiO3M6MDoiIjtzOjE5OiIAKgBsYWJlbF9jb2xsZWN0aW9uIjtzOjA6IiI7czoxNzoiACoAbGFiZWxfc2luZ3VsYXIiO3M6MDoiIjtzOjE1OiIAKgBsYWJlbF9wbHVyYWwiO3M6MDoiIjtzOjE0OiIAKgBsYWJlbF9jb3VudCI7YTowOnt9czoxNToiACoAdXJpX2NhbGxiYWNrIjtOO3M6ODoiACoAZ3JvdXAiO047czoxNDoiACoAZ3JvdXBfbGFiZWwiO047czoyMjoiACoAZmllbGRfdWlfYmFzZV9yb3V0ZSI7TjtzOjI2OiIAKgBjb21tb25fcmVmZXJlbmNlX3RhcmdldCI7YjowO3M6MjI6IgAqAGxpc3RfY2FjaGVfY29udGV4dHMiO2E6MDp7fXM6MTg6IgAqAGxpc3RfY2FjaGVfdGFncyI7YToxOntpOjA7czo5OiJ0ZXN0X2xpc3QiO31zOjE0OiIAKgBjb25zdHJhaW50cyI7YTowOnt9czoxMzoiACoAYWRkaXRpb25hbCI7YTowOnt9czo4OiIAKgBjbGFzcyI7TjtzOjExOiIAKgBwcm92aWRlciI7TjtzOjIwOiIAKgBzdHJpbmdUcmFuc2xhdGlvbiI7Tjt9';
|
||||
/** @var \Drupal\Tests\system\Functional\Entity\Update\TestRevisionMetadataBcLayerEntityType $entity_type */
|
||||
$entity_type = unserialize(base64_decode($cached_with_no_metadata_keys));
|
||||
$required_revision_metadata_keys_no_bc = [];
|
||||
$this->assertEquals($required_revision_metadata_keys_no_bc, $entity_type->getRevisionMetadataKeys(FALSE));
|
||||
$required_revision_metadata_keys_with_bc = $required_revision_metadata_keys_no_bc + [
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log_message',
|
||||
];
|
||||
$this->assertEquals($required_revision_metadata_keys_with_bc, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
|
||||
// Ensure that cached entity types with only one required revision metadata
|
||||
// key will return only that one after a second required revision metadata
|
||||
// key has been added.
|
||||
// Contains one revision metadata key - revision_default which is also
|
||||
// contained in the property holding the required revision metadata keys,
|
||||
// the entity type id is "entity_test_mul_revlog".
|
||||
$cached_with_metadata_key_revision_default = 'Tzo4MjoiRHJ1cGFsXFRlc3RzXHN5c3RlbVxGdW5jdGlvbmFsXEVudGl0eVxVcGRhdGVcVGVzdFJldmlzaW9uTWV0YWRhdGFCY0xheWVyRW50aXR5VHlwZSI6Mzk6e3M6MjU6IgAqAHJldmlzaW9uX21ldGFkYXRhX2tleXMiO2E6MTp7czoxNjoicmV2aXNpb25fZGVmYXVsdCI7czoxNjoicmV2aXNpb25fZGVmYXVsdCI7fXM6MzE6IgAqAHJlcXVpcmVkUmV2aXNpb25NZXRhZGF0YUtleXMiO2E6MTp7czoxNjoicmV2aXNpb25fZGVmYXVsdCI7czoxNjoicmV2aXNpb25fZGVmYXVsdCI7fXM6MTU6IgAqAHN0YXRpY19jYWNoZSI7YjoxO3M6MTU6IgAqAHJlbmRlcl9jYWNoZSI7YjoxO3M6MTk6IgAqAHBlcnNpc3RlbnRfY2FjaGUiO2I6MTtzOjE0OiIAKgBlbnRpdHlfa2V5cyI7YTo1OntzOjg6InJldmlzaW9uIjtzOjA6IiI7czo2OiJidW5kbGUiO3M6MDoiIjtzOjg6Imxhbmdjb2RlIjtzOjA6IiI7czoxNjoiZGVmYXVsdF9sYW5nY29kZSI7czoxNjoiZGVmYXVsdF9sYW5nY29kZSI7czoyOToicmV2aXNpb25fdHJhbnNsYXRpb25fYWZmZWN0ZWQiO3M6Mjk6InJldmlzaW9uX3RyYW5zbGF0aW9uX2FmZmVjdGVkIjt9czo1OiIAKgBpZCI7czoyMjoiZW50aXR5X3Rlc3RfbXVsX3JldmxvZyI7czoxNjoiACoAb3JpZ2luYWxDbGFzcyI7TjtzOjExOiIAKgBoYW5kbGVycyI7YTozOntzOjY6ImFjY2VzcyI7czo0NToiRHJ1cGFsXENvcmVcRW50aXR5XEVudGl0eUFjY2Vzc0NvbnRyb2xIYW5kbGVyIjtzOjc6InN0b3JhZ2UiO3M6NDY6IkRydXBhbFxDb3JlXEVudGl0eVxTcWxcU3FsQ29udGVudEVudGl0eVN0b3JhZ2UiO3M6MTI6InZpZXdfYnVpbGRlciI7czozNjoiRHJ1cGFsXENvcmVcRW50aXR5XEVudGl0eVZpZXdCdWlsZGVyIjt9czoxOToiACoAYWRtaW5fcGVybWlzc2lvbiI7TjtzOjI1OiIAKgBwZXJtaXNzaW9uX2dyYW51bGFyaXR5IjtzOjExOiJlbnRpdHlfdHlwZSI7czo4OiIAKgBsaW5rcyI7YTowOnt9czoxNzoiACoAbGFiZWxfY2FsbGJhY2siO047czoyMToiACoAYnVuZGxlX2VudGl0eV90eXBlIjtOO3M6MTI6IgAqAGJ1bmRsZV9vZiI7TjtzOjE1OiIAKgBidW5kbGVfbGFiZWwiO047czoxMzoiACoAYmFzZV90YWJsZSI7TjtzOjIyOiIAKgByZXZpc2lvbl9kYXRhX3RhYmxlIjtOO3M6MTc6IgAqAHJldmlzaW9uX3RhYmxlIjtOO3M6MTM6IgAqAGRhdGFfdGFibGUiO047czoxNToiACoAdHJhbnNsYXRhYmxlIjtiOjA7czoxOToiACoAc2hvd19yZXZpc2lvbl91aSI7YjowO3M6ODoiACoAbGFiZWwiO3M6MDoiIjtzOjE5OiIAKgBsYWJlbF9jb2xsZWN0aW9uIjtzOjA6IiI7czoxNzoiACoAbGFiZWxfc2luZ3VsYXIiO3M6MDoiIjtzOjE1OiIAKgBsYWJlbF9wbHVyYWwiO3M6MDoiIjtzOjE0OiIAKgBsYWJlbF9jb3VudCI7YTowOnt9czoxNToiACoAdXJpX2NhbGxiYWNrIjtOO3M6ODoiACoAZ3JvdXAiO047czoxNDoiACoAZ3JvdXBfbGFiZWwiO047czoyMjoiACoAZmllbGRfdWlfYmFzZV9yb3V0ZSI7TjtzOjI2OiIAKgBjb21tb25fcmVmZXJlbmNlX3RhcmdldCI7YjowO3M6MjI6IgAqAGxpc3RfY2FjaGVfY29udGV4dHMiO2E6MDp7fXM6MTg6IgAqAGxpc3RfY2FjaGVfdGFncyI7YToxOntpOjA7czo5OiJ0ZXN0X2xpc3QiO31zOjE0OiIAKgBjb25zdHJhaW50cyI7YTowOnt9czoxMzoiACoAYWRkaXRpb25hbCI7YTowOnt9czo4OiIAKgBjbGFzcyI7TjtzOjExOiIAKgBwcm92aWRlciI7TjtzOjIwOiIAKgBzdHJpbmdUcmFuc2xhdGlvbiI7Tjt9';
|
||||
$entity_type = unserialize(base64_decode($cached_with_metadata_key_revision_default));
|
||||
$required_revision_metadata_keys_no_bc = [
|
||||
'revision_default' => 'revision_default',
|
||||
];
|
||||
$this->assertEquals($required_revision_metadata_keys_no_bc, $entity_type->getRevisionMetadataKeys(FALSE));
|
||||
$required_revision_metadata_keys_with_bc = $required_revision_metadata_keys_no_bc + [
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log_message',
|
||||
];
|
||||
$this->assertEquals($required_revision_metadata_keys_with_bc, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
|
||||
// Ensure that newly instantiated entity types will return the two required
|
||||
// revision metadata keys.
|
||||
$entity_type = new TestRevisionMetadataBcLayerEntityType(['id' => 'test']);
|
||||
$required_revision_metadata_keys = [
|
||||
'revision_default' => 'revision_default',
|
||||
'second_required_key' => 'second_required_key',
|
||||
];
|
||||
$this->assertEquals($required_revision_metadata_keys, $entity_type->getRevisionMetadataKeys(FALSE));
|
||||
|
||||
// Load an entity type from the cache with no revision metadata keys in the
|
||||
// annotation.
|
||||
$entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
|
||||
$entity_type = $entity_last_installed_schema_repository->getLastInstalledDefinition('entity_test_mul_revlog');
|
||||
$revision_metadata_keys = [];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(FALSE));
|
||||
$revision_metadata_keys = [
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log_message',
|
||||
];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
|
||||
// Load an entity type without using the cache with no revision metadata
|
||||
// keys in the annotation.
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$entity_type_manager->useCaches(FALSE);
|
||||
$entity_type = $entity_type_manager->getDefinition('entity_test_mul_revlog');
|
||||
$revision_metadata_keys = [
|
||||
'revision_default' => 'revision_default',
|
||||
];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(FALSE));
|
||||
$revision_metadata_keys = [
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log_message',
|
||||
'revision_default' => 'revision_default',
|
||||
];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
|
||||
// Ensure that the BC layer will not be triggered if one of the required
|
||||
// revision metadata keys is defined in the annotation.
|
||||
$definition = [
|
||||
'id' => 'entity_test_mul_revlog',
|
||||
'revision_metadata_keys' => [
|
||||
'revision_default' => 'revision_default',
|
||||
],
|
||||
];
|
||||
$entity_type = new ContentEntityType($definition);
|
||||
$revision_metadata_keys = [
|
||||
'revision_default' => 'revision_default',
|
||||
];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
|
||||
// Ensure that the BC layer will be triggered if no revision metadata keys
|
||||
// have been defined in the annotation.
|
||||
$definition = [
|
||||
'id' => 'entity_test_mul_revlog',
|
||||
];
|
||||
$entity_type = new ContentEntityType($definition);
|
||||
$revision_metadata_keys = [
|
||||
'revision_default' => 'revision_default',
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log_message',
|
||||
];
|
||||
$this->assertEquals($revision_metadata_keys, $entity_type->getRevisionMetadataKeys(TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the revision metadata key BC layer was updated correctly.
|
||||
*/
|
||||
public function testSystemUpdate8501() {
|
||||
$this->runUpdates();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $definition_update_manager */
|
||||
$definition_update_manager = $this->container->get('entity.definition_update_manager');
|
||||
foreach (['block_content', 'node'] as $entity_type_id) {
|
||||
$installed_entity_type = $definition_update_manager->getEntityType($entity_type_id);
|
||||
$revision_metadata_keys = $installed_entity_type->get('revision_metadata_keys');
|
||||
$this->assertTrue(isset($revision_metadata_keys['revision_default']));
|
||||
$required_revision_metadata_keys = $installed_entity_type->get('requiredRevisionMetadataKeys');
|
||||
$this->assertTrue(isset($required_revision_metadata_keys['revision_default']));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity type class for adding new required revision metadata keys.
|
||||
*/
|
||||
class TestRevisionMetadataBcLayerEntityType extends ContentEntityType {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($definition) {
|
||||
// Only new instances should provide the required revision metadata keys.
|
||||
// The cached instances should return only what already has been stored
|
||||
// under the property $revision_metadata_keys. The BC layer in
|
||||
// ::getRevisionMetadataKeys() has to detect if the revision metadata keys
|
||||
// have been provided by the entity type annotation, therefore we add keys
|
||||
// to the property $requiredRevisionMetadataKeys only if those keys aren't
|
||||
// set in the entity type annotation.
|
||||
if (!isset($definition['revision_metadata_keys']['second_required_key'])) {
|
||||
$this->requiredRevisionMetadataKeys['second_required_key'] = 'second_required_key';
|
||||
}
|
||||
parent::__construct($definition);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
/**
|
||||
* Tests converting a non-translatable entity type with data to revisionable.
|
||||
*
|
||||
* @group Entity
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class SqlContentEntityStorageSchemaConverterNonTranslatableTest extends SqlContentEntityStorageSchemaConverterTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.0.0-rc1-filled.standard.entity_test_update.php.gz',
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.entity-test-schema-converter-enabled.php',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\Core\Entity\Sql\TemporaryTableMapping;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* Defines a class for testing the conversion of entity types to revisionable.
|
||||
*/
|
||||
abstract class SqlContentEntityStorageSchemaConverterTestBase extends UpdatePathTestBase {
|
||||
|
||||
use EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* The entity manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The entity definition update manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
|
||||
*/
|
||||
protected $entityDefinitionUpdateManager;
|
||||
|
||||
/**
|
||||
* The last installed schema repository service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
|
||||
*/
|
||||
protected $lastInstalledSchemaRepository;
|
||||
|
||||
/**
|
||||
* The key-value collection for tracking installed storage schema.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $installedStorageSchema;
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityManager = \Drupal::entityManager();
|
||||
$this->entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager();
|
||||
$this->lastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository');
|
||||
$this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql');
|
||||
$this->state = \Drupal::state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the conversion of an entity type to revisionable.
|
||||
*/
|
||||
public function testMakeRevisionable() {
|
||||
// Check that entity type is not revisionable prior to running the update
|
||||
// process.
|
||||
$entity_test_update = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
|
||||
$this->assertFalse($entity_test_update->isRevisionable());
|
||||
|
||||
$translatable = $entity_test_update->isTranslatable();
|
||||
|
||||
// Make the entity type revisionable and run the updates.
|
||||
if ($translatable) {
|
||||
$this->updateEntityTypeToRevisionableAndTranslatable();
|
||||
}
|
||||
else {
|
||||
$this->updateEntityTypeToRevisionable();
|
||||
}
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_test_update */
|
||||
$entity_test_update = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
|
||||
$field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
|
||||
|
||||
$this->assertTrue($entity_test_update->isRevisionable());
|
||||
$this->assertEquals($translatable, isset($field_storage_definitions['revision_translation_affected']));
|
||||
|
||||
/** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
|
||||
$this->assertEqual(count($storage->loadMultiple()), 102, 'All test entities were found.');
|
||||
|
||||
// Check that each field value was copied correctly to the revision tables.
|
||||
for ($i = 1; $i <= 102; $i++) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
$revision = $storage->loadRevision($i);
|
||||
|
||||
$this->assertEqual($i, $revision->id());
|
||||
$this->assertEqual($i, $revision->getRevisionId());
|
||||
|
||||
$this->assertEqual($i . ' - test single property', $revision->test_single_property->value);
|
||||
|
||||
$this->assertEqual($i . ' - test multiple properties - value1', $revision->test_multiple_properties->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties - value2', $revision->test_multiple_properties->value2);
|
||||
|
||||
$this->assertEqual($i . ' - test single property multiple values 0', $revision->test_single_property_multiple_values->value);
|
||||
$this->assertEqual($i . ' - test single property multiple values 1', $revision->test_single_property_multiple_values[1]->value);
|
||||
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value1 0', $revision->test_multiple_properties_multiple_values[0]->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value2 0', $revision->test_multiple_properties_multiple_values[0]->value2);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value1 1', $revision->test_multiple_properties_multiple_values[1]->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value2 1', $revision->test_multiple_properties_multiple_values[1]->value2);
|
||||
|
||||
$this->assertEqual($i . ' - field test configurable field - value1 0', $revision->field_test_configurable_field[0]->value1);
|
||||
$this->assertEqual($i . ' - field test configurable field - value2 0', $revision->field_test_configurable_field[0]->value2);
|
||||
$this->assertEqual($i . ' - field test configurable field - value1 1', $revision->field_test_configurable_field[1]->value1);
|
||||
$this->assertEqual($i . ' - field test configurable field - value2 1', $revision->field_test_configurable_field[1]->value2);
|
||||
|
||||
$this->assertEqual($i . ' - test entity base field info', $revision->test_entity_base_field_info->value);
|
||||
|
||||
// Do the same checks for translated field values if the entity type is
|
||||
// translatable.
|
||||
if (!$translatable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the correct initial value was provided for the
|
||||
// 'revision_translation_affected' field.
|
||||
$this->assertTrue($revision->revision_translation_affected->value);
|
||||
|
||||
$translation = $revision->getTranslation('ro');
|
||||
|
||||
$this->assertEqual($i . ' - test single property - ro', $translation->test_single_property->value);
|
||||
|
||||
$this->assertEqual($i . ' - test multiple properties - value1 - ro', $translation->test_multiple_properties->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties - value2 - ro', $translation->test_multiple_properties->value2);
|
||||
|
||||
$this->assertEqual($i . ' - test single property multiple values 0 - ro', $translation->test_single_property_multiple_values[0]->value);
|
||||
$this->assertEqual($i . ' - test single property multiple values 1 - ro', $translation->test_single_property_multiple_values[1]->value);
|
||||
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value1 0 - ro', $translation->test_multiple_properties_multiple_values[0]->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value2 0 - ro', $translation->test_multiple_properties_multiple_values[0]->value2);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value1 1 - ro', $translation->test_multiple_properties_multiple_values[1]->value1);
|
||||
$this->assertEqual($i . ' - test multiple properties multiple values - value2 1 - ro', $translation->test_multiple_properties_multiple_values[1]->value2);
|
||||
|
||||
$this->assertEqual($i . ' - field test configurable field - value1 0 - ro', $translation->field_test_configurable_field[0]->value1);
|
||||
$this->assertEqual($i . ' - field test configurable field - value2 0 - ro', $translation->field_test_configurable_field[0]->value2);
|
||||
$this->assertEqual($i . ' - field test configurable field - value1 1 - ro', $translation->field_test_configurable_field[1]->value1);
|
||||
$this->assertEqual($i . ' - field test configurable field - value2 1 - ro', $translation->field_test_configurable_field[1]->value2);
|
||||
|
||||
$this->assertEqual($i . ' - test entity base field info - ro', $translation->test_entity_base_field_info->value);
|
||||
}
|
||||
|
||||
// Check that temporary tables have been removed at the end of the process.
|
||||
$schema = \Drupal::database()->schema();
|
||||
foreach ($storage->getTableMapping()->getTableNames() as $table_name) {
|
||||
$this->assertFalse($schema->tableExists(TemporaryTableMapping::getTempTableName($table_name)));
|
||||
}
|
||||
|
||||
// Check that backup tables have been removed at the end of the process.
|
||||
$schema = \Drupal::database()->schema();
|
||||
foreach ($storage->getTableMapping()->getTableNames() as $table_name) {
|
||||
$this->assertFalse($schema->tableExists(TemporaryTableMapping::getTempTableName($table_name, 'old_')));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\Core\Entity\Sql\TemporaryTableMapping;
|
||||
|
||||
/**
|
||||
* Tests converting a translatable entity type with data to revisionable.
|
||||
*
|
||||
* @group Entity
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class SqlContentEntityStorageSchemaConverterTranslatableTest extends SqlContentEntityStorageSchemaConverterTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.0.0-rc1-filled.standard.entity_test_update_mul.php.gz',
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.entity-test-schema-converter-enabled.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a failed "make revisionable" update preserves the existing data.
|
||||
*/
|
||||
public function testMakeRevisionableErrorHandling() {
|
||||
$original_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
|
||||
$original_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
|
||||
|
||||
$original_entity_schema_data = $this->installedStorageSchema->get('entity_test_update.entity_schema_data', []);
|
||||
foreach ($original_storage_definitions as $storage_definition) {
|
||||
$original_field_schema_data[$storage_definition->getName()] = $this->installedStorageSchema->get('entity_test_update.field_schema_data.' . $storage_definition->getName(), []);
|
||||
}
|
||||
|
||||
// Check that entity type is not revisionable prior to running the update
|
||||
// process.
|
||||
$this->assertFalse($original_entity_type->isRevisionable());
|
||||
|
||||
// Make the update throw an exception during the entity save process.
|
||||
\Drupal::state()->set('entity_test_update.throw_exception', TRUE);
|
||||
|
||||
// Since the update process is interrupted by the exception thrown above,
|
||||
// we can not do the full post update testing offered by UpdatePathTestBase.
|
||||
$this->checkFailedUpdates = FALSE;
|
||||
|
||||
// Make the entity type revisionable and run the updates.
|
||||
$this->updateEntityTypeToRevisionableAndTranslatable();
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Check that the update failed.
|
||||
$this->assertRaw('<strong>' . t('Failed:') . '</strong>');
|
||||
|
||||
// Check that the last installed entity type definition is kept as
|
||||
// non-revisionable.
|
||||
$new_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
|
||||
$this->assertFalse($new_entity_type->isRevisionable(), 'The entity type is kept unchanged.');
|
||||
|
||||
// Check that the last installed field storage definitions did not change by
|
||||
// looking at the 'langcode' field, which is updated automatically.
|
||||
$new_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
|
||||
$langcode_key = $original_entity_type->getKey('langcode');
|
||||
$this->assertEqual($original_storage_definitions[$langcode_key]->isRevisionable(), $new_storage_definitions[$langcode_key]->isRevisionable(), "The 'langcode' field is kept unchanged.");
|
||||
|
||||
/** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
|
||||
|
||||
// Check that installed storage schema did not change.
|
||||
$new_entity_schema_data = $this->installedStorageSchema->get('entity_test_update.entity_schema_data', []);
|
||||
$this->assertEqual($original_entity_schema_data, $new_entity_schema_data);
|
||||
|
||||
foreach ($new_storage_definitions as $storage_definition) {
|
||||
$new_field_schema_data[$storage_definition->getName()] = $this->installedStorageSchema->get('entity_test_update.field_schema_data.' . $storage_definition->getName(), []);
|
||||
}
|
||||
$this->assertEqual($original_field_schema_data, $new_field_schema_data);
|
||||
|
||||
// Check that temporary tables have been removed.
|
||||
$schema = \Drupal::database()->schema();
|
||||
foreach ($storage->getTableMapping()->getTableNames() as $table_name) {
|
||||
$this->assertFalse($schema->tableExists(TemporaryTableMapping::getTempTableName($table_name)));
|
||||
}
|
||||
|
||||
// Check that the original tables still exist and their data is intact.
|
||||
$this->assertTrue($schema->tableExists('entity_test_update'));
|
||||
$this->assertTrue($schema->tableExists('entity_test_update_data'));
|
||||
|
||||
$base_table_count = \Drupal::database()->select('entity_test_update')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($base_table_count, 102);
|
||||
|
||||
$data_table_count = \Drupal::database()->select('entity_test_update_data')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
// There are two records for each entity, one for English and one for
|
||||
// Romanian.
|
||||
$this->assertEqual($data_table_count, 204);
|
||||
|
||||
$base_table_row = \Drupal::database()->select('entity_test_update')
|
||||
->fields('entity_test_update')
|
||||
->condition('id', 1, '=')
|
||||
->condition('langcode', 'en', '=')
|
||||
->execute()
|
||||
->fetchAllAssoc('id');
|
||||
$this->assertEqual('843e9ac7-3351-4cc1-a202-2dbffffae21c', $base_table_row[1]->uuid);
|
||||
|
||||
$data_table_row = \Drupal::database()->select('entity_test_update_data')
|
||||
->fields('entity_test_update_data')
|
||||
->condition('id', 1, '=')
|
||||
->condition('langcode', 'en', '=')
|
||||
->execute()
|
||||
->fetchAllAssoc('id');
|
||||
$this->assertEqual('1 - test single property', $data_table_row[1]->test_single_property);
|
||||
$this->assertEqual('1 - test multiple properties - value1', $data_table_row[1]->test_multiple_properties__value1);
|
||||
$this->assertEqual('1 - test multiple properties - value2', $data_table_row[1]->test_multiple_properties__value2);
|
||||
$this->assertEqual('1 - test entity base field info', $data_table_row[1]->test_entity_base_field_info);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
/**
|
||||
* Runs SqlContentEntityStorageSchemaIndexTest with a dump filled with content.
|
||||
*
|
||||
* @group Entity
|
||||
* @group legacy
|
||||
*/
|
||||
class SqlContentEntityStorageSchemaIndexFilledTest extends SqlContentEntityStorageSchemaIndexTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
parent::setDatabaseDumpFiles();
|
||||
$this->databaseDumpFiles[0] = __DIR__ . '/../../../../fixtures/update/drupal-8.filled.standard.php.gz';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests that a newly-added index is properly created during database updates.
|
||||
*
|
||||
* @group Entity
|
||||
* @group legacy
|
||||
*/
|
||||
class SqlContentEntityStorageSchemaIndexTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity and field schema database updates and execution order.
|
||||
*/
|
||||
public function testIndex() {
|
||||
// The initial Drupal 8 database dump before any updates does not include
|
||||
// the entity ID in the entity field data table indices that were added in
|
||||
// https://www.drupal.org/node/2261669.
|
||||
$this->assertTrue(db_index_exists('node_field_data', 'node__default_langcode'), 'Index node__default_langcode exists prior to running updates.');
|
||||
$this->assertFalse(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode does not exist prior to running updates.');
|
||||
$this->assertFalse(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode does not exist prior to running updates.');
|
||||
|
||||
// Running database updates should update the entity schemata to add the
|
||||
// indices from https://www.drupal.org/node/2261669.
|
||||
$this->runUpdates();
|
||||
$this->assertFalse(db_index_exists('node_field_data', 'node__default_langcode'), 'Index node__default_langcode properly removed.');
|
||||
$this->assertTrue(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode properly created on the node_field_data table.');
|
||||
$this->assertTrue(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode properly created on the user_field_data table.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Entity\Update;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Update\DbUpdatesTrait;
|
||||
|
||||
/**
|
||||
* Tests performing entity updates through the Update API.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class UpdateApiEntityDefinitionUpdateTest extends BrowserTestBase {
|
||||
|
||||
use DbUpdatesTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test'];
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The entity definition update manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
|
||||
*/
|
||||
protected $updatesManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityManager = $this->container->get('entity.manager');
|
||||
$this->updatesManager = $this->container->get('entity.definition_update_manager');
|
||||
|
||||
$admin = $this->drupalCreateUser([], FALSE, TRUE);
|
||||
$this->drupalLogin($admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that individual updates applied sequentially work as expected.
|
||||
*/
|
||||
public function testSingleUpdates() {
|
||||
// Create a test entity.
|
||||
$user_ids = [mt_rand(), mt_rand()];
|
||||
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => $user_ids]);
|
||||
$entity->save();
|
||||
|
||||
// Check that only a single value is stored for 'user_id'.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
|
||||
|
||||
// Make 'user_id' multiple by applying updates.
|
||||
$this->enableUpdates('entity_test', 'entity_definition_updates', 8001);
|
||||
$this->applyUpdates();
|
||||
|
||||
// Ensure the 'entity_test__user_id' table got created.
|
||||
$this->assertTrue(\Drupal::database()->schema()->tableExists('entity_test__user_id'));
|
||||
|
||||
// Check that data was correctly migrated.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
|
||||
|
||||
// Store multiple data and check it is correctly stored.
|
||||
$entity->user_id = $user_ids;
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 2);
|
||||
$this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
|
||||
$this->assertEqual($entity->user_id[1]->target_id, $user_ids[1]);
|
||||
|
||||
// Make 'user_id' single again by applying updates.
|
||||
$this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
|
||||
$this->applyUpdates();
|
||||
|
||||
// Check that data was correctly migrated/dropped.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
|
||||
|
||||
// Check that only a single value is stored for 'user_id' again.
|
||||
$entity->user_id = $user_ids;
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that multiple updates applied in bulk work as expected.
|
||||
*/
|
||||
public function testMultipleUpdates() {
|
||||
// Create a test entity.
|
||||
$user_ids = [mt_rand(), mt_rand()];
|
||||
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => $user_ids]);
|
||||
$entity->save();
|
||||
|
||||
// Check that only a single value is stored for 'user_id'.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
|
||||
|
||||
// Make 'user_id' multiple and then single again by applying updates.
|
||||
$this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
|
||||
$this->applyUpdates();
|
||||
|
||||
// Check that data was correctly migrated back and forth.
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id->target_id, $user_ids[0]);
|
||||
|
||||
// Check that only a single value is stored for 'user_id' again.
|
||||
$entity->user_id = $user_ids;
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertEqual(count($entity->user_id), 1);
|
||||
$this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that entity updates are correctly reported in the status report page.
|
||||
*/
|
||||
public function testStatusReport() {
|
||||
// Create a test entity.
|
||||
$entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => mt_rand()]);
|
||||
$entity->save();
|
||||
|
||||
// Check that the status report initially displays no error.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertNoRaw('Out of date');
|
||||
$this->assertNoRaw('Mismatched entity and/or field definitions');
|
||||
|
||||
// Enable an entity update and check that we have a dedicated status report
|
||||
// item.
|
||||
$this->container->get('state')->set('entity_test.remove_name_field', TRUE);
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertNoRaw('Out of date');
|
||||
$this->assertRaw('Mismatched entity and/or field definitions');
|
||||
|
||||
// Enable a db update and check that now the entity update status report
|
||||
// item is no longer displayed. We assume an update function will fix the
|
||||
// mismatch.
|
||||
$this->enableUpdates('entity_test', 'status_report', 8001);
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertRaw('Out of date');
|
||||
$this->assertRaw('Mismatched entity and/or field definitions');
|
||||
|
||||
// Apply db updates and check that entity updates were not applied.
|
||||
$this->applyUpdates();
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertNoRaw('Out of date');
|
||||
$this->assertRaw('Mismatched entity and/or field definitions');
|
||||
|
||||
// Apply the entity updates and check that the entity update status report
|
||||
// item is no longer displayed.
|
||||
$this->updatesManager->applyUpdates();
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertNoRaw('Out of date');
|
||||
$this->assertNoRaw('Mismatched entity and/or field definitions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the specified entity.
|
||||
*
|
||||
* @param \Drupal\entity_test\Entity\EntityTest $entity
|
||||
* An entity object.
|
||||
*
|
||||
* @return \Drupal\entity_test\Entity\EntityTest
|
||||
* The reloaded entity object.
|
||||
*/
|
||||
protected function reloadEntity(EntityTest $entity) {
|
||||
$this->entityManager->useCaches(FALSE);
|
||||
$this->entityManager->getStorage('entity_test')->resetCache([$entity->id()]);
|
||||
return EntityTest::load($entity->id());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@ use Drupal\Tests\BrowserTestBase;
|
|||
*/
|
||||
class ConfigTest extends BrowserTestBase {
|
||||
|
||||
protected function setUp(){
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin ($this->drupalCreateUser(['administer site configuration']));
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use Drupal\Component\PhpStorage\FileStorage;
|
|||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the log message added by file_save_htacess().
|
||||
* Tests the log message added by file_save_htaccess().
|
||||
*
|
||||
* @group File
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class FileTransferTest extends BrowserTestBase {
|
|||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->testConnection = TestFileTransfer::factory(\Drupal::root(), ['hostname' => $this->hostname, 'username' => $this->username, 'password' => $this->password, 'port' => $this->port]);
|
||||
$this->testConnection = TestFileTransfer::factory($this->root, ['hostname' => $this->hostname, 'username' => $this->username, 'password' => $this->password, 'port' => $this->port]);
|
||||
}
|
||||
|
||||
public function _getFakeModuleFiles() {
|
||||
|
|
@ -28,11 +28,11 @@ class FileTransferTest extends BrowserTestBase {
|
|||
'fake.module',
|
||||
'fake.info.yml',
|
||||
'theme' => [
|
||||
'fake.html.twig'
|
||||
'fake.html.twig',
|
||||
],
|
||||
'inc' => [
|
||||
'fake.inc'
|
||||
]
|
||||
'fake.inc',
|
||||
],
|
||||
];
|
||||
return $files;
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ class FileTransferTest extends BrowserTestBase {
|
|||
$this->_writeDirectory($base . DIRECTORY_SEPARATOR . $key, $file);
|
||||
}
|
||||
else {
|
||||
//just write the filename into the file
|
||||
// just write the filename into the file
|
||||
file_put_contents($base . DIRECTORY_SEPARATOR . $file, $file);
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ class FileTransferTest extends BrowserTestBase {
|
|||
|
||||
$gotit = TRUE;
|
||||
try {
|
||||
$this->testConnection->copyDirectory($source, \Drupal::root() . '/' . PublicStream::basePath());
|
||||
$this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
|
||||
}
|
||||
catch (FileTransferException $e) {
|
||||
$gotit = FALSE;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ class TestFileTransfer extends FileTransfer {
|
|||
|
||||
/**
|
||||
* This is for testing the CopyRecursive logic.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $shouldIsDirectoryReturnTrue = FALSE;
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ class TestFileTransfer extends FileTransfer {
|
|||
|
||||
public function removeFileJailed($destination) {
|
||||
if (!ftp_delete($this->connection, $item)) {
|
||||
throw new FileTransferException('Unable to remove to file @file.', NULL, ['@file' => $item]);
|
||||
throw new FileTransferException('Unable to remove the file @file.', NULL, ['@file' => $item]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests hook_form_alter() and hook_form_FORM_ID_alter().
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class AlterTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'form_test'];
|
||||
|
||||
/**
|
||||
* Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter().
|
||||
*/
|
||||
public function testExecutionOrder() {
|
||||
$this->drupalGet('form-test/alter');
|
||||
// Ensure that the order is first by module, then for a given module, the
|
||||
// id-specific one after the generic one.
|
||||
$expected = [
|
||||
'block_form_form_test_alter_form_alter() executed.',
|
||||
'form_test_form_alter() executed.',
|
||||
'form_test_form_form_test_alter_form_alter() executed.',
|
||||
'system_form_form_test_alter_form_alter() executed.',
|
||||
];
|
||||
$content = preg_replace('/\s+/', ' ', Xss::filter($this->getSession()->getPage()->getContent(), []));
|
||||
$this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Form alter hooks executed in the expected order.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests altering forms to be rebuilt so there are multiple steps.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ArbitraryRebuildTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['text', 'form_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Auto-create a field for testing.
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'user',
|
||||
'field_name' => 'test_multiple',
|
||||
'type' => 'text',
|
||||
'cardinality' => -1,
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'user',
|
||||
'field_name' => 'test_multiple',
|
||||
'bundle' => 'user',
|
||||
'label' => 'Test a multiple valued field',
|
||||
])->save();
|
||||
entity_get_form_display('user', 'user', 'register')
|
||||
->setComponent('test_multiple', [
|
||||
'type' => 'text_textfield',
|
||||
'weight' => 0,
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a basic rebuild with the user registration form.
|
||||
*/
|
||||
public function testUserRegistrationRebuild() {
|
||||
$edit = [
|
||||
'name' => 'foo',
|
||||
'mail' => 'bar@example.com',
|
||||
];
|
||||
$this->drupalPostForm('user/register', $edit, 'Rebuild');
|
||||
$this->assertText('Form rebuilt.');
|
||||
$this->assertFieldByName('name', 'foo', 'Entered username has been kept.');
|
||||
$this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a rebuild caused by a multiple value field.
|
||||
*/
|
||||
public function testUserRegistrationMultipleField() {
|
||||
$edit = [
|
||||
'name' => 'foo',
|
||||
'mail' => 'bar@example.com',
|
||||
];
|
||||
$this->drupalPostForm('user/register', $edit, t('Add another item'));
|
||||
$this->assertText('Test a multiple valued field', 'Form has been rebuilt.');
|
||||
$this->assertFieldByName('name', 'foo', 'Entered username has been kept.');
|
||||
$this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests form API checkbox handling of various combinations of #default_value
|
||||
* and #return_value.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class CheckboxTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
public function testFormCheckbox() {
|
||||
// Ensure that the checked state is determined and rendered correctly for
|
||||
// tricky combinations of default and return values.
|
||||
foreach ([FALSE, NULL, TRUE, 0, '0', '', 1, '1', 'foobar', '1foobar'] as $default_value) {
|
||||
// Only values that can be used for array indices are supported for
|
||||
// #return_value, with the exception of integer 0, which is not supported.
|
||||
// @see \Drupal\Core\Render\Element\Checkbox::processCheckbox().
|
||||
foreach (['0', '', 1, '1', 'foobar', '1foobar'] as $return_value) {
|
||||
$form_array = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestCheckboxTypeJugglingForm', $default_value, $return_value);
|
||||
$form = \Drupal::service('renderer')->renderRoot($form_array);
|
||||
if ($default_value === TRUE) {
|
||||
$checked = TRUE;
|
||||
}
|
||||
elseif ($return_value === '0') {
|
||||
$checked = ($default_value === '0');
|
||||
}
|
||||
elseif ($return_value === '') {
|
||||
$checked = ($default_value === '');
|
||||
}
|
||||
elseif ($return_value === 1 || $return_value === '1') {
|
||||
$checked = ($default_value === 1 || $default_value === '1');
|
||||
}
|
||||
elseif ($return_value === 'foobar') {
|
||||
$checked = ($default_value === 'foobar');
|
||||
}
|
||||
elseif ($return_value === '1foobar') {
|
||||
$checked = ($default_value === '1foobar');
|
||||
}
|
||||
$checked_in_html = strpos($form, 'checked') !== FALSE;
|
||||
$message = format_string('#default_value is %default_value #return_value is %return_value.', ['%default_value' => var_export($default_value, TRUE), '%return_value' => var_export($return_value, TRUE)]);
|
||||
$this->assertIdentical($checked, $checked_in_html, $message);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that $form_state->getValues() is populated correctly for a
|
||||
// checkboxes group that includes a 0-indexed array of options.
|
||||
$this->drupalPostForm('form-test/checkboxes-zero/1', [], 'Save');
|
||||
$results = json_decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertIdentical($results->checkbox_off, [0, 0, 0], 'All three in checkbox_off are zeroes: off.');
|
||||
$this->assertIdentical($results->checkbox_zero_default, ['0', 0, 0], 'The first choice is on in checkbox_zero_default');
|
||||
$this->assertIdentical($results->checkbox_string_zero_default, ['0', 0, 0], 'The first choice is on in checkbox_string_zero_default');
|
||||
// Due to Mink driver differences, we cannot submit an empty checkbox value
|
||||
// to drupalPostForm(), even if that empty value is the 'true' value for
|
||||
// the checkbox.
|
||||
$this->drupalGet('form-test/checkboxes-zero/1');
|
||||
$this->assertSession()->fieldExists('checkbox_off[0]')->check();
|
||||
$this->drupalPostForm(NULL, NULL, 'Save');
|
||||
$results = json_decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertIdentical($results->checkbox_off, ['0', 0, 0], 'The first choice is on in checkbox_off but the rest is not');
|
||||
|
||||
// Ensure that each checkbox is rendered correctly for a checkboxes group
|
||||
// that includes a 0-indexed array of options.
|
||||
$this->drupalPostForm('form-test/checkboxes-zero/0', [], 'Save');
|
||||
$checkboxes = $this->xpath('//input[@type="checkbox"]');
|
||||
|
||||
$this->assertIdentical(count($checkboxes), 9, 'Correct number of checkboxes found.');
|
||||
foreach ($checkboxes as $checkbox) {
|
||||
$checked = $checkbox->isChecked();
|
||||
$name = $checkbox->getAttribute('name');
|
||||
$this->assertIdentical($checked, $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', ['%name' => $name]));
|
||||
}
|
||||
// Due to Mink driver differences, we cannot submit an empty checkbox value
|
||||
// to drupalPostForm(), even if that empty value is the 'true' value for
|
||||
// the checkbox.
|
||||
$this->drupalGet('form-test/checkboxes-zero/0');
|
||||
$this->assertSession()->fieldExists('checkbox_off[0]')->check();
|
||||
$this->drupalPostForm(NULL, NULL, 'Save');
|
||||
$checkboxes = $this->xpath('//input[@type="checkbox"]');
|
||||
|
||||
$this->assertIdentical(count($checkboxes), 9, 'Correct number of checkboxes found.');
|
||||
foreach ($checkboxes as $checkbox) {
|
||||
$checked = $checkbox->isChecked();
|
||||
$name = (string) $checkbox->getAttribute('name');
|
||||
$this->assertIdentical($checked, $name == 'checkbox_off[0]' || $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', ['%name' => $name]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests confirmation forms.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ConfirmFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
public function testConfirmForm() {
|
||||
// Test the building of the form.
|
||||
$this->drupalGet('form-test/confirm-form');
|
||||
$site_name = $this->config('system.site')->get('name');
|
||||
$this->assertTitle(t('ConfirmFormTestForm::getQuestion(). | @site-name', ['@site-name' => $site_name]), 'The question was found as the page title.');
|
||||
$this->assertText(t('ConfirmFormTestForm::getDescription().'), 'The description was used.');
|
||||
$this->assertFieldByXPath('//input[@id="edit-submit"]', t('ConfirmFormTestForm::getConfirmText().'), 'The confirm text was used.');
|
||||
|
||||
// Test cancelling the form.
|
||||
$this->clickLink(t('ConfirmFormTestForm::getCancelText().'));
|
||||
$this->assertUrl('form-test/autocomplete', [], "The form's cancel link was followed.");
|
||||
|
||||
// Test submitting the form.
|
||||
$this->drupalPostForm('form-test/confirm-form', NULL, t('ConfirmFormTestForm::getConfirmText().'));
|
||||
$this->assertText('The ConfirmFormTestForm::submitForm() method was used for this form.');
|
||||
$this->assertUrl('', [], "The form's redirect was followed.");
|
||||
|
||||
// Test submitting the form with a destination.
|
||||
$this->drupalPostForm('form-test/confirm-form', NULL, t('ConfirmFormTestForm::getConfirmText().'), ['query' => ['destination' => 'admin/config']]);
|
||||
$this->assertUrl('admin/config', [], "The form's redirect was not followed, the destination query string was followed.");
|
||||
|
||||
// Test cancelling the form with a complex destination.
|
||||
$this->drupalGet('form-test/confirm-form-array-path');
|
||||
$this->clickLink(t('ConfirmFormArrayPathTestForm::getCancelText().'));
|
||||
$this->assertUrl('form-test/confirm-form', ['query' => ['destination' => 'admin/config']], "The form's complex cancel link was followed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the confirm form does not use external destinations.
|
||||
*/
|
||||
public function testConfirmFormWithExternalDestination() {
|
||||
$this->drupalGet('form-test/confirm-form');
|
||||
$this->assertCancelLinkUrl(Url::fromRoute('form_test.route8'));
|
||||
$this->drupalGet('form-test/confirm-form', ['query' => ['destination' => 'node']]);
|
||||
$this->assertCancelLinkUrl(Url::fromUri('internal:/node'));
|
||||
$this->drupalGet('form-test/confirm-form', ['query' => ['destination' => 'http://example.com']]);
|
||||
$this->assertCancelLinkUrl(Url::fromRoute('form_test.route8'));
|
||||
$this->drupalGet('form-test/confirm-form', ['query' => ['destination' => '<front>']]);
|
||||
$this->assertCancelLinkUrl(Url::fromRoute('<front>'));
|
||||
// Other invalid destinations, should fall back to the form default.
|
||||
$this->drupalGet('form-test/confirm-form', ['query' => ['destination' => '/http://example.com']]);
|
||||
$this->assertCancelLinkUrl(Url::fromRoute('form_test.route8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a cancel link is present pointing to the provided URL.
|
||||
*
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The url to check for.
|
||||
* @param string $message
|
||||
* The assert message.
|
||||
* @param string $group
|
||||
* The assertion group.
|
||||
*
|
||||
* @return bool
|
||||
* Result of the assertion.
|
||||
*/
|
||||
public function assertCancelLinkUrl(Url $url, $message = '', $group = 'Other') {
|
||||
$links = $this->xpath('//a[@href=:url]', [':url' => $url->toString()]);
|
||||
$message = ($message ? $message : new FormattableMarkup('Cancel link with URL %url found.', ['%url' => $url->toString()]));
|
||||
return $this->assertTrue(isset($links[0]), $message, $group);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests building and processing of core form elements.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ElementTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests placeholder text for elements that support placeholders.
|
||||
*/
|
||||
public function testPlaceHolderText() {
|
||||
$this->drupalGet('form-test/placeholder-text');
|
||||
$expected = 'placeholder-text';
|
||||
// Test to make sure non-textarea elements have the proper placeholder text.
|
||||
foreach (['textfield', 'tel', 'url', 'password', 'email', 'number'] as $type) {
|
||||
$element = $this->xpath('//input[@id=:id and @placeholder=:expected]', [
|
||||
':id' => 'edit-' . $type,
|
||||
':expected' => $expected,
|
||||
]);
|
||||
$this->assertTrue(!empty($element), format_string('Placeholder text placed in @type.', ['@type' => $type]));
|
||||
}
|
||||
|
||||
// Test to make sure textarea has the proper placeholder text.
|
||||
$element = $this->xpath('//textarea[@id=:id and @placeholder=:expected]', [
|
||||
':id' => 'edit-textarea',
|
||||
':expected' => $expected,
|
||||
]);
|
||||
$this->assertTrue(!empty($element), 'Placeholder text placed in textarea.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests expansion of #options for #type checkboxes and radios.
|
||||
*/
|
||||
public function testOptions() {
|
||||
$this->drupalGet('form-test/checkboxes-radios');
|
||||
|
||||
// Verify that all options appear in their defined order.
|
||||
foreach (['checkbox', 'radio'] as $type) {
|
||||
$elements = $this->xpath('//input[@type=:type]', [':type' => $type]);
|
||||
$expected_values = ['0', 'foo', '1', 'bar', '>'];
|
||||
foreach ($elements as $element) {
|
||||
$expected = array_shift($expected_values);
|
||||
$this->assertIdentical((string) $element->getAttribute('value'), $expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the choices are admin filtered as expected.
|
||||
$this->assertRaw("<em>Special Char</em>alert('checkboxes');");
|
||||
$this->assertRaw("<em>Special Char</em>alert('radios');");
|
||||
$this->assertRaw('<em>Bar - checkboxes</em>');
|
||||
$this->assertRaw('<em>Bar - radios</em>');
|
||||
|
||||
// Enable customized option sub-elements.
|
||||
$this->drupalGet('form-test/checkboxes-radios/customize');
|
||||
|
||||
// Verify that all options appear in their defined order, taking a custom
|
||||
// #weight into account.
|
||||
foreach (['checkbox', 'radio'] as $type) {
|
||||
$elements = $this->xpath('//input[@type=:type]', [':type' => $type]);
|
||||
$expected_values = ['0', 'foo', 'bar', '>', '1'];
|
||||
foreach ($elements as $element) {
|
||||
$expected = array_shift($expected_values);
|
||||
$this->assertIdentical((string) $element->getAttribute('value'), $expected);
|
||||
}
|
||||
}
|
||||
// Verify that custom #description properties are output.
|
||||
foreach (['checkboxes', 'radios'] as $type) {
|
||||
$elements = $this->xpath('//input[@id=:id]/following-sibling::div[@class=:class]', [
|
||||
':id' => 'edit-' . $type . '-foo',
|
||||
':class' => 'description',
|
||||
]);
|
||||
$this->assertTrue(count($elements), format_string('Custom %type option description found.', [
|
||||
'%type' => $type,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests correct checked attribute for radios element.
|
||||
*/
|
||||
public function testRadiosChecked() {
|
||||
// Verify that there is only one radio option checked.
|
||||
$this->drupalGet('form-test/radios-checked');
|
||||
$elements = $this->xpath('//input[@name="radios" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('0', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-string" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('bar', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-boolean-true" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('1', $elements[0]->getValue());
|
||||
// A default value of FALSE indicates that nothing is set.
|
||||
$elements = $this->xpath('//input[@name="radios-boolean-false" and @checked]');
|
||||
$this->assertCount(0, $elements);
|
||||
$elements = $this->xpath('//input[@name="radios-boolean-any" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('All', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-string-zero" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('0', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-int-non-zero" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('10', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-int-non-zero-as-string" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('100', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-empty-string" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('0', $elements[0]->getValue());
|
||||
$elements = $this->xpath('//input[@name="radios-empty-array" and @checked]');
|
||||
$this->assertCount(0, $elements);
|
||||
$elements = $this->xpath('//input[@name="radios-key-FALSE" and @checked]');
|
||||
$this->assertCount(1, $elements);
|
||||
$this->assertSame('0', $elements[0]->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests wrapper ids for checkboxes and radios.
|
||||
*/
|
||||
public function testWrapperIds() {
|
||||
$this->drupalGet('form-test/checkboxes-radios');
|
||||
|
||||
// Verify that wrapper id is different from element id.
|
||||
foreach (['checkboxes', 'radios'] as $type) {
|
||||
$element_ids = $this->xpath('//div[@id=:id]', [':id' => 'edit-' . $type]);
|
||||
$wrapper_ids = $this->xpath('//fieldset[@id=:id]', [':id' => 'edit-' . $type . '--wrapper']);
|
||||
$this->assertTrue(count($element_ids) == 1, format_string('A single element id found for type %type', ['%type' => $type]));
|
||||
$this->assertTrue(count($wrapper_ids) == 1, format_string('A single wrapper id found for type %type', ['%type' => $type]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests button classes.
|
||||
*/
|
||||
public function testButtonClasses() {
|
||||
$this->drupalGet('form-test/button-class');
|
||||
// Just contains(@class, "button") won't do because then
|
||||
// "button--foo" would contain "button". Instead, check
|
||||
// " button ". Make sure it matches in the beginning and the end too
|
||||
// by adding a space before and after.
|
||||
$this->assertEqual(2, count($this->xpath('//*[contains(concat(" ", @class, " "), " button ")]')));
|
||||
$this->assertEqual(1, count($this->xpath('//*[contains(concat(" ", @class, " "), " button--foo ")]')));
|
||||
$this->assertEqual(1, count($this->xpath('//*[contains(concat(" ", @class, " "), " button--danger ")]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the #group property.
|
||||
*/
|
||||
public function testGroupElements() {
|
||||
$this->drupalGet('form-test/group-details');
|
||||
$elements = $this->xpath('//div[@class="details-wrapper"]//div[@class="details-wrapper"]//label');
|
||||
$this->assertTrue(count($elements) == 1);
|
||||
$this->drupalGet('form-test/group-container');
|
||||
$elements = $this->xpath('//div[@id="edit-container"]//div[@class="details-wrapper"]//label');
|
||||
$this->assertTrue(count($elements) == 1);
|
||||
$this->drupalGet('form-test/group-fieldset');
|
||||
$elements = $this->xpath('//fieldset[@id="edit-fieldset"]//div[@id="edit-meta"]//label');
|
||||
$this->assertTrue(count($elements) == 1);
|
||||
$this->drupalGet('form-test/group-vertical-tabs');
|
||||
$elements = $this->xpath('//div[@data-vertical-tabs-panes]//details[@id="edit-meta"]//label');
|
||||
$this->assertTrue(count($elements) == 1);
|
||||
$elements = $this->xpath('//div[@data-vertical-tabs-panes]//details[@id="edit-meta-2"]//label');
|
||||
$this->assertTrue(count($elements) == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the #required property on details and fieldset elements.
|
||||
*/
|
||||
public function testRequiredFieldsetsAndDetails() {
|
||||
$this->drupalGet('form-test/group-details');
|
||||
$this->assertFalse($this->cssSelect('summary.form-required'));
|
||||
$this->drupalGet('form-test/group-details/1');
|
||||
$this->assertTrue($this->cssSelect('summary.form-required'));
|
||||
$this->drupalGet('form-test/group-fieldset');
|
||||
$this->assertFalse($this->cssSelect('span.form-required'));
|
||||
$this->drupalGet('form-test/group-fieldset/1');
|
||||
$this->assertTrue($this->cssSelect('span.form-required'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a form with a autocomplete setting..
|
||||
*/
|
||||
public function testFormAutocomplete() {
|
||||
$this->drupalGet('form-test/autocomplete');
|
||||
|
||||
$result = $this->xpath('//input[@id="edit-autocomplete-1" and contains(@data-autocomplete-path, "form-test/autocomplete-1")]');
|
||||
$this->assertEqual(count($result), 0, 'Ensure that the user does not have access to the autocompletion');
|
||||
$result = $this->xpath('//input[@id="edit-autocomplete-2" and contains(@data-autocomplete-path, "form-test/autocomplete-2/value")]');
|
||||
$this->assertEqual(count($result), 0, 'Ensure that the user does not have access to the autocompletion');
|
||||
|
||||
$user = $this->drupalCreateUser(['access autocomplete test']);
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet('form-test/autocomplete');
|
||||
|
||||
// Make sure that the autocomplete library is added.
|
||||
$this->assertRaw('core/misc/autocomplete.js');
|
||||
|
||||
$result = $this->xpath('//input[@id="edit-autocomplete-1" and contains(@data-autocomplete-path, "form-test/autocomplete-1")]');
|
||||
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
|
||||
$result = $this->xpath('//input[@id="edit-autocomplete-2" and contains(@data-autocomplete-path, "form-test/autocomplete-2/value")]');
|
||||
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests form element error messages.
|
||||
*/
|
||||
public function testFormElementErrors() {
|
||||
$this->drupalPostForm('form_test/details-form', [], 'Submit');
|
||||
$this->assertText('I am an error on the details element.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests summary attributes of details.
|
||||
*/
|
||||
public function testDetailsSummaryAttributes() {
|
||||
$this->drupalGet('form-test/group-details');
|
||||
$this->assertTrue($this->cssSelect('summary[data-summary-attribute="test"]'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the container form element for expected behavior.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ElementsContainerTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests the #optional container property.
|
||||
*/
|
||||
public function testOptionalContainerElements() {
|
||||
$this->drupalGet('form-test/optional-container');
|
||||
$assertSession = $this->assertSession();
|
||||
$assertSession->elementNotExists('css', 'div.empty_optional');
|
||||
$assertSession->elementExists('css', 'div.empty_nonoptional');
|
||||
$assertSession->elementExists('css', 'div.nonempty_optional');
|
||||
$assertSession->elementExists('css', 'div.nonempty_nonoptional');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\form_test\Form\FormTestLabelForm;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests form element labels, required markers and associated output.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ElementsLabelsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Test form elements, labels, title attributes and required marks output
|
||||
* correctly and have the correct label option class if needed.
|
||||
*/
|
||||
public function testFormLabels() {
|
||||
$this->drupalGet('form_test/form-labels');
|
||||
|
||||
// Check that the checkbox/radio processing is not interfering with
|
||||
// basic placement.
|
||||
$elements = $this->xpath('//input[@id="edit-form-checkboxes-test-third-checkbox"]/following-sibling::label[@for="edit-form-checkboxes-test-third-checkbox" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label follows field and label option class correct for regular checkboxes.');
|
||||
|
||||
// Make sure the label is rendered for checkboxes.
|
||||
$elements = $this->xpath('//input[@id="edit-form-checkboxes-test-0"]/following-sibling::label[@for="edit-form-checkboxes-test-0" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label 0 found checkbox.');
|
||||
|
||||
$elements = $this->xpath('//input[@id="edit-form-radios-test-second-radio"]/following-sibling::label[@for="edit-form-radios-test-second-radio" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label follows field and label option class correct for regular radios.');
|
||||
|
||||
// Make sure the label is rendered for radios.
|
||||
$elements = $this->xpath('//input[@id="edit-form-radios-test-0"]/following-sibling::label[@for="edit-form-radios-test-0" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label 0 found radios.');
|
||||
|
||||
// Exercise various defaults for checkboxes and modifications to ensure
|
||||
// appropriate override and correct behavior.
|
||||
$elements = $this->xpath('//input[@id="edit-form-checkbox-test"]/following-sibling::label[@for="edit-form-checkbox-test" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label follows field and label option class correct for a checkbox by default.');
|
||||
|
||||
// Exercise various defaults for textboxes and modifications to ensure
|
||||
// appropriate override and correct behavior.
|
||||
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required" and @class="js-form-required form-required"]/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label precedes textfield, with required marker inside label.');
|
||||
|
||||
$elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::label[@for="edit-form-textfield-test-no-title-required" and @class="js-form-required form-required"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label tag with required marker precedes required textfield with no title.');
|
||||
|
||||
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title-invisible"]/preceding-sibling::label[@for="edit-form-textfield-test-title-invisible" and @class="visually-hidden"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label preceding field and label class is visually-hidden.');
|
||||
|
||||
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::span[@class="js-form-required form-required"]');
|
||||
$this->assertFalse(isset($elements[0]), 'No required marker on non-required field.');
|
||||
|
||||
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title-after"]/following-sibling::label[@for="edit-form-textfield-test-title-after" and @class="option"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Label after field and label option class correct for text field.');
|
||||
|
||||
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-no-show"]');
|
||||
$this->assertFalse(isset($elements[0]), 'No label tag when title set not to display.');
|
||||
|
||||
$elements = $this->xpath('//div[contains(@class, "js-form-item-form-textfield-test-title-invisible") and contains(@class, "form-no-label")]');
|
||||
$this->assertTrue(isset($elements[0]), 'Field class is form-no-label when there is no label.');
|
||||
|
||||
// Check #field_prefix and #field_suffix placement.
|
||||
$elements = $this->xpath('//span[@class="field-prefix"]/following-sibling::div[@id="edit-form-radios-test"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Properly placed the #field_prefix element after the label and before the field.');
|
||||
|
||||
$elements = $this->xpath('//span[@class="field-suffix"]/preceding-sibling::div[@id="edit-form-radios-test"]');
|
||||
$this->assertTrue(isset($elements[0]), 'Properly places the #field_suffix element immediately after the form field.');
|
||||
|
||||
// Check #prefix and #suffix placement.
|
||||
$elements = $this->xpath('//div[@id="form-test-textfield-title-prefix"]/following-sibling::div[contains(@class, \'js-form-item-form-textfield-test-title\')]');
|
||||
$this->assertTrue(isset($elements[0]), 'Properly places the #prefix element before the form item.');
|
||||
|
||||
$elements = $this->xpath('//div[@id="form-test-textfield-title-suffix"]/preceding-sibling::div[contains(@class, \'js-form-item-form-textfield-test-title\')]');
|
||||
$this->assertTrue(isset($elements[0]), 'Properly places the #suffix element before the form item.');
|
||||
|
||||
// Check title attribute for radios and checkboxes.
|
||||
$elements = $this->xpath('//div[@id="edit-form-checkboxes-title-attribute"]');
|
||||
$this->assertEqual($elements[0]->getAttribute('title'), 'Checkboxes test' . ' (' . t('Required') . ')', 'Title attribute found.');
|
||||
$elements = $this->xpath('//div[@id="edit-form-radios-title-attribute"]');
|
||||
$this->assertEqual($elements[0]->getAttribute('title'), 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.');
|
||||
|
||||
$elements = $this->xpath('//fieldset[@id="edit-form-checkboxes-title-invisible--wrapper"]/legend/span[contains(@class, "visually-hidden")]');
|
||||
$this->assertTrue(!empty($elements), "Title/Label not displayed when 'visually-hidden' attribute is set in checkboxes.");
|
||||
|
||||
$elements = $this->xpath('//fieldset[@id="edit-form-radios-title-invisible--wrapper"]/legend/span[contains(@class, "visually-hidden")]');
|
||||
$this->assertTrue(!empty($elements), "Title/Label not displayed when 'visually-hidden' attribute is set in radios.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests XSS-protection of element labels.
|
||||
*/
|
||||
public function testTitleEscaping() {
|
||||
$this->drupalGet('form_test/form-labels');
|
||||
foreach (FormTestLabelForm::$typesWithTitle as $type) {
|
||||
$this->assertSession()->responseContains("$type alert('XSS') is XSS filtered!");
|
||||
$this->assertSession()->responseNotContains("$type <script>alert('XSS')</script> is XSS filtered!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests different display options for form element descriptions.
|
||||
*/
|
||||
public function testFormDescriptions() {
|
||||
$this->drupalGet('form_test/form-descriptions');
|
||||
|
||||
// Check #description placement with #description_display='after'.
|
||||
$field_id = 'edit-form-textfield-test-description-after';
|
||||
$description_id = $field_id . '--description';
|
||||
$elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[@id="' . $description_id . '"]');
|
||||
$this->assertTrue(isset($elements[0]), t('Properly places the #description element after the form item.'));
|
||||
|
||||
// Check #description placement with #description_display='before'.
|
||||
$field_id = 'edit-form-textfield-test-description-before';
|
||||
$description_id = $field_id . '--description';
|
||||
$elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/preceding-sibling::div[@id="' . $description_id . '"]');
|
||||
$this->assertTrue(isset($elements[0]), t('Properly places the #description element before the form item.'));
|
||||
|
||||
// Check if the class is 'visually-hidden' on the form element description
|
||||
// for the option with #description_display='invisible' and also check that
|
||||
// the description is placed after the form element.
|
||||
$field_id = 'edit-form-textfield-test-description-invisible';
|
||||
$description_id = $field_id . '--description';
|
||||
$elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[contains(@class, "visually-hidden")]');
|
||||
$this->assertTrue(isset($elements[0]), t('Properly renders the #description element visually-hidden.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test forms in theme-less environments.
|
||||
*/
|
||||
public function testFormsInThemeLessEnvironments() {
|
||||
$form = $this->getFormWithLimitedProperties();
|
||||
$render_service = $this->container->get('renderer');
|
||||
// This should not throw any notices.
|
||||
$render_service->renderPlain($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a form with element with not all properties defined.
|
||||
*/
|
||||
protected function getFormWithLimitedProperties() {
|
||||
$form = [];
|
||||
|
||||
$form['fieldset'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => 'Fieldset',
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the vertical_tabs form element for expected behavior.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ElementsVerticalTabsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* A user with permission to access vertical_tab_test_tabs.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A normal user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['access vertical_tab_test tabs']);
|
||||
$this->webUser = $this->drupalCreateUser();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that vertical-tabs.js is included before collapse.js.
|
||||
*
|
||||
* Otherwise, collapse.js adds "SHOW" or "HIDE" labels to the tabs.
|
||||
*/
|
||||
public function testJavaScriptOrdering() {
|
||||
$this->drupalGet('form_test/vertical-tabs');
|
||||
$content = $this->getSession()->getPage()->getContent();
|
||||
$position1 = strpos($content, 'core/misc/vertical-tabs.js');
|
||||
$position2 = strpos($content, 'core/misc/collapse.js');
|
||||
$this->assertTrue($position1 !== FALSE && $position2 !== FALSE && $position1 < $position2, 'vertical-tabs.js is included before collapse.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that vertical tab markup is not shown if user has no tab access.
|
||||
*/
|
||||
public function testWrapperNotShownWhenEmpty() {
|
||||
// Test admin user can see vertical tabs and wrapper.
|
||||
$this->drupalGet('form_test/vertical-tabs');
|
||||
$wrapper = $this->xpath("//div[@data-vertical-tabs-panes]");
|
||||
$this->assertTrue(isset($wrapper[0]), 'Vertical tab panes found.');
|
||||
|
||||
// Test wrapper markup not present for non-privileged web user.
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('form_test/vertical-tabs');
|
||||
$wrapper = $this->xpath("//div[@data-vertical-tabs-panes]");
|
||||
$this->assertFalse(isset($wrapper[0]), 'Vertical tab wrappers are not displayed to unprivileged users.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that default vertical tab is correctly selected.
|
||||
*/
|
||||
public function testDefaultTab() {
|
||||
$this->drupalGet('form_test/vertical-tabs');
|
||||
|
||||
$value = $this->assertSession()
|
||||
->elementExists('css', 'input[name="vertical_tabs__active_tab"]')
|
||||
->getValue();
|
||||
|
||||
$this->assertSame('edit-tab3', $value, t('The default vertical tab is correctly selected.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that vertical tab form values are cleaned.
|
||||
*/
|
||||
public function testDefaultTabCleaned() {
|
||||
$values = Json::decode($this->drupalPostForm('form_test/form-state-values-clean', [], t('Submit')));
|
||||
$this->assertFalse(isset($values['vertical_tabs__active_tab']), new FormattableMarkup('%element was removed.', ['%element' => 'vertical_tabs__active_tab']));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the form API email element.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class EmailTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests that #type 'email' fields are properly validated.
|
||||
*/
|
||||
public function testFormEmail() {
|
||||
$edit = [];
|
||||
$edit['email'] = 'invalid';
|
||||
$edit['email_required'] = ' ';
|
||||
$this->drupalPostForm('form-test/email', $edit, 'Submit');
|
||||
$this->assertRaw(t('The email address %mail is not valid.', ['%mail' => 'invalid']));
|
||||
$this->assertRaw(t('@name field is required.', ['@name' => 'Address']));
|
||||
|
||||
$edit = [];
|
||||
$edit['email_required'] = ' foo.bar@example.com ';
|
||||
$this->drupalPostForm('form-test/email', $edit, 'Submit');
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertIdentical($values['email'], '');
|
||||
$this->assertEqual($values['email_required'], 'foo.bar@example.com');
|
||||
|
||||
$edit = [];
|
||||
$edit['email'] = 'foo@example.com';
|
||||
$edit['email_required'] = 'example@drupal.org';
|
||||
$this->drupalPostForm('form-test/email', $edit, 'Submit');
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual($values['email'], 'foo@example.com');
|
||||
$this->assertEqual($values['email_required'], 'example@drupal.org');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,15 +2,14 @@
|
|||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\system\Tests\System\SystemConfigFormTestBase;
|
||||
use Drupal\form_test\FormTestObject;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests building a form from an object.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class FormObjectTest extends SystemConfigFormTestBase {
|
||||
class FormObjectTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
|
|
@ -19,19 +18,6 @@ class FormObjectTest extends SystemConfigFormTestBase {
|
|||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->form = new FormTestObject($this->container->get('config.factory'));
|
||||
$this->values = [
|
||||
'bananas' => [
|
||||
'#value' => $this->randomString(10),
|
||||
'#config_name' => 'form_test.object',
|
||||
'#config_key' => 'bananas',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using an object as the form callback.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests form storage from cached pages.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class FormStoragePageCacheTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the build id of the current form.
|
||||
*/
|
||||
protected function getFormBuildId() {
|
||||
$build_id_fields = $this->xpath('//input[@name="form_build_id"]');
|
||||
$this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
|
||||
return (string) $build_id_fields[0]->getAttribute('value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build-id is regenerated when validating cached form.
|
||||
*/
|
||||
public function testValidateFormStorageOnCachedPage() {
|
||||
$this->drupalGet('form-test/form-storage-page-cache');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_initial = $this->getFormBuildId();
|
||||
|
||||
// Trigger validation error by submitting an empty title.
|
||||
$edit = ['title' => ''];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_first_validation = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_initial, $build_id_first_validation, 'Build id changes when form validation fails');
|
||||
|
||||
// Trigger validation error by again submitting an empty title.
|
||||
$edit = ['title' => ''];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_second_validation = $this->getFormBuildId();
|
||||
$this->assertEqual($build_id_first_validation, $build_id_second_validation, 'Build id remains the same when form validation fails subsequently');
|
||||
|
||||
// Repeat the test sequence but this time with a page loaded from the cache.
|
||||
$this->drupalGet('form-test/form-storage-page-cache');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_from_cache_initial = $this->getFormBuildId();
|
||||
$this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request');
|
||||
|
||||
// Trigger validation error by submitting an empty title.
|
||||
$edit = ['title' => ''];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_from_cache_first_validation = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_initial, $build_id_from_cache_first_validation, 'Build id changes when form validation fails');
|
||||
$this->assertNotEqual($build_id_first_validation, $build_id_from_cache_first_validation, 'Build id from first user is not reused');
|
||||
|
||||
// Trigger validation error by again submitting an empty title.
|
||||
$edit = ['title' => ''];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_from_cache_second_validation = $this->getFormBuildId();
|
||||
$this->assertEqual($build_id_from_cache_first_validation, $build_id_from_cache_second_validation, 'Build id remains the same when form validation fails subsequently');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build-id is regenerated when rebuilding cached form.
|
||||
*/
|
||||
public function testRebuildFormStorageOnCachedPage() {
|
||||
$this->drupalGet('form-test/form-storage-page-cache');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
|
||||
$this->assertText('No old build id', 'No old build id on the page');
|
||||
$build_id_initial = $this->getFormBuildId();
|
||||
|
||||
// Trigger rebuild, should regenerate build id. When a submit handler
|
||||
// triggers a rebuild, the form is built twice in the same POST request,
|
||||
// and during the second build, there is an old build ID, but because the
|
||||
// form is not cached during the initial GET request, it is different from
|
||||
// that initial build ID.
|
||||
$edit = ['title' => 'something'];
|
||||
$this->drupalPostForm(NULL, $edit, 'Rebuild');
|
||||
$this->assertNoText('No old build id', 'There is no old build id on the page.');
|
||||
$this->assertNoText($build_id_initial, 'The old build id is not the initial build id.');
|
||||
$build_id_first_rebuild = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_initial, $build_id_first_rebuild, 'Build id changes on first rebuild.');
|
||||
|
||||
// Trigger subsequent rebuild, should regenerate the build id again.
|
||||
$edit = ['title' => 'something'];
|
||||
$this->drupalPostForm(NULL, $edit, 'Rebuild');
|
||||
$this->assertText($build_id_first_rebuild, 'First build id as old build id on the page');
|
||||
$build_id_second_rebuild = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_first_rebuild, $build_id_second_rebuild, 'Build id changes on second rebuild.');
|
||||
}
|
||||
|
||||
}
|
||||
773
web/core/modules/system/tests/src/Functional/Form/FormTest.php
Normal file
773
web/core/modules/system/tests/src/Functional/Form/FormTest.php
Normal file
|
|
@ -0,0 +1,773 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\form_test\Form\FormTestDisabledElementsForm;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* Tests various form element validation mechanisms.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class FormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['filter', 'form_test', 'file', 'datetime'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$filtered_html_format = FilterFormat::create([
|
||||
'format' => 'filtered_html',
|
||||
'name' => 'Filtered HTML',
|
||||
]);
|
||||
$filtered_html_format->save();
|
||||
|
||||
$filtered_html_permission = $filtered_html_format->getPermissionName();
|
||||
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [$filtered_html_permission]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check several empty values for required forms elements.
|
||||
*
|
||||
* Carriage returns, tabs, spaces, and unchecked checkbox elements are not
|
||||
* valid content for a required field.
|
||||
*
|
||||
* If the form field is found in $form_state->getErrors() then the test pass.
|
||||
*/
|
||||
public function testRequiredFields() {
|
||||
// Originates from https://www.drupal.org/node/117748.
|
||||
// Sets of empty strings and arrays.
|
||||
$empty_strings = ['""' => "", '"\n"' => "\n", '" "' => " ", '"\t"' => "\t", '" \n\t "' => " \n\t ", '"\n\n\n\n\n"' => "\n\n\n\n\n"];
|
||||
$empty_arrays = ['array()' => []];
|
||||
$empty_checkbox = [NULL];
|
||||
|
||||
$elements['textfield']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'textfield'];
|
||||
$elements['textfield']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['telephone']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'tel'];
|
||||
$elements['telephone']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['url']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'url'];
|
||||
$elements['url']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['search']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'search'];
|
||||
$elements['search']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['password']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'password'];
|
||||
$elements['password']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['password_confirm']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'password_confirm'];
|
||||
// Provide empty values for both password fields.
|
||||
foreach ($empty_strings as $key => $value) {
|
||||
$elements['password_confirm']['empty_values'][$key] = ['pass1' => $value, 'pass2' => $value];
|
||||
}
|
||||
|
||||
$elements['textarea']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'textarea'];
|
||||
$elements['textarea']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['radios']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'radios', '#options' => ['' => t('None'), $this->randomMachineName(), $this->randomMachineName(), $this->randomMachineName()]];
|
||||
$elements['radios']['empty_values'] = $empty_arrays;
|
||||
|
||||
$elements['checkbox']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'checkbox', '#required' => TRUE];
|
||||
$elements['checkbox']['empty_values'] = $empty_checkbox;
|
||||
|
||||
$elements['checkboxes']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'checkboxes', '#options' => [$this->randomMachineName(), $this->randomMachineName(), $this->randomMachineName()]];
|
||||
$elements['checkboxes']['empty_values'] = $empty_arrays;
|
||||
|
||||
$elements['select']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'select', '#options' => ['' => t('None'), $this->randomMachineName(), $this->randomMachineName(), $this->randomMachineName()]];
|
||||
$elements['select']['empty_values'] = $empty_strings;
|
||||
|
||||
$elements['file']['element'] = ['#title' => $this->randomMachineName(), '#type' => 'file'];
|
||||
$elements['file']['empty_values'] = $empty_strings;
|
||||
|
||||
// Regular expression to find the expected marker on required elements.
|
||||
$required_marker_preg = '@<.*?class=".*?js-form-required.*form-required.*?">@';
|
||||
// Go through all the elements and all the empty values for them.
|
||||
foreach ($elements as $type => $data) {
|
||||
foreach ($data['empty_values'] as $key => $empty) {
|
||||
foreach ([TRUE, FALSE] as $required) {
|
||||
$form_id = $this->randomMachineName();
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$form['op'] = ['#type' => 'submit', '#value' => t('Submit')];
|
||||
$element = $data['element']['#title'];
|
||||
$form[$element] = $data['element'];
|
||||
$form[$element]['#required'] = $required;
|
||||
$user_input[$element] = $empty;
|
||||
$user_input['form_id'] = $form_id;
|
||||
$form_state->setUserInput($user_input);
|
||||
$form_state->setFormObject(new StubForm($form_id, $form));
|
||||
$form_state->setMethod('POST');
|
||||
// The form token CSRF protection should not interfere with this test,
|
||||
// so we bypass it by setting the token to FALSE.
|
||||
$form['#token'] = FALSE;
|
||||
\Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
|
||||
\Drupal::formBuilder()->processForm($form_id, $form, $form_state);
|
||||
$errors = $form_state->getErrors();
|
||||
// Form elements of type 'radios' throw all sorts of PHP notices
|
||||
// when you try to render them like this, so we ignore those for
|
||||
// testing the required marker.
|
||||
// @todo Fix this work-around (https://www.drupal.org/node/588438).
|
||||
$form_output = ($type == 'radios') ? '' : \Drupal::service('renderer')->renderRoot($form);
|
||||
if ($required) {
|
||||
// Make sure we have a form error for this element.
|
||||
$this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'");
|
||||
if (!empty($form_output)) {
|
||||
// Make sure the form element is marked as required.
|
||||
$this->assertTrue(preg_match($required_marker_preg, $form_output), "Required '$type' field is marked as required");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!empty($form_output)) {
|
||||
// Make sure the form element is *not* marked as required.
|
||||
$this->assertFalse(preg_match($required_marker_preg, $form_output), "Optional '$type' field is not marked as required");
|
||||
}
|
||||
if ($type == 'select') {
|
||||
// Select elements are going to have validation errors with empty
|
||||
// input, since those are illegal choices. Just make sure the
|
||||
// error is not "field is required".
|
||||
$this->assertTrue((empty($errors[$element]) || strpos('field is required', (string) $errors[$element]) === FALSE), "Optional '$type' field '$element' is not treated as a required element");
|
||||
}
|
||||
else {
|
||||
// Make sure there is *no* form error for this element.
|
||||
$this->assertTrue(empty($errors[$element]), "Optional '$type' field '$element' has no errors with empty input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear the expected form error messages so they don't appear as exceptions.
|
||||
\Drupal::messenger()->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation for required checkbox, select, and radio elements.
|
||||
*
|
||||
* Submits a test form containing several types of form elements. The form
|
||||
* is submitted twice, first without values for required fields and then
|
||||
* with values. Each submission is checked for relevant error messages.
|
||||
*
|
||||
* @see \Drupal\form_test\Form\FormTestValidateRequiredForm
|
||||
*/
|
||||
public function testRequiredCheckboxesRadio() {
|
||||
$form = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestValidateRequiredForm');
|
||||
|
||||
// Attempt to submit the form with no required fields set.
|
||||
$edit = [];
|
||||
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
||||
|
||||
// The only error messages that should appear are the relevant 'required'
|
||||
// messages for each field.
|
||||
$expected = [];
|
||||
foreach (['textfield', 'checkboxes', 'select', 'radios'] as $key) {
|
||||
if (isset($form[$key]['#required_error'])) {
|
||||
$expected[] = $form[$key]['#required_error'];
|
||||
}
|
||||
elseif (isset($form[$key]['#form_test_required_error'])) {
|
||||
$expected[] = $form[$key]['#form_test_required_error'];
|
||||
}
|
||||
else {
|
||||
$expected[] = t('@name field is required.', ['@name' => $form[$key]['#title']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the page for error messages.
|
||||
$errors = $this->xpath('//div[contains(@class, "error")]//li');
|
||||
foreach ($errors as $error) {
|
||||
$expected_key = array_search($error->getText(), $expected);
|
||||
// If the error message is not one of the expected messages, fail.
|
||||
if ($expected_key === FALSE) {
|
||||
$this->fail(format_string("Unexpected error message: @error", ['@error' => $error[0]]));
|
||||
}
|
||||
// Remove the expected message from the list once it is found.
|
||||
else {
|
||||
unset($expected[$expected_key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if any expected messages were not found.
|
||||
foreach ($expected as $not_found) {
|
||||
$this->fail(format_string("Found error message: @error", ['@error' => $not_found]));
|
||||
}
|
||||
|
||||
// Verify that input elements are still empty.
|
||||
$this->assertFieldByName('textfield', '');
|
||||
$this->assertNoFieldChecked('edit-checkboxes-foo');
|
||||
$this->assertNoFieldChecked('edit-checkboxes-bar');
|
||||
$this->assertOptionSelected('edit-select', '');
|
||||
$this->assertNoFieldChecked('edit-radios-foo');
|
||||
$this->assertNoFieldChecked('edit-radios-bar');
|
||||
$this->assertNoFieldChecked('edit-radios-optional-foo');
|
||||
$this->assertNoFieldChecked('edit-radios-optional-bar');
|
||||
$this->assertNoFieldChecked('edit-radios-optional-default-value-false-foo');
|
||||
$this->assertNoFieldChecked('edit-radios-optional-default-value-false-bar');
|
||||
|
||||
// Submit again with required fields set and verify that there are no
|
||||
// error messages.
|
||||
$edit = [
|
||||
'textfield' => $this->randomString(),
|
||||
'checkboxes[foo]' => TRUE,
|
||||
'select' => 'foo',
|
||||
'radios' => 'bar',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.');
|
||||
$this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that input is retained for safe elements even with an invalid token.
|
||||
*
|
||||
* Submits a test form containing several types of form elements.
|
||||
*/
|
||||
public function testInputWithInvalidToken() {
|
||||
// We need to be logged in to have CSRF tokens.
|
||||
$account = $this->createUser();
|
||||
$this->drupalLogin($account);
|
||||
// Submit again with required fields set but an invalid form token and
|
||||
// verify that all the values are retained.
|
||||
$this->drupalGet(Url::fromRoute('form_test.validate_required'));
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="form_token"]')
|
||||
->setValue('invalid token');
|
||||
$edit = [
|
||||
'textfield' => $this->randomString(),
|
||||
'checkboxes[bar]' => TRUE,
|
||||
'select' => 'bar',
|
||||
'radios' => 'foo',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
// Verify that input elements retained the posted values.
|
||||
$this->assertFieldByName('textfield', $edit['textfield']);
|
||||
$this->assertNoFieldChecked('edit-checkboxes-foo');
|
||||
$this->assertFieldChecked('edit-checkboxes-bar');
|
||||
$this->assertOptionSelected('edit-select', 'bar');
|
||||
$this->assertFieldChecked('edit-radios-foo');
|
||||
|
||||
// Check another form that has a textarea input.
|
||||
$this->drupalGet(Url::fromRoute('form_test.required'));
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="form_token"]')
|
||||
->setValue('invalid token');
|
||||
$edit = [
|
||||
'textfield' => $this->randomString(),
|
||||
'textarea' => $this->randomString() . "\n",
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
$this->assertFieldByName('textfield', $edit['textfield']);
|
||||
$this->assertFieldByName('textarea', $edit['textarea']);
|
||||
|
||||
// Check another form that has a number input.
|
||||
$this->drupalGet(Url::fromRoute('form_test.number'));
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="form_token"]')
|
||||
->setValue('invalid token');
|
||||
$edit = [
|
||||
'integer_step' => mt_rand(1, 100),
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
$this->assertFieldByName('integer_step', $edit['integer_step']);
|
||||
|
||||
// Check a form with a Url field
|
||||
$this->drupalGet(Url::fromRoute('form_test.url'));
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="form_token"]')
|
||||
->setValue('invalid token');
|
||||
$edit = [
|
||||
'url' => $this->randomString(),
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
$this->assertFieldByName('url', $edit['url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF tokens for GET forms should not be added by default.
|
||||
*/
|
||||
public function testGetFormsCsrfToken() {
|
||||
// We need to be logged in to have CSRF tokens.
|
||||
$account = $this->createUser();
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->drupalGet(Url::fromRoute('form_test.get_form'));
|
||||
$this->assertNoRaw('form_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation for required textfield element without title.
|
||||
*
|
||||
* Submits a test form containing a textfield form element without title.
|
||||
* The form is submitted twice, first without value for the required field
|
||||
* and then with value. Each submission is checked for relevant error
|
||||
* messages.
|
||||
*
|
||||
* @see \Drupal\form_test\Form\FormTestValidateRequiredNoTitleForm
|
||||
*/
|
||||
public function testRequiredTextfieldNoTitle() {
|
||||
// Attempt to submit the form with no required field set.
|
||||
$edit = [];
|
||||
$this->drupalPostForm('form-test/validate-required-no-title', $edit, 'Submit');
|
||||
$this->assertNoRaw("The form_test_validate_required_form_no_title form was submitted successfully.", 'Validation form submitted successfully.');
|
||||
|
||||
// Check the page for the error class on the textfield.
|
||||
$this->assertFieldByXPath('//input[contains(@class, "error")]', FALSE, 'Error input form element class found.');
|
||||
|
||||
// Check the page for the aria-invalid attribute on the textfield.
|
||||
$this->assertFieldByXPath('//input[contains(@aria-invalid, "true")]', FALSE, 'Aria invalid attribute found.');
|
||||
|
||||
// Submit again with required fields set and verify that there are no
|
||||
// error messages.
|
||||
$edit = [
|
||||
'textfield' => $this->randomString(),
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$this->assertNoFieldByXpath('//input[contains(@class, "error")]', FALSE, 'No error input form element class found.');
|
||||
$this->assertRaw("The form_test_validate_required_form_no_title form was submitted successfully.", 'Validation form submitted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test default value handling for checkboxes.
|
||||
*
|
||||
* @see _form_test_checkbox()
|
||||
*/
|
||||
public function testCheckboxProcessing() {
|
||||
// First, try to submit without the required checkbox.
|
||||
$edit = [];
|
||||
$this->drupalPostForm('form-test/checkbox', $edit, t('Submit'));
|
||||
$this->assertRaw(t('@name field is required.', ['@name' => 'required_checkbox']), 'A required checkbox is actually mandatory');
|
||||
|
||||
// Now try to submit the form correctly.
|
||||
$this->drupalPostForm(NULL, ['required_checkbox' => 1], t('Submit'));
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$expected_values = [
|
||||
'disabled_checkbox_on' => 'disabled_checkbox_on',
|
||||
'disabled_checkbox_off' => 0,
|
||||
'checkbox_on' => 'checkbox_on',
|
||||
'checkbox_off' => 0,
|
||||
'zero_checkbox_on' => '0',
|
||||
'zero_checkbox_off' => 0,
|
||||
];
|
||||
foreach ($expected_values as $widget => $expected_value) {
|
||||
$this->assertSame($values[$widget], $expected_value, format_string('Checkbox %widget returns expected value (expected: %expected, got: %value)', [
|
||||
'%widget' => var_export($widget, TRUE),
|
||||
'%expected' => var_export($expected_value, TRUE),
|
||||
'%value' => var_export($values[$widget], TRUE),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation of #type 'select' elements.
|
||||
*/
|
||||
public function testSelect() {
|
||||
$form = \Drupal::formBuilder()->getForm('Drupal\form_test\Form\FormTestSelectForm');
|
||||
$this->drupalGet('form-test/select');
|
||||
|
||||
// Verify that the options are escaped as expected.
|
||||
$this->assertEscaped('<strong>four</strong>');
|
||||
$this->assertNoRaw('<strong>four</strong>');
|
||||
|
||||
// Posting without any values should throw validation errors.
|
||||
$this->drupalPostForm(NULL, [], 'Submit');
|
||||
$no_errors = [
|
||||
'select',
|
||||
'select_required',
|
||||
'select_optional',
|
||||
'empty_value',
|
||||
'empty_value_one',
|
||||
'no_default_optional',
|
||||
'no_default_empty_option_optional',
|
||||
'no_default_empty_value_optional',
|
||||
'multiple',
|
||||
'multiple_no_default',
|
||||
];
|
||||
foreach ($no_errors as $key) {
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
}
|
||||
|
||||
$expected_errors = [
|
||||
'no_default',
|
||||
'no_default_empty_option',
|
||||
'no_default_empty_value',
|
||||
'no_default_empty_value_one',
|
||||
'multiple_no_default_required',
|
||||
];
|
||||
foreach ($expected_errors as $key) {
|
||||
$this->assertText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
}
|
||||
|
||||
// Post values for required fields.
|
||||
$edit = [
|
||||
'no_default' => 'three',
|
||||
'no_default_empty_option' => 'three',
|
||||
'no_default_empty_value' => 'three',
|
||||
'no_default_empty_value_one' => 'three',
|
||||
'multiple_no_default_required[]' => 'three',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Submit');
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
// Verify expected values.
|
||||
$expected = [
|
||||
'select' => 'one',
|
||||
'empty_value' => 'one',
|
||||
'empty_value_one' => 'one',
|
||||
'no_default' => 'three',
|
||||
'no_default_optional' => 'one',
|
||||
'no_default_optional_empty_value' => '',
|
||||
'no_default_empty_option' => 'three',
|
||||
'no_default_empty_option_optional' => '',
|
||||
'no_default_empty_value' => 'three',
|
||||
'no_default_empty_value_one' => 'three',
|
||||
'no_default_empty_value_optional' => 0,
|
||||
'multiple' => ['two' => 'two'],
|
||||
'multiple_no_default' => [],
|
||||
'multiple_no_default_required' => ['three' => 'three'],
|
||||
];
|
||||
foreach ($expected as $key => $value) {
|
||||
$this->assertIdentical($values[$key], $value, format_string('@name: @actual is equal to @expected.', [
|
||||
'@name' => $key,
|
||||
'@actual' => var_export($values[$key], TRUE),
|
||||
'@expected' => var_export($value, TRUE),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a select element when #options is not set.
|
||||
*/
|
||||
public function testEmptySelect() {
|
||||
$this->drupalGet('form-test/empty-select');
|
||||
$this->assertFieldByXPath("//select[1]", NULL, 'Select element found.');
|
||||
$this->assertNoFieldByXPath("//select[1]/option", NULL, 'No option element found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation of #type 'number' and 'range' elements.
|
||||
*/
|
||||
public function testNumber() {
|
||||
$form = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestNumberForm');
|
||||
|
||||
// Array with all the error messages to be checked.
|
||||
$error_messages = [
|
||||
'no_number' => '%name must be a number.',
|
||||
'too_low' => '%name must be higher than or equal to %min.',
|
||||
'too_high' => '%name must be lower than or equal to %max.',
|
||||
'step_mismatch' => '%name is not a valid number.',
|
||||
];
|
||||
|
||||
// The expected errors.
|
||||
$expected = [
|
||||
'integer_no_number' => 'no_number',
|
||||
'integer_no_step' => 0,
|
||||
'integer_no_step_step_error' => 'step_mismatch',
|
||||
'integer_step' => 0,
|
||||
'integer_step_error' => 'step_mismatch',
|
||||
'integer_step_min' => 0,
|
||||
'integer_step_min_error' => 'too_low',
|
||||
'integer_step_max' => 0,
|
||||
'integer_step_max_error' => 'too_high',
|
||||
'integer_step_min_border' => 0,
|
||||
'integer_step_max_border' => 0,
|
||||
'integer_step_based_on_min' => 0,
|
||||
'integer_step_based_on_min_error' => 'step_mismatch',
|
||||
'float_small_step' => 0,
|
||||
'float_step_no_error' => 0,
|
||||
'float_step_error' => 'step_mismatch',
|
||||
'float_step_hard_no_error' => 0,
|
||||
'float_step_hard_error' => 'step_mismatch',
|
||||
'float_step_any_no_error' => 0,
|
||||
];
|
||||
|
||||
// First test the number element type, then range.
|
||||
foreach (['form-test/number', 'form-test/number/range'] as $path) {
|
||||
// Post form and show errors.
|
||||
$this->drupalPostForm($path, [], 'Submit');
|
||||
|
||||
foreach ($expected as $element => $error) {
|
||||
// Create placeholder array.
|
||||
$placeholders = [
|
||||
'%name' => $form[$element]['#title'],
|
||||
'%min' => isset($form[$element]['#min']) ? $form[$element]['#min'] : '0',
|
||||
'%max' => isset($form[$element]['#max']) ? $form[$element]['#max'] : '0',
|
||||
];
|
||||
|
||||
foreach ($error_messages as $id => $message) {
|
||||
// Check if the error exists on the page, if the current message ID is
|
||||
// expected. Otherwise ensure that the error message is not present.
|
||||
if ($id === $error) {
|
||||
$this->assertRaw(format_string($message, $placeholders));
|
||||
}
|
||||
else {
|
||||
$this->assertNoRaw(format_string($message, $placeholders));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default value handling of #type 'range' elements.
|
||||
*/
|
||||
public function testRange() {
|
||||
$this->drupalPostForm('form-test/range', [], 'Submit');
|
||||
$values = json_decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual($values->with_default_value, 18);
|
||||
$this->assertEqual($values->float, 10.5);
|
||||
$this->assertEqual($values->integer, 6);
|
||||
$this->assertEqual($values->offset, 6.9);
|
||||
|
||||
$this->drupalPostForm('form-test/range/invalid', [], 'Submit');
|
||||
$this->assertFieldByXPath('//input[@type="range" and contains(@class, "error")]', NULL, 'Range element has the error class.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation of #type 'color' elements.
|
||||
*/
|
||||
public function testColorValidation() {
|
||||
// Keys are inputs, values are expected results.
|
||||
$values = [
|
||||
'' => '#000000',
|
||||
'#000' => '#000000',
|
||||
'AAA' => '#aaaaaa',
|
||||
'#af0DEE' => '#af0dee',
|
||||
'#99ccBc' => '#99ccbc',
|
||||
'#aabbcc' => '#aabbcc',
|
||||
'123456' => '#123456',
|
||||
];
|
||||
|
||||
// Tests that valid values are properly normalized.
|
||||
foreach ($values as $input => $expected) {
|
||||
$edit = [
|
||||
'color' => $input,
|
||||
];
|
||||
$this->drupalPostForm('form-test/color', $edit, 'Submit');
|
||||
$result = json_decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual($result->color, $expected);
|
||||
}
|
||||
|
||||
// Tests invalid values are rejected.
|
||||
$values = ['#0008', '#1234', '#fffffg', '#abcdef22', '17', '#uaa'];
|
||||
foreach ($values as $input) {
|
||||
$edit = [
|
||||
'color' => $input,
|
||||
];
|
||||
$this->drupalPostForm('form-test/color', $edit, 'Submit');
|
||||
$this->assertRaw(t('%name must be a valid color.', ['%name' => 'Color']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test handling of disabled elements.
|
||||
*
|
||||
* @see _form_test_disabled_elements()
|
||||
*/
|
||||
public function testDisabledElements() {
|
||||
// Get the raw form in its original state.
|
||||
$form_state = new FormState();
|
||||
$form = (new FormTestDisabledElementsForm())->buildForm([], $form_state);
|
||||
|
||||
// Build a submission that tries to hijack the form by submitting input for
|
||||
// elements that are disabled.
|
||||
$edit = [];
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#test_hijack_value'])) {
|
||||
if (is_array($form[$key]['#test_hijack_value'])) {
|
||||
foreach ($form[$key]['#test_hijack_value'] as $subkey => $value) {
|
||||
$edit[$key . '[' . $subkey . ']'] = $value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$edit[$key] = $form[$key]['#test_hijack_value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the form with no input, as the browser does for disabled elements,
|
||||
// and fetch the $form_state->getValues() that is passed to the submit handler.
|
||||
$this->drupalPostForm('form-test/disabled-elements', [], t('Submit'));
|
||||
$returned_values['normal'] = Json::decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
// Do the same with input, as could happen if JavaScript un-disables an
|
||||
// element. drupalPostForm() emulates a browser by not submitting input for
|
||||
// disabled elements, so we need to un-disable those elements first.
|
||||
$this->drupalGet('form-test/disabled-elements');
|
||||
$disabled_elements = [];
|
||||
foreach ($this->xpath('//*[@disabled]') as $element) {
|
||||
$disabled_elements[] = (string) $element->getAttribute('name');
|
||||
}
|
||||
|
||||
// All the elements should be marked as disabled, including the ones below
|
||||
// the disabled container.
|
||||
$actual_count = count($disabled_elements);
|
||||
$expected_count = 42;
|
||||
$this->assertEqual($actual_count, $expected_count, new FormattableMarkup('Found @actual elements with disabled property (expected @expected).', [
|
||||
'@actual' => count($disabled_elements),
|
||||
'@expected' => $expected_count,
|
||||
]));
|
||||
|
||||
// Mink does not "see" hidden elements, so we need to set the value of the
|
||||
// hidden element directly.
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="hidden"]')
|
||||
->setValue($edit['hidden']);
|
||||
unset($edit['hidden']);
|
||||
$this->drupalPostForm(NULL, $edit, t('Submit'));
|
||||
$returned_values['hijacked'] = Json::decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
// Ensure that the returned values match the form's default values in both
|
||||
// cases.
|
||||
foreach ($returned_values as $values) {
|
||||
$this->assertFormValuesDefault($values, $form);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the values submitted to a form matches the default values of the elements.
|
||||
*/
|
||||
public function assertFormValuesDefault($values, $form) {
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#default_value'])) {
|
||||
if (isset($form[$key]['#expected_value'])) {
|
||||
$expected_value = $form[$key]['#expected_value'];
|
||||
}
|
||||
else {
|
||||
$expected_value = $form[$key]['#default_value'];
|
||||
}
|
||||
|
||||
if ($key == 'checkboxes_multiple') {
|
||||
// Checkboxes values are not filtered out.
|
||||
$values[$key] = array_filter($values[$key]);
|
||||
}
|
||||
$this->assertIdentical($expected_value, $values[$key], format_string('Default value for %type: expected %expected, returned %returned.', ['%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE)]));
|
||||
}
|
||||
|
||||
// Recurse children.
|
||||
$this->assertFormValuesDefault($values, $form[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify markup for disabled form elements.
|
||||
*
|
||||
* @see _form_test_disabled_elements()
|
||||
*/
|
||||
public function testDisabledMarkup() {
|
||||
$this->drupalGet('form-test/disabled-elements');
|
||||
$form = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestDisabledElementsForm');
|
||||
$type_map = [
|
||||
'textarea' => 'textarea',
|
||||
'select' => 'select',
|
||||
'weight' => 'select',
|
||||
'datetime' => 'datetime',
|
||||
];
|
||||
|
||||
foreach ($form as $name => $item) {
|
||||
// Skip special #types.
|
||||
if (!isset($item['#type']) || in_array($item['#type'], ['hidden', 'text_format'])) {
|
||||
continue;
|
||||
}
|
||||
// Setup XPath and CSS class depending on #type.
|
||||
if (in_array($item['#type'], ['button', 'submit'])) {
|
||||
$path = "//!type[contains(@class, :div-class) and @value=:value]";
|
||||
$class = 'is-disabled';
|
||||
}
|
||||
elseif (in_array($item['#type'], ['image_button'])) {
|
||||
$path = "//!type[contains(@class, :div-class) and @value=:value]";
|
||||
$class = 'is-disabled';
|
||||
}
|
||||
else {
|
||||
// starts-with() required for checkboxes.
|
||||
$path = "//div[contains(@class, :div-class)]/descendant::!type[starts-with(@name, :name)]";
|
||||
$class = 'form-disabled';
|
||||
}
|
||||
// Replace DOM element name in $path according to #type.
|
||||
$type = 'input';
|
||||
if (isset($type_map[$item['#type']])) {
|
||||
$type = $type_map[$item['#type']];
|
||||
}
|
||||
if (isset($item['#value']) && is_object($item['#value'])) {
|
||||
$item['#value'] = (string) $item['#value'];
|
||||
}
|
||||
$path = strtr($path, ['!type' => $type]);
|
||||
// Verify that the element exists.
|
||||
$element = $this->xpath($path, [
|
||||
':name' => Html::escape($name),
|
||||
':div-class' => $class,
|
||||
':value' => isset($item['#value']) ? $item['#value'] : '',
|
||||
]);
|
||||
$this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => $item['#type']]));
|
||||
}
|
||||
|
||||
// Verify special element #type text-format.
|
||||
$element = $this->xpath('//div[contains(@class, :div-class)]/descendant::textarea[@name=:name]', [
|
||||
':name' => 'text_format[value]',
|
||||
':div-class' => 'form-disabled',
|
||||
]);
|
||||
$this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => 'text_format[value]']));
|
||||
$element = $this->xpath('//div[contains(@class, :div-class)]/descendant::select[@name=:name]', [
|
||||
':name' => 'text_format[format]',
|
||||
':div-class' => 'form-disabled',
|
||||
]);
|
||||
$this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => 'text_format[format]']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Form API protections against input forgery.
|
||||
*
|
||||
* @see \Drupal\form_test\Form\FormTestInputForgeryForm
|
||||
*/
|
||||
public function testInputForgery() {
|
||||
$this->drupalGet('form-test/input-forgery');
|
||||
// The value for checkboxes[two] was changed using post render to simulate
|
||||
// an input forgery.
|
||||
// @see \Drupal\form_test\Form\FormTestInputForgeryForm::postRender
|
||||
$this->drupalPostForm(NULL, ['checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE], t('Submit'));
|
||||
$this->assertText('An illegal choice has been detected.', 'Input forgery was detected.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests required attribute.
|
||||
*/
|
||||
public function testRequiredAttribute() {
|
||||
$this->drupalGet('form-test/required-attribute');
|
||||
$expected = 'required';
|
||||
// Test to make sure the elements have the proper required attribute.
|
||||
foreach (['textfield', 'password'] as $type) {
|
||||
$element = $this->xpath('//input[@id=:id and @required=:expected]', [
|
||||
':id' => 'edit-' . $type,
|
||||
':expected' => $expected,
|
||||
]);
|
||||
$this->assertTrue(!empty($element), format_string('The @type has the proper required attribute.', ['@type' => $type]));
|
||||
}
|
||||
|
||||
// Test to make sure textarea has the proper required attribute.
|
||||
$element = $this->xpath('//textarea[@id=:id and @required=:expected]', [
|
||||
':id' => 'edit-textarea',
|
||||
':expected' => $expected,
|
||||
]);
|
||||
$this->assertTrue(!empty($element), 'The textarea has the proper required attribute.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the language select form element prints and submits the right
|
||||
* options.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class LanguageSelectElementTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test', 'language'];
|
||||
|
||||
/**
|
||||
* Tests that the options printed by the language select element are correct.
|
||||
*/
|
||||
public function testLanguageSelectElementOptions() {
|
||||
// Add some languages.
|
||||
ConfigurableLanguage::create([
|
||||
'id' => 'aaa',
|
||||
'label' => $this->randomMachineName(),
|
||||
])->save();
|
||||
|
||||
ConfigurableLanguage::create([
|
||||
'id' => 'bbb',
|
||||
'label' => $this->randomMachineName(),
|
||||
])->save();
|
||||
|
||||
\Drupal::languageManager()->reset();
|
||||
|
||||
$this->drupalGet('form-test/language_select');
|
||||
// Check that the language fields were rendered on the page.
|
||||
$ids = [
|
||||
'edit-languages-all' => LanguageInterface::STATE_ALL,
|
||||
'edit-languages-configurable' => LanguageInterface::STATE_CONFIGURABLE,
|
||||
'edit-languages-locked' => LanguageInterface::STATE_LOCKED,
|
||||
'edit-languages-config-and-locked' => LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_LOCKED,
|
||||
];
|
||||
foreach ($ids as $id => $flags) {
|
||||
$this->assertField($id, format_string('The @id field was found on the page.', ['@id' => $id]));
|
||||
$options = [];
|
||||
/* @var $language_manager \Drupal\Core\Language\LanguageManagerInterface */
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
foreach ($language_manager->getLanguages($flags) as $langcode => $language) {
|
||||
$options[$langcode] = $language->isLocked() ? t('- @name -', ['@name' => $language->getName()]) : $language->getName();
|
||||
}
|
||||
$this->_testLanguageSelectElementOptions($id, $options);
|
||||
}
|
||||
|
||||
// Test that the #options were not altered by #languages.
|
||||
$this->assertField('edit-language-custom-options', format_string('The @id field was found on the page.', ['@id' => 'edit-language-custom-options']));
|
||||
$this->_testLanguageSelectElementOptions('edit-language-custom-options', ['opt1' => 'First option', 'opt2' => 'Second option', 'opt3' => 'Third option']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the case when the language select elements should not be printed.
|
||||
*
|
||||
* This happens when the language module is disabled.
|
||||
*/
|
||||
public function testHiddenLanguageSelectElement() {
|
||||
// Disable the language module, so that the language select field will not
|
||||
// be rendered.
|
||||
$this->container->get('module_installer')->uninstall(['language']);
|
||||
$this->drupalGet('form-test/language_select');
|
||||
// Check that the language fields were rendered on the page.
|
||||
$ids = ['edit-languages-all', 'edit-languages-configurable', 'edit-languages-locked', 'edit-languages-config-and-locked'];
|
||||
foreach ($ids as $id) {
|
||||
$this->assertNoField($id, format_string('The @id field was not found on the page.', ['@id' => $id]));
|
||||
}
|
||||
|
||||
// Check that the submitted values were the default values of the language
|
||||
// field elements.
|
||||
$edit = [];
|
||||
$this->drupalPostForm(NULL, $edit, t('Submit'));
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual($values['languages_all'], 'xx');
|
||||
$this->assertEqual($values['languages_configurable'], 'en');
|
||||
$this->assertEqual($values['languages_locked'], LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->assertEqual($values['languages_config_and_locked'], 'dummy_value');
|
||||
$this->assertEqual($values['language_custom_options'], 'opt2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check the options of a language select form element.
|
||||
*
|
||||
* @param string $id
|
||||
* The id of the language select element to check.
|
||||
*
|
||||
* @param array $options
|
||||
* An array with options to compare with.
|
||||
*/
|
||||
protected function _testLanguageSelectElementOptions($id, $options) {
|
||||
// Check that the options in the language field are exactly the same,
|
||||
// including the order, as the languages sent as a parameter.
|
||||
$elements = $this->xpath("//select[@id='" . $id . "']");
|
||||
$count = 0;
|
||||
/** @var \Behat\Mink\Element\NodeElement $option */
|
||||
foreach ($elements[0]->findAll('css', 'option') as $option) {
|
||||
$count++;
|
||||
$option_title = current($options);
|
||||
$this->assertEqual($option->getText(), $option_title);
|
||||
next($options);
|
||||
}
|
||||
$this->assertEqual($count, count($options), format_string('The number of languages and the number of options shown by the language element are the same: @languages languages, @number options', ['@languages' => count($options), '@number' => $count]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -51,4 +51,29 @@ class ModulesListFormWebTest extends BrowserTestBase {
|
|||
$this->assertText('simpletest');
|
||||
}
|
||||
|
||||
public function testModulesListFormWithInvalidInfoFile() {
|
||||
$broken_info_yml = <<<BROKEN
|
||||
name: Module With Broken Info file
|
||||
type: module
|
||||
BROKEN;
|
||||
$path = \Drupal::service('site.path') . "/modules/broken";
|
||||
mkdir($path, 0777, TRUE);
|
||||
file_put_contents("$path/broken.info.yml", $broken_info_yml);
|
||||
|
||||
$this->drupalLogin(
|
||||
$this->drupalCreateUser(
|
||||
['administer modules', 'administer permissions']
|
||||
)
|
||||
);
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Confirm that the error message is shown.
|
||||
$this->assertSession()
|
||||
->pageTextContains('Modules could not be listed due to an error: Missing required keys (core) in ' . $path . '/broken.info.yml');
|
||||
|
||||
// Check that the module filter text box is available.
|
||||
$this->assertTrue($this->xpath('//input[@name="text"]'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ class RedirectTest extends BrowserTestBase {
|
|||
$this->drupalPostForm($path, $edit, t('Submit'));
|
||||
$this->assertUrl($edit['destination'], [], 'Basic redirection works.');
|
||||
|
||||
|
||||
// Test without redirection.
|
||||
$edit = [
|
||||
'redirection' => FALSE,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the form API Response element.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ResponseTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests that enforced responses propagate through subscribers and middleware.
|
||||
*/
|
||||
public function testFormResponse() {
|
||||
$edit = [
|
||||
'content' => $this->randomString(),
|
||||
'status' => 200,
|
||||
];
|
||||
$this->drupalPostForm('form-test/response', $edit, 'Submit');
|
||||
$content = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertResponse(200);
|
||||
$this->assertIdentical($edit['content'], $content, 'Response content matches');
|
||||
$this->assertIdentical('invoked', $this->drupalGetHeader('X-Form-Test-Response-Event'), 'Response handled by kernel response subscriber');
|
||||
$this->assertIdentical('invoked', $this->drupalGetHeader('X-Form-Test-Stack-Middleware'), 'Response handled by kernel middleware');
|
||||
|
||||
$edit = [
|
||||
'content' => $this->randomString(),
|
||||
'status' => 418,
|
||||
];
|
||||
$this->drupalPostForm('form-test/response', $edit, 'Submit');
|
||||
$content = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertResponse(418);
|
||||
$this->assertIdentical($edit['content'], $content, 'Response content matches');
|
||||
$this->assertIdentical('invoked', $this->drupalGetHeader('X-Form-Test-Response-Event'), 'Response handled by kernel response subscriber');
|
||||
$this->assertIdentical('invoked', $this->drupalGetHeader('X-Form-Test-Stack-Middleware'), 'Response handled by kernel middleware');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests proper removal of submitted form values using
|
||||
* \Drupal\Core\Form\FormState::cleanValues() when having forms with elements
|
||||
* containing buttons like "managed_file".
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class StateValuesCleanAdvancedTest extends BrowserTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['file', 'form_test'];
|
||||
|
||||
/**
|
||||
* An image file path for uploading.
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Form\FormState::cleanValues().
|
||||
*/
|
||||
public function testFormStateValuesCleanAdvanced() {
|
||||
|
||||
// Get an image for uploading.
|
||||
$image_files = $this->drupalGetTestFiles('image');
|
||||
$this->image = current($image_files);
|
||||
|
||||
// Check if the physical file is there.
|
||||
$this->assertTrue(is_file($this->image->uri), "The image file we're going to upload exists.");
|
||||
|
||||
// "Browse" for the desired file.
|
||||
$edit = ['files[image]' => \Drupal::service('file_system')->realpath($this->image->uri)];
|
||||
|
||||
// Post the form.
|
||||
$this->drupalPostForm('form_test/form-state-values-clean-advanced', $edit, t('Submit'));
|
||||
|
||||
// Expecting a 200 HTTP code.
|
||||
$this->assertResponse(200, 'Received a 200 response for posted test file.');
|
||||
$this->assertRaw(t('You WIN!'), 'Found the success message.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests proper removal of submitted form values using
|
||||
* \Drupal\Core\Form\FormState::cleanValues().
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class StateValuesCleanTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Form\FormState::cleanValues().
|
||||
*/
|
||||
public function testFormStateValuesClean() {
|
||||
$this->drupalPostForm('form_test/form-state-values-clean', [], t('Submit'));
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
|
||||
// Setup the expected result.
|
||||
$result = [
|
||||
'beer' => 1000,
|
||||
'baz' => ['beer' => 2000],
|
||||
];
|
||||
|
||||
// Verify that all internal Form API elements were removed.
|
||||
$this->assertFalse(isset($values['form_id']), format_string('%element was removed.', ['%element' => 'form_id']));
|
||||
$this->assertFalse(isset($values['form_token']), format_string('%element was removed.', ['%element' => 'form_token']));
|
||||
$this->assertFalse(isset($values['form_build_id']), format_string('%element was removed.', ['%element' => 'form_build_id']));
|
||||
$this->assertFalse(isset($values['op']), format_string('%element was removed.', ['%element' => 'op']));
|
||||
|
||||
// Verify that all buttons were removed.
|
||||
$this->assertFalse(isset($values['foo']), format_string('%element was removed.', ['%element' => 'foo']));
|
||||
$this->assertFalse(isset($values['bar']), format_string('%element was removed.', ['%element' => 'bar']));
|
||||
$this->assertFalse(isset($values['baz']['foo']), format_string('%element was removed.', ['%element' => 'foo']));
|
||||
$this->assertFalse(isset($values['baz']['baz']), format_string('%element was removed.', ['%element' => 'baz']));
|
||||
|
||||
// Verify values manually added for cleaning were removed.
|
||||
$this->assertFalse(isset($values['wine']), new FormattableMarkup('%element was removed.', ['%element' => 'wine']));
|
||||
|
||||
// Verify that nested form value still exists.
|
||||
$this->assertTrue(isset($values['baz']['beer']), 'Nested form value still exists.');
|
||||
|
||||
// Verify that actual form values equal resulting form values.
|
||||
$this->assertEqual($values, $result, 'Expected form values equal actual form values.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a stub form for testing purposes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StubForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The form array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The form ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $formId;
|
||||
|
||||
/**
|
||||
* Constructs a StubForm.
|
||||
*
|
||||
* @param string $form_id
|
||||
* The form ID.
|
||||
* @param array $form
|
||||
* The form array.
|
||||
*/
|
||||
public function __construct($form_id, $form) {
|
||||
$this->formId = $form_id;
|
||||
$this->form = $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
$this->formId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the SystemConfigFormTestBase class.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class SystemConfigFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests the SystemConfigFormTestBase class.
|
||||
*/
|
||||
public function testSystemConfigForm() {
|
||||
$this->drupalGet('form-test/system-config-form');
|
||||
$element = $this->xpath('//div[@id = :id]/input[contains(@class, :class)]', [':id' => 'edit-actions', ':class' => 'button--primary']);
|
||||
$this->assertTrue($element, 'The primary action submit button was found.');
|
||||
$this->drupalPostForm(NULL, [], t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the form API URL element.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class UrlTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Tests that #type 'url' fields are properly validated and trimmed.
|
||||
*/
|
||||
public function testFormUrl() {
|
||||
$edit = [];
|
||||
$edit['url'] = 'http://';
|
||||
$edit['url_required'] = ' ';
|
||||
$this->drupalPostForm('form-test/url', $edit, 'Submit');
|
||||
$this->assertRaw(t('The URL %url is not valid.', ['%url' => 'http://']));
|
||||
$this->assertRaw(t('@name field is required.', ['@name' => 'Required URL']));
|
||||
|
||||
$edit = [];
|
||||
$edit['url'] = "\n";
|
||||
$edit['url_required'] = 'http://example.com/ ';
|
||||
$this->drupalPostForm('form-test/url', $edit, 'Submit');
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertIdentical($values['url'], '');
|
||||
$this->assertEqual($values['url_required'], 'http://example.com/');
|
||||
|
||||
$edit = [];
|
||||
$edit['url'] = 'http://foo.bar.example.com/';
|
||||
$edit['url_required'] = 'https://www.drupal.org/node/1174630?page=0&foo=bar#new';
|
||||
$this->drupalPostForm('form-test/url', $edit, 'Submit');
|
||||
$values = Json::decode($this->getSession()->getPage()->getContent());
|
||||
$this->assertEqual($values['url'], $edit['url']);
|
||||
$this->assertEqual($values['url_required'], $edit['url_required']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Form;
|
||||
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests form processing and alteration via form validation handlers.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class ValidationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['form_test'];
|
||||
|
||||
/**
|
||||
* Tests #element_validate and #validate.
|
||||
*/
|
||||
public function testValidate() {
|
||||
$this->drupalGet('form-test/validate');
|
||||
// Verify that #element_validate handlers can alter the form and submitted
|
||||
// form values.
|
||||
$edit = [
|
||||
'name' => 'element_validate',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertFieldByName('name', '#value changed by #element_validate', 'Form element #value was altered.');
|
||||
$this->assertText('Name value: value changed by setValueForElement() in #element_validate', 'Form element value in $form_state was altered.');
|
||||
|
||||
// Verify that #validate handlers can alter the form and submitted
|
||||
// form values.
|
||||
$edit = [
|
||||
'name' => 'validate',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertFieldByName('name', '#value changed by #validate', 'Form element #value was altered.');
|
||||
$this->assertText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was altered.');
|
||||
|
||||
// Verify that #element_validate handlers can make form elements
|
||||
// inaccessible, but values persist.
|
||||
$edit = [
|
||||
'name' => 'element_validate_access',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertNoFieldByName('name', 'Form element was hidden.');
|
||||
$this->assertText('Name value: element_validate_access', 'Value for inaccessible form element exists.');
|
||||
|
||||
// Verify that value for inaccessible form element persists.
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
$this->assertNoFieldByName('name', 'Form element was hidden.');
|
||||
$this->assertText('Name value: element_validate_access', 'Value for inaccessible form element exists.');
|
||||
|
||||
// Verify that #validate handlers don't run if the CSRF token is invalid.
|
||||
$this->drupalLogin($this->drupalCreateUser());
|
||||
$this->drupalGet('form-test/validate');
|
||||
// $this->assertSession()->fieldExists() does not recognize hidden fields,
|
||||
// which breaks $this->drupalPostForm() if we try to change the value of a
|
||||
// hidden field such as form_token.
|
||||
$this->assertSession()
|
||||
->elementExists('css', 'input[name="form_token"]')
|
||||
->setValue('invalid_token');
|
||||
$this->drupalPostForm(NULL, ['name' => 'validate'], 'Save');
|
||||
$this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
|
||||
$this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.');
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a form with a disabled CSRF token can be validated.
|
||||
*/
|
||||
public function testDisabledToken() {
|
||||
$this->drupalPostForm('form-test/validate-no-token', [], 'Save');
|
||||
$this->assertText('The form_test_validate_no_token form has been submitted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests partial form validation through #limit_validation_errors.
|
||||
*/
|
||||
public function testValidateLimitErrors() {
|
||||
$edit = [
|
||||
'test' => 'invalid',
|
||||
'test_numeric_index[0]' => 'invalid',
|
||||
'test_substring[foo]' => 'invalid',
|
||||
];
|
||||
$path = 'form-test/limit-validation-errors';
|
||||
|
||||
// Render the form, and verify that the buttons with limited server-side
|
||||
// validation have the proper 'formnovalidate' attribute (to prevent
|
||||
// client-side validation by the browser).
|
||||
$this->drupalGet($path);
|
||||
$expected = 'formnovalidate';
|
||||
foreach (['partial', 'partial-numeric-index', 'substring'] as $type) {
|
||||
$element = $this->xpath('//input[@id=:id and @formnovalidate=:expected]', [
|
||||
':id' => 'edit-' . $type,
|
||||
':expected' => $expected,
|
||||
]);
|
||||
$this->assertTrue(!empty($element), format_string('The @type button has the proper formnovalidate attribute.', ['@type' => $type]));
|
||||
}
|
||||
// The button with full server-side validation should not have the
|
||||
// 'formnovalidate' attribute.
|
||||
$element = $this->xpath('//input[@id=:id and not(@formnovalidate)]', [
|
||||
':id' => 'edit-full',
|
||||
]);
|
||||
$this->assertTrue(!empty($element), 'The button with full server-side validation does not have the formnovalidate attribute.');
|
||||
|
||||
// Submit the form by pressing the 'Partial validate' button (uses
|
||||
// #limit_validation_errors) and ensure that the title field is not
|
||||
// validated, but the #element_validate handler for the 'test' field
|
||||
// is triggered.
|
||||
$this->drupalPostForm($path, $edit, t('Partial validate'));
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => 'Title']));
|
||||
$this->assertText('Test element is invalid');
|
||||
|
||||
// Edge case of #limit_validation_errors containing numeric indexes: same
|
||||
// thing with the 'Partial validate (numeric index)' button and the
|
||||
// 'test_numeric_index' field.
|
||||
$this->drupalPostForm($path, $edit, t('Partial validate (numeric index)'));
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => 'Title']));
|
||||
$this->assertText('Test (numeric index) element is invalid');
|
||||
|
||||
// Ensure something like 'foobar' isn't considered "inside" 'foo'.
|
||||
$this->drupalPostForm($path, $edit, t('Partial validate (substring)'));
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => 'Title']));
|
||||
$this->assertText('Test (substring) foo element is invalid');
|
||||
|
||||
// Ensure not validated values are not available to submit handlers.
|
||||
$this->drupalPostForm($path, ['title' => '', 'test' => 'valid'], t('Partial validate'));
|
||||
$this->assertText('Only validated values appear in the form values.');
|
||||
|
||||
// Now test full form validation and ensure that the #element_validate
|
||||
// handler is still triggered.
|
||||
$this->drupalPostForm($path, $edit, t('Full validate'));
|
||||
$this->assertText(t('@name field is required.', ['@name' => 'Title']));
|
||||
$this->assertText('Test element is invalid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests #pattern validation.
|
||||
*/
|
||||
public function testPatternValidation() {
|
||||
$textfield_error = t('%name field is not in the right format.', ['%name' => 'One digit followed by lowercase letters']);
|
||||
$tel_error = t('%name field is not in the right format.', ['%name' => 'Everything except numbers']);
|
||||
$password_error = t('%name field is not in the right format.', ['%name' => 'Password']);
|
||||
|
||||
// Invalid textfield, valid tel.
|
||||
$edit = [
|
||||
'textfield' => 'invalid',
|
||||
'tel' => 'valid',
|
||||
];
|
||||
$this->drupalPostForm('form-test/pattern', $edit, 'Submit');
|
||||
$this->assertRaw($textfield_error);
|
||||
$this->assertNoRaw($tel_error);
|
||||
$this->assertNoRaw($password_error);
|
||||
|
||||
// Valid textfield, invalid tel, valid password.
|
||||
$edit = [
|
||||
'textfield' => '7seven',
|
||||
'tel' => '818937',
|
||||
'password' => '0100110',
|
||||
];
|
||||
$this->drupalPostForm('form-test/pattern', $edit, 'Submit');
|
||||
$this->assertNoRaw($textfield_error);
|
||||
$this->assertRaw($tel_error);
|
||||
$this->assertNoRaw($password_error);
|
||||
|
||||
// Non required fields are not validated if empty.
|
||||
$edit = [
|
||||
'textfield' => '',
|
||||
'tel' => '',
|
||||
];
|
||||
$this->drupalPostForm('form-test/pattern', $edit, 'Submit');
|
||||
$this->assertNoRaw($textfield_error);
|
||||
$this->assertNoRaw($tel_error);
|
||||
$this->assertNoRaw($password_error);
|
||||
|
||||
// Invalid password.
|
||||
$edit = [
|
||||
'password' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalPostForm('form-test/pattern', $edit, 'Submit');
|
||||
$this->assertNoRaw($textfield_error);
|
||||
$this->assertNoRaw($tel_error);
|
||||
$this->assertRaw($password_error);
|
||||
|
||||
// The pattern attribute overrides #pattern and is not validated on the
|
||||
// server side.
|
||||
$edit = [
|
||||
'textfield' => '',
|
||||
'tel' => '',
|
||||
'url' => 'http://www.example.com/',
|
||||
];
|
||||
$this->drupalPostForm('form-test/pattern', $edit, 'Submit');
|
||||
$this->assertNoRaw(t('%name field is not in the right format.', ['%name' => 'Client side validation']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests #required with custom validation errors.
|
||||
*
|
||||
* @see \Drupal\form_test\Form\FormTestValidateRequiredForm
|
||||
*/
|
||||
public function testCustomRequiredError() {
|
||||
$form = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestValidateRequiredForm');
|
||||
|
||||
// Verify that a custom #required error can be set.
|
||||
$edit = [];
|
||||
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
||||
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#required_error'])) {
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
$this->assertText($form[$key]['#required_error']);
|
||||
}
|
||||
elseif (isset($form[$key]['#form_test_required_error'])) {
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
$this->assertText($form[$key]['#form_test_required_error']);
|
||||
}
|
||||
}
|
||||
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
|
||||
|
||||
// Verify that no custom validation error appears with valid values.
|
||||
$edit = [
|
||||
'textfield' => $this->randomString(),
|
||||
'checkboxes[foo]' => TRUE,
|
||||
'select' => 'foo',
|
||||
];
|
||||
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
||||
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#required_error'])) {
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
$this->assertNoText($form[$key]['#required_error']);
|
||||
}
|
||||
elseif (isset($form[$key]['#form_test_required_error'])) {
|
||||
$this->assertNoText(t('@name field is required.', ['@name' => $form[$key]['#title']]));
|
||||
$this->assertNoText($form[$key]['#form_test_required_error']);
|
||||
}
|
||||
}
|
||||
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase;
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class ActionHalJsonAnonTest extends ActionResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase;
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class ActionHalJsonBasicAuthTest extends ActionResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal', 'basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase;
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class ActionHalJsonCookieTest extends ActionResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\system\Functional\Rest\MenuResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class MenuHalJsonAnonTest extends MenuResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\system\Functional\Rest\MenuResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class MenuHalJsonBasicAuthTest extends MenuResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal', 'basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\system\Functional\Rest\MenuResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class MenuHalJsonCookieTest extends MenuResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
namespace Drupal\Tests\system\Functional\Mail;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Mail\MailFormatHelper;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
|
@ -14,6 +13,7 @@ use Drupal\Tests\BrowserTestBase;
|
|||
* @group Mail
|
||||
*/
|
||||
class HtmlToTextTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Converts a string to its PHP source equivalent for display in test messages.
|
||||
*
|
||||
|
|
@ -48,7 +48,7 @@ class HtmlToTextTest extends BrowserTestBase {
|
|||
* \Drupal\Core\Mail\MailFormatHelper::htmlToText().
|
||||
*/
|
||||
protected function assertHtmlToText($html, $text, $message, $allowed_tags = NULL) {
|
||||
preg_match_all('/<([a-z0-6]+)/', Unicode::strtolower($html), $matches);
|
||||
preg_match_all('/<([a-z0-6]+)/', mb_strtolower($html), $matches);
|
||||
$tested_tags = implode(', ', array_unique($matches[1]));
|
||||
$message .= ' (' . $tested_tags . ')';
|
||||
$result = MailFormatHelper::htmlToText($html, $allowed_tags);
|
||||
|
|
@ -241,8 +241,8 @@ class HtmlToTextTest extends BrowserTestBase {
|
|||
if (!$pass) {
|
||||
$this->verbose($this->stringToHtml($output));
|
||||
}
|
||||
$output_upper = Unicode::strtoupper($output);
|
||||
$upper_input = Unicode::strtoupper($input);
|
||||
$output_upper = mb_strtoupper($output);
|
||||
$upper_input = mb_strtoupper($input);
|
||||
$upper_output = MailFormatHelper::htmlToText($upper_input);
|
||||
$pass = $this->assertEqual(
|
||||
$upper_output,
|
||||
|
|
@ -347,8 +347,8 @@ class HtmlToTextTest extends BrowserTestBase {
|
|||
|
||||
$maximum_line_length = 0;
|
||||
foreach (explode($eol, $output) as $line) {
|
||||
// We must use strlen() rather than Unicode::strlen() in order to count
|
||||
// octets rather than characters.
|
||||
// We must use strlen() rather than mb_strlen() in order to count octets
|
||||
// rather than characters.
|
||||
$maximum_line_length = max($maximum_line_length, strlen($line . $eol));
|
||||
}
|
||||
$verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
namespace Drupal\Tests\system\Functional\Mail;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Mail\MailFormatHelper;
|
||||
use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure;
|
||||
|
||||
|
|
@ -18,7 +24,7 @@ class MailTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['simpletest', 'system_mail_failure_test'];
|
||||
public static $modules = ['simpletest', 'system_mail_failure_test', 'mail_html_test', 'file', 'image'];
|
||||
|
||||
/**
|
||||
* Assert that the pluggable mail system is functional.
|
||||
|
|
@ -90,13 +96,191 @@ class MailTest extends BrowserTestBase {
|
|||
$this->assertEqual($reply_email, $sent_message['headers']['Reply-to'], 'Message reply-to headers are set.');
|
||||
$this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
|
||||
|
||||
// Test that long site names containing characters that need MIME encoding
|
||||
// works as expected.
|
||||
$this->config('system.site')->set('name', 'Drépal this is a very long test sentence to test what happens with very long site names')->save();
|
||||
// Send an email and check that the From-header contains the site name.
|
||||
\Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language);
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
$this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.');
|
||||
$this->assertEquals('=?UTF-8?B?RHLDqXBhbCB0aGlzIGlzIGEgdmVyeSBsb25nIHRlc3Qgc2VudGVuY2UgdG8gdGU=?= <simpletest@example.com>', $sent_message['headers']['From'], 'From header is correctly encoded.');
|
||||
$this->assertEquals('Drépal this is a very long test sentence to te <simpletest@example.com>', Unicode::mimeHeaderDecode($sent_message['headers']['From']), 'From header is correctly encoded.');
|
||||
$this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
|
||||
$this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that relative paths in mails are converted into absolute URLs.
|
||||
*/
|
||||
public function testConvertRelativeUrlsIntoAbsolute() {
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
|
||||
// Use the HTML compatible state system collector mail backend.
|
||||
$this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save();
|
||||
|
||||
// Fetch the hostname and port for matching against.
|
||||
$http_host = \Drupal::request()->getSchemeAndHttpHost();
|
||||
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
// One random tag name.
|
||||
$tag_name = strtolower($random->name(8, TRUE));
|
||||
|
||||
// Test root relative urls.
|
||||
foreach (['href', 'src'] as $attribute) {
|
||||
// Reset the state variable that holds sent messages.
|
||||
\Drupal::state()->set('system.test_mail_collector', []);
|
||||
|
||||
$html = "<$tag_name $attribute=\"/root-relative\">root relative url in mail test</$tag_name>";
|
||||
$expected_html = "<$tag_name $attribute=\"{$http_host}/root-relative\">root relative url in mail test</$tag_name>";
|
||||
|
||||
// Prepare render array.
|
||||
$render = ['#markup' => Markup::create($html)];
|
||||
|
||||
// Send a test message that simpletest_mail_alter should cancel.
|
||||
\Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
|
||||
// Retrieve sent message.
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
|
||||
// Wrap the expected HTML and assert.
|
||||
$expected_html = MailFormatHelper::wrapMail($expected_html);
|
||||
$this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
|
||||
}
|
||||
|
||||
// Test protocol relative urls.
|
||||
foreach (['href', 'src'] as $attribute) {
|
||||
// Reset the state variable that holds sent messages.
|
||||
\Drupal::state()->set('system.test_mail_collector', []);
|
||||
|
||||
$html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test</$tag_name>";
|
||||
$expected_html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test</$tag_name>";
|
||||
|
||||
// Prepare render array.
|
||||
$render = ['#markup' => Markup::create($html)];
|
||||
|
||||
// Send a test message that simpletest_mail_alter should cancel.
|
||||
\Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
|
||||
// Retrieve sent message.
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
|
||||
// Wrap the expected HTML and assert.
|
||||
$expected_html = MailFormatHelper::wrapMail($expected_html);
|
||||
$this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
|
||||
}
|
||||
|
||||
// Test absolute urls.
|
||||
foreach (['href', 'src'] as $attribute) {
|
||||
// Reset the state variable that holds sent messages.
|
||||
\Drupal::state()->set('system.test_mail_collector', []);
|
||||
|
||||
$html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test</$tag_name>";
|
||||
$expected_html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test</$tag_name>";
|
||||
|
||||
// Prepare render array.
|
||||
$render = ['#markup' => Markup::create($html)];
|
||||
|
||||
// Send a test message that simpletest_mail_alter should cancel.
|
||||
\Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
|
||||
// Retrieve sent message.
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
|
||||
// Wrap the expected HTML and assert.
|
||||
$expected_html = MailFormatHelper::wrapMail($expected_html);
|
||||
$this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that mails built from render arrays contain absolute paths.
|
||||
*
|
||||
* By default Drupal uses relative paths for images and links. When sending
|
||||
* emails, absolute paths should be used instead.
|
||||
*/
|
||||
public function testRenderedElementsUseAbsolutePaths() {
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
|
||||
// Use the HTML compatible state system collector mail backend.
|
||||
$this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save();
|
||||
|
||||
// Fetch the hostname and port for matching against.
|
||||
$http_host = \Drupal::request()->getSchemeAndHttpHost();
|
||||
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
$image_name = $random->name();
|
||||
|
||||
// Create an image file.
|
||||
$file = File::create(['uri' => "public://{$image_name}.png", 'filename' => "{$image_name}.png"]);
|
||||
$file->save();
|
||||
|
||||
$base_path = base_path();
|
||||
|
||||
$path_pairs = [
|
||||
'root relative' => [$file->getFileUri(), "{$http_host}{$base_path}{$this->publicFilesDirectory}/{$image_name}.png"],
|
||||
'protocol relative' => ['//example.com/image.png', '//example.com/image.png'],
|
||||
'absolute' => ['http://example.com/image.png', 'http://example.com/image.png'],
|
||||
];
|
||||
|
||||
// Test images.
|
||||
foreach ($path_pairs as $test_type => $paths) {
|
||||
list($input_path, $expected_path) = $paths;
|
||||
|
||||
// Reset the state variable that holds sent messages.
|
||||
\Drupal::state()->set('system.test_mail_collector', []);
|
||||
|
||||
// Build the render array.
|
||||
$render = [
|
||||
'#theme' => 'image',
|
||||
'#uri' => $input_path,
|
||||
];
|
||||
$expected_html = "<img src=\"$expected_path\" alt=\"\" />";
|
||||
|
||||
// Send a test message that simpletest_mail_alter should cancel.
|
||||
\Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
|
||||
// Retrieve sent message.
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
|
||||
// Wrap the expected HTML and assert.
|
||||
$expected_html = MailFormatHelper::wrapMail($expected_html);
|
||||
$this->assertSame($expected_html, $sent_message['body'], "Asserting that {$test_type} paths are converted properly.");
|
||||
}
|
||||
|
||||
// Test links.
|
||||
$path_pairs = [
|
||||
'root relative' => [Url::fromUserInput('/path/to/something'), "{$http_host}{$base_path}path/to/something"],
|
||||
'protocol relative' => [Url::fromUri('//example.com/image.png'), '//example.com/image.png'],
|
||||
'absolute' => [Url::fromUri('http://example.com/image.png'), 'http://example.com/image.png'],
|
||||
];
|
||||
|
||||
foreach ($path_pairs as $paths) {
|
||||
list($input_path, $expected_path) = $paths;
|
||||
|
||||
// Reset the state variable that holds sent messages.
|
||||
\Drupal::state()->set('system.test_mail_collector', []);
|
||||
|
||||
// Build the render array.
|
||||
$render = [
|
||||
'#title' => 'Link',
|
||||
'#type' => 'link',
|
||||
'#url' => $input_path,
|
||||
];
|
||||
$expected_html = "<a href=\"$expected_path\">Link</a>";
|
||||
|
||||
// Send a test message that simpletest_mail_alter should cancel.
|
||||
\Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
|
||||
// Retrieve sent message.
|
||||
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
|
||||
$sent_message = end($captured_emails);
|
||||
|
||||
// Wrap the expected HTML and assert.
|
||||
$expected_html = MailFormatHelper::wrapMail($expected_html);
|
||||
$this->assertSame($expected_html, $sent_message['body']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides test assertions for verifying breadcrumbs.
|
||||
*/
|
||||
trait AssertBreadcrumbTrait {
|
||||
|
||||
use AssertMenuActiveTrailTrait;
|
||||
|
||||
/**
|
||||
* Assert that a given path shows certain breadcrumb links.
|
||||
*
|
||||
* @param \Drupal\Core\Url|string $goto
|
||||
* (optional) A path or URL to pass to
|
||||
* Drupal\simpletest\WebTestBase::drupalGet().
|
||||
* @param array $trail
|
||||
* An associative array whose keys are expected breadcrumb link paths and
|
||||
* whose values are expected breadcrumb link texts (not sanitized).
|
||||
* @param string $page_title
|
||||
* (optional) A page title to additionally assert via
|
||||
* Drupal\simpletest\WebTestBase::assertTitle(). Without site name suffix.
|
||||
* @param array $tree
|
||||
* (optional) An associative array whose keys are link paths and whose
|
||||
* values are link titles (not sanitized) of an expected active trail in a
|
||||
* menu tree output on the page.
|
||||
* @param $last_active
|
||||
* (optional) Whether the last link in $tree is expected to be active (TRUE)
|
||||
* or just to be in the active trail (FALSE).
|
||||
*/
|
||||
protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, array $tree = [], $last_active = TRUE) {
|
||||
if (isset($goto)) {
|
||||
$this->drupalGet($goto);
|
||||
}
|
||||
$this->assertBreadcrumbParts($trail);
|
||||
|
||||
// Additionally assert page title, if given.
|
||||
if (isset($page_title)) {
|
||||
$this->assertTitle(strtr('@title | Drupal', ['@title' => $page_title]));
|
||||
}
|
||||
|
||||
// Additionally assert active trail in a menu tree output, if given.
|
||||
if ($tree) {
|
||||
$this->assertMenuActiveTrail($tree, $last_active);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a trail exists in the internal browser.
|
||||
*
|
||||
* @param array $trail
|
||||
* An associative array whose keys are expected breadcrumb link paths and
|
||||
* whose values are expected breadcrumb link texts (not sanitized).
|
||||
*/
|
||||
protected function assertBreadcrumbParts($trail) {
|
||||
// Compare paths with actual breadcrumb.
|
||||
$parts = $this->getBreadcrumbParts();
|
||||
$pass = TRUE;
|
||||
// There may be more than one breadcrumb on the page. If $trail is empty
|
||||
// this test would go into an infinite loop, so we need to check that too.
|
||||
while ($trail && !empty($parts)) {
|
||||
foreach ($trail as $path => $title) {
|
||||
// If the path is empty, generate the path from the <front> route. If
|
||||
// the path does not start with a leading slash, then run it through
|
||||
// Url::fromUri('base:')->toString() to get the correct base
|
||||
// prepended.
|
||||
if ($path == '') {
|
||||
$url = Url::fromRoute('<front>')->toString();
|
||||
}
|
||||
elseif ($path[0] != '/') {
|
||||
$url = Url::fromUri('base:' . $path)->toString();
|
||||
}
|
||||
else {
|
||||
$url = $path;
|
||||
}
|
||||
$part = array_shift($parts);
|
||||
$pass = ($pass && $part['href'] === $url && $part['text'] === Html::escape($title));
|
||||
}
|
||||
}
|
||||
// No parts must be left, or an expected "Home" will always pass.
|
||||
$pass = ($pass && empty($parts));
|
||||
|
||||
$this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', [
|
||||
'%parts' => implode(' » ', $trail),
|
||||
'@path' => $this->getUrl(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb contents of the current page in the internal browser.
|
||||
*/
|
||||
protected function getBreadcrumbParts() {
|
||||
$parts = [];
|
||||
$elements = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
|
||||
if (!empty($elements)) {
|
||||
foreach ($elements as $element) {
|
||||
$parts[] = [
|
||||
'text' => $element->getText(),
|
||||
'href' => $element->getAttribute('href'),
|
||||
'title' => $element->getAttribute('title'),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $parts;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides test assertions for verifying the active menu trail.
|
||||
*/
|
||||
trait AssertMenuActiveTrailTrait {
|
||||
|
||||
/**
|
||||
* Assert that active trail exists in a menu tree output.
|
||||
*
|
||||
* @param array $tree
|
||||
* An associative array whose keys are link paths and whose
|
||||
* values are link titles (not sanitized) of an expected active trail in a
|
||||
* menu tree output on the page.
|
||||
* @param bool $last_active
|
||||
* Whether the last link in $tree is expected to be active (TRUE)
|
||||
* or just to be in the active trail (FALSE).
|
||||
*/
|
||||
protected function assertMenuActiveTrail($tree, $last_active) {
|
||||
end($tree);
|
||||
$active_link_path = key($tree);
|
||||
$active_link_title = array_pop($tree);
|
||||
$xpath = '';
|
||||
if ($tree) {
|
||||
$i = 0;
|
||||
foreach ($tree as $link_path => $link_title) {
|
||||
$part_xpath = (!$i ? '//' : '/following-sibling::ul/descendant::');
|
||||
$part_xpath .= 'li[contains(@class, :class)]/a[contains(@href, :href) and contains(text(), :title)]';
|
||||
$part_args = [
|
||||
':class' => 'menu-item--active-trail',
|
||||
':href' => Url::fromUri('base:' . $link_path)->toString(),
|
||||
':title' => $link_title,
|
||||
];
|
||||
$xpath .= $this->buildXPathQuery($part_xpath, $part_args);
|
||||
$i++;
|
||||
}
|
||||
$elements = $this->xpath($xpath);
|
||||
$this->assertTrue(!empty($elements), 'Active trail to current page was found in menu tree.');
|
||||
|
||||
// Append prefix for active link asserted below.
|
||||
$xpath .= '/following-sibling::ul/descendant::';
|
||||
}
|
||||
else {
|
||||
$xpath .= '//';
|
||||
}
|
||||
$xpath_last_active = ($last_active ? 'and contains(@class, :class-active)' : '');
|
||||
$xpath .= 'li[contains(@class, :class-trail)]/a[contains(@href, :href) ' . $xpath_last_active . 'and contains(text(), :title)]';
|
||||
$args = [
|
||||
':class-trail' => 'menu-item--active-trail',
|
||||
':class-active' => 'is-active',
|
||||
':href' => Url::fromUri('base:' . $active_link_path)->toString(),
|
||||
':title' => $active_link_title,
|
||||
];
|
||||
$elements = $this->xpath($xpath, $args);
|
||||
$this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', [
|
||||
'%title' => $active_link_title,
|
||||
'%tree' => implode(' » ', $tree),
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests breadcrumbs functionality.
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class BreadcrumbFrontCacheContextsTest extends BrowserTestBase {
|
||||
|
||||
use AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'block',
|
||||
'node',
|
||||
'path',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* A test node with path alias.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $nodeWithAlias;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
$user = $this->drupalCreateUser();
|
||||
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'page',
|
||||
]);
|
||||
|
||||
// Create a node for front page.
|
||||
$node_front = $this->drupalCreateNode([
|
||||
'uid' => $user->id(),
|
||||
]);
|
||||
|
||||
// Create a node with a random alias.
|
||||
$this->nodeWithAlias = $this->drupalCreateNode([
|
||||
'uid' => $user->id(),
|
||||
'type' => 'page',
|
||||
'path' => '/' . $this->randomMachineName(),
|
||||
]);
|
||||
|
||||
// Configure 'node' as front page.
|
||||
$this->config('system.site')
|
||||
->set('page.front', '/node/' . $node_front->id())
|
||||
->save();
|
||||
|
||||
\Drupal::cache('render')->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that breadcrumb markup get the right cache contexts.
|
||||
*
|
||||
* Checking that the breadcrumb will be printed on node canonical routes even
|
||||
* if it was rendered for the <front> page first.
|
||||
*/
|
||||
public function testBreadcrumbsFrontPageCache() {
|
||||
// Hit front page first as anonymous user with 'cold' render cache.
|
||||
$this->drupalGet('<front>');
|
||||
$web_assert = $this->assertSession();
|
||||
// Verify that no breadcrumb block presents.
|
||||
$web_assert->elementNotExists('css', '.block-system-breadcrumb-block');
|
||||
|
||||
// Verify that breadcrumb appears correctly for the test content
|
||||
// (which is not set as front page).
|
||||
$this->drupalGet($this->nodeWithAlias->path->alias);
|
||||
$breadcrumbs = $this->assertSession()->elementExists('css', '.block-system-breadcrumb-block');
|
||||
$crumbs = $breadcrumbs->findAll('css', 'ol li');
|
||||
$this->assertTrue(count($crumbs) === 1);
|
||||
$this->assertTrue($crumbs[0]->getText() === 'Home');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,384 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests breadcrumbs functionality.
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class BreadcrumbTest extends BrowserTestBase {
|
||||
|
||||
use AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['menu_test', 'block'];
|
||||
|
||||
/**
|
||||
* An administrative user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A regular user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* Test paths in the Standard profile.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'standard';
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$perms = array_keys(\Drupal::service('user.permissions')->getPermissions());
|
||||
$this->adminUser = $this->drupalCreateUser($perms);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// This test puts menu links in the Tools menu and then tests for their
|
||||
// presence on the page, so we need to ensure that the Tools block will be
|
||||
// displayed in the admin theme.
|
||||
$this->drupalPlaceBlock('system_menu_block:tools', [
|
||||
'region' => 'content',
|
||||
'theme' => $this->config('system.theme')->get('admin'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests breadcrumbs on node and administrative paths.
|
||||
*/
|
||||
public function testBreadCrumbs() {
|
||||
// Prepare common base breadcrumb elements.
|
||||
$home = ['' => 'Home'];
|
||||
$admin = $home + ['admin' => t('Administration')];
|
||||
$config = $admin + ['admin/config' => t('Configuration')];
|
||||
$type = 'article';
|
||||
|
||||
// Verify Taxonomy administration breadcrumbs.
|
||||
$trail = $admin + [
|
||||
'admin/structure' => t('Structure'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/taxonomy', $trail);
|
||||
|
||||
$trail += [
|
||||
'admin/structure/taxonomy' => t('Taxonomy'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/taxonomy/manage/tags', $trail);
|
||||
$trail += [
|
||||
'admin/structure/taxonomy/manage/tags' => t('Edit Tags'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/taxonomy/manage/tags/overview', $trail);
|
||||
$this->assertBreadcrumb('admin/structure/taxonomy/manage/tags/add', $trail);
|
||||
|
||||
// Verify Menu administration breadcrumbs.
|
||||
$trail = $admin + [
|
||||
'admin/structure' => t('Structure'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/menu', $trail);
|
||||
|
||||
$trail += [
|
||||
'admin/structure/menu' => t('Menus'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/menu/manage/tools', $trail);
|
||||
|
||||
$trail += [
|
||||
'admin/structure/menu/manage/tools' => t('Tools'),
|
||||
];
|
||||
$this->assertBreadcrumb("admin/structure/menu/link/node.add_page/edit", $trail);
|
||||
$this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail);
|
||||
|
||||
// Verify Node administration breadcrumbs.
|
||||
$trail = $admin + [
|
||||
'admin/structure' => t('Structure'),
|
||||
'admin/structure/types' => t('Content types'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/structure/types/add', $trail);
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type", $trail);
|
||||
$trail += [
|
||||
"admin/structure/types/manage/$type" => t('Article'),
|
||||
];
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type/fields", $trail);
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type/display", $trail);
|
||||
$trail_teaser = $trail + [
|
||||
"admin/structure/types/manage/$type/display" => t('Manage display'),
|
||||
];
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser);
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail);
|
||||
$trail += [
|
||||
"admin/structure/types/manage/$type/fields" => t('Manage fields'),
|
||||
];
|
||||
$this->assertBreadcrumb("admin/structure/types/manage/$type/fields/node.$type.body", $trail);
|
||||
|
||||
// Verify Filter text format administration breadcrumbs.
|
||||
$filter_formats = filter_formats();
|
||||
$format = reset($filter_formats);
|
||||
$format_id = $format->id();
|
||||
$trail = $config + [
|
||||
'admin/config/content' => t('Content authoring'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/config/content/formats', $trail);
|
||||
|
||||
$trail += [
|
||||
'admin/config/content/formats' => t('Text formats and editors'),
|
||||
];
|
||||
$this->assertBreadcrumb('admin/config/content/formats/add', $trail);
|
||||
$this->assertBreadcrumb("admin/config/content/formats/manage/$format_id", $trail);
|
||||
// @todo Remove this part once we have a _title_callback, see
|
||||
// https://www.drupal.org/node/2076085.
|
||||
$trail += [
|
||||
"admin/config/content/formats/manage/$format_id" => $format->label(),
|
||||
];
|
||||
$this->assertBreadcrumb("admin/config/content/formats/manage/$format_id/disable", $trail);
|
||||
|
||||
// Verify node breadcrumbs (without menu link).
|
||||
$node1 = $this->drupalCreateNode();
|
||||
$nid1 = $node1->id();
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb("node/$nid1", $trail);
|
||||
// Also verify that the node does not appear elsewhere (e.g., menu trees).
|
||||
$this->assertNoLink($node1->getTitle());
|
||||
// Also verify that the node does not appear elsewhere (e.g., menu trees).
|
||||
$this->assertNoLink($node1->getTitle());
|
||||
|
||||
$trail += [
|
||||
"node/$nid1" => $node1->getTitle(),
|
||||
];
|
||||
$this->assertBreadcrumb("node/$nid1/edit", $trail);
|
||||
|
||||
// Verify that breadcrumb on node listing page contains "Home" only.
|
||||
$trail = [];
|
||||
$this->assertBreadcrumb('node', $trail);
|
||||
|
||||
// Verify node breadcrumbs (in menu).
|
||||
// Do this separately for Main menu and Tools menu, since only the
|
||||
// latter is a preferred menu by default.
|
||||
// @todo Also test all themes? Manually testing led to the suspicion that
|
||||
// breadcrumbs may differ, possibly due to theme overrides.
|
||||
$menus = ['main', 'tools'];
|
||||
// Alter node type menu settings.
|
||||
$node_type = NodeType::load($type);
|
||||
$node_type->setThirdPartySetting('menu_ui', 'available_menus', $menus);
|
||||
$node_type->setThirdPartySetting('menu_ui', 'parent', 'tools:');
|
||||
$node_type->save();
|
||||
|
||||
foreach ($menus as $menu) {
|
||||
// Create a parent node in the current menu.
|
||||
$title = $this->randomMachineName();
|
||||
$node2 = $this->drupalCreateNode([
|
||||
'type' => $type,
|
||||
'title' => $title,
|
||||
'menu' => [
|
||||
'enabled' => 1,
|
||||
'title' => 'Parent ' . $title,
|
||||
'description' => '',
|
||||
'menu_name' => $menu,
|
||||
'parent' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($menu == 'tools') {
|
||||
$parent = $node2;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Tools menu link for 'node', move the last parent node menu
|
||||
// link below it, and verify a full breadcrumb for the last child node.
|
||||
$menu = 'tools';
|
||||
$edit = [
|
||||
'title[0][value]' => 'Root',
|
||||
'link[0][uri]' => '/node',
|
||||
];
|
||||
$this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
|
||||
$menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => 'Root']);
|
||||
$link = reset($menu_links);
|
||||
|
||||
$edit = [
|
||||
'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
|
||||
];
|
||||
$this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save'));
|
||||
$expected = [
|
||||
"node" => $link->getTitle(),
|
||||
];
|
||||
$trail = $home + $expected;
|
||||
$tree = $expected + [
|
||||
'node/' . $parent->id() => $parent->menu['title'],
|
||||
];
|
||||
$trail += [
|
||||
'node/' . $parent->id() => $parent->menu['title'],
|
||||
];
|
||||
|
||||
// Add a taxonomy term/tag to last node, and add a link for that term to the
|
||||
// Tools menu.
|
||||
$tags = [
|
||||
'Drupal' => [],
|
||||
'Breadcrumbs' => [],
|
||||
];
|
||||
$edit = [
|
||||
'field_tags[target_id]' => implode(',', array_keys($tags)),
|
||||
];
|
||||
$this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save'));
|
||||
|
||||
// Put both terms into a hierarchy Drupal » Breadcrumbs. Required for both
|
||||
// the menu links and the terms itself, since taxonomy_term_page() resets
|
||||
// the breadcrumb based on taxonomy term hierarchy.
|
||||
$parent_tid = 0;
|
||||
foreach ($tags as $name => $null) {
|
||||
$terms = entity_load_multiple_by_properties('taxonomy_term', ['name' => $name]);
|
||||
$term = reset($terms);
|
||||
$tags[$name]['term'] = $term;
|
||||
if ($parent_tid) {
|
||||
$edit = [
|
||||
'parent[]' => [$parent_tid],
|
||||
];
|
||||
$this->drupalPostForm("taxonomy/term/{$term->id()}/edit", $edit, t('Save'));
|
||||
}
|
||||
$parent_tid = $term->id();
|
||||
}
|
||||
$parent_mlid = '';
|
||||
foreach ($tags as $name => $data) {
|
||||
$term = $data['term'];
|
||||
$edit = [
|
||||
'title[0][value]' => "$name link",
|
||||
'link[0][uri]' => "/taxonomy/term/{$term->id()}",
|
||||
'menu_parent' => "$menu:{$parent_mlid}",
|
||||
'enabled[value]' => 1,
|
||||
];
|
||||
$this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
|
||||
$menu_links = entity_load_multiple_by_properties('menu_link_content', [
|
||||
'title' => $edit['title[0][value]'],
|
||||
'link.uri' => 'internal:/taxonomy/term/' . $term->id(),
|
||||
]);
|
||||
$tags[$name]['link'] = reset($menu_links);
|
||||
$parent_mlid = $tags[$name]['link']->getPluginId();
|
||||
}
|
||||
|
||||
// Verify expected breadcrumbs for menu links.
|
||||
$trail = $home;
|
||||
$tree = [];
|
||||
// Logout the user because we want to check the active class as well, which
|
||||
// is just rendered as anonymous user.
|
||||
$this->drupalLogout();
|
||||
foreach ($tags as $name => $data) {
|
||||
$term = $data['term'];
|
||||
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $link */
|
||||
$link = $data['link'];
|
||||
|
||||
$link_path = $link->getUrlObject()->getInternalPath();
|
||||
$tree += [
|
||||
$link_path => $link->getTitle(),
|
||||
];
|
||||
$this->assertBreadcrumb($link_path, $trail, $term->getName(), $tree);
|
||||
$this->assertEscaped($parent->getTitle(), 'Tagged node found.');
|
||||
|
||||
// Additionally make sure that this link appears only once; i.e., the
|
||||
// untranslated menu links automatically generated from menu router items
|
||||
// ('taxonomy/term/%') should never be translated and appear in any menu
|
||||
// other than the breadcrumb trail.
|
||||
$elements = $this->xpath('//nav[@id=:menu]/descendant::a[@href=:href]', [
|
||||
':menu' => 'block-bartik-tools',
|
||||
':href' => Url::fromUri('base:' . $link_path)->toString(),
|
||||
]);
|
||||
$this->assertTrue(count($elements) == 1, "Link to {$link_path} appears only once.");
|
||||
|
||||
// Next iteration should expect this tag as parent link.
|
||||
// Note: Term name, not link name, due to taxonomy_term_page().
|
||||
$trail += [
|
||||
$link_path => $term->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
// Verify breadcrumbs on user and user/%.
|
||||
// We need to log back in and out below, and cannot simply grant the
|
||||
// 'administer users' permission, since user_page() makes your head explode.
|
||||
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [
|
||||
'access user profiles',
|
||||
]);
|
||||
|
||||
// Verify breadcrumb on front page.
|
||||
$this->assertBreadcrumb('<front>', []);
|
||||
|
||||
// Verify breadcrumb on user pages (without menu link) for anonymous user.
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb('user', $trail, t('Log in'));
|
||||
$this->assertBreadcrumb('user/' . $this->adminUser->id(), $trail, $this->adminUser->getUsername());
|
||||
|
||||
// Verify breadcrumb on user pages (without menu link) for registered users.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb('user', $trail, $this->adminUser->getUsername());
|
||||
$this->assertBreadcrumb('user/' . $this->adminUser->id(), $trail, $this->adminUser->getUsername());
|
||||
$trail += [
|
||||
'user/' . $this->adminUser->id() => $this->adminUser->getUsername(),
|
||||
];
|
||||
$this->assertBreadcrumb('user/' . $this->adminUser->id() . '/edit', $trail, $this->adminUser->getUsername());
|
||||
|
||||
// Create a second user to verify breadcrumb on user pages again.
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'administer users',
|
||||
'access user profiles',
|
||||
]);
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Verify correct breadcrumb and page title on another user's account pages.
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb('user/' . $this->adminUser->id(), $trail, $this->adminUser->getUsername());
|
||||
$trail += [
|
||||
'user/' . $this->adminUser->id() => $this->adminUser->getUsername(),
|
||||
];
|
||||
$this->assertBreadcrumb('user/' . $this->adminUser->id() . '/edit', $trail, $this->adminUser->getUsername());
|
||||
|
||||
// Verify correct breadcrumb and page title when viewing own user account.
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb('user/' . $this->webUser->id(), $trail, $this->webUser->getUsername());
|
||||
$trail += [
|
||||
'user/' . $this->webUser->id() => $this->webUser->getUsername(),
|
||||
];
|
||||
$this->assertBreadcrumb('user/' . $this->webUser->id() . '/edit', $trail, $this->webUser->getUsername());
|
||||
|
||||
// Create an only slightly privileged user being able to access site reports
|
||||
// but not administration pages.
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'access site reports',
|
||||
]);
|
||||
$this->drupalLogin($this->webUser);
|
||||
|
||||
// Verify that we can access recent log entries, there is a corresponding
|
||||
// page title, and that the breadcrumb is just the Home link (because the
|
||||
// user is not able to access "Administer".
|
||||
$trail = $home;
|
||||
$this->assertBreadcrumb('admin', $trail, t('Access denied'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Since the 'admin' path is not accessible, we still expect only the Home
|
||||
// link.
|
||||
$this->assertBreadcrumb('admin/reports', $trail, t('Reports'));
|
||||
$this->assertSession()->statusCodeNotEquals(403);
|
||||
|
||||
// Since the Reports page is accessible, that will show.
|
||||
$trail += ['admin/reports' => t('Reports')];
|
||||
$this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages'));
|
||||
$this->assertSession()->statusCodeNotEquals(403);
|
||||
|
||||
// Ensure that the breadcrumb is safe against XSS.
|
||||
$this->drupalGet('menu-test/breadcrumb1/breadcrumb2/breadcrumb3');
|
||||
$this->assertRaw('<script>alert(12);</script>');
|
||||
$this->assertEscaped('<script>alert(123);</script>');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests local actions derived from router and added/altered via hooks.
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class LocalActionTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $modules = ['block', 'menu_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests appearance of local actions.
|
||||
*/
|
||||
public function testLocalAction() {
|
||||
$this->drupalGet('menu-test-local-action');
|
||||
// Ensure that both menu and route based actions are shown.
|
||||
$this->assertLocalAction([
|
||||
[Url::fromRoute('menu_test.local_action4'), 'My dynamic-title action'],
|
||||
[Url::fromRoute('menu_test.local_action4'), Html::escape("<script>alert('Welcome to the jungle!')</script>")],
|
||||
[Url::fromRoute('menu_test.local_action4'), Html::escape("<script>alert('Welcome to the derived jungle!')</script>")],
|
||||
[Url::fromRoute('menu_test.local_action2'), 'My hook_menu action'],
|
||||
[Url::fromRoute('menu_test.local_action3'), 'My YAML discovery action'],
|
||||
[Url::fromRoute('menu_test.local_action5'), 'Title override'],
|
||||
]);
|
||||
// Test a local action title that changes based on a config value.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_action6'));
|
||||
$this->assertLocalAction([
|
||||
[Url::fromRoute('menu_test.local_action5'), 'Original title'],
|
||||
]);
|
||||
// Verify the expected cache tag in the response headers.
|
||||
$header_values = explode(' ', $this->drupalGetHeader('x-drupal-cache-tags'));
|
||||
$this->assertTrue(in_array('config:menu_test.links.action', $header_values), "Found 'config:menu_test.links.action' cache tag in header");
|
||||
/** @var \Drupal\Core\Config\Config $config */
|
||||
$config = $this->container->get('config.factory')->getEditable('menu_test.links.action');
|
||||
$config->set('title', 'New title');
|
||||
$config->save();
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_action6'));
|
||||
$this->assertLocalAction([
|
||||
[Url::fromRoute('menu_test.local_action5'), 'New title'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts local actions in the page output.
|
||||
*
|
||||
* @param array $actions
|
||||
* A list of expected action link titles, keyed by the hrefs.
|
||||
*/
|
||||
protected function assertLocalAction(array $actions) {
|
||||
$elements = $this->xpath('//a[contains(@class, :class)]', [
|
||||
':class' => 'button-action',
|
||||
]);
|
||||
$index = 0;
|
||||
foreach ($actions as $action) {
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
list($url, $title) = $action;
|
||||
// SimpleXML gives us the unescaped text, not the actual escaped markup,
|
||||
// so use a pattern instead to check the raw content.
|
||||
// This behaviour is a bug in libxml, see
|
||||
// https://bugs.php.net/bug.php?id=49437.
|
||||
$this->assertPattern('@<a [^>]*class="[^"]*button-action[^"]*"[^>]*>' . preg_quote($title, '@') . '</@');
|
||||
$this->assertEqual($elements[$index]->getAttribute('href'), $url->toString());
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests local tasks derived from router and added/altered via hooks.
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class LocalTasksTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $modules = ['block', 'menu_test', 'entity_test', 'node'];
|
||||
|
||||
/**
|
||||
* The local tasks block under testing.
|
||||
*
|
||||
* @var \Drupal\block\Entity\Block
|
||||
*/
|
||||
protected $sut;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->sut = $this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts local tasks in the page output.
|
||||
*
|
||||
* @param array $routes
|
||||
* A list of expected local tasks, prepared as an array of route names and
|
||||
* their associated route parameters, to assert on the page (in the given
|
||||
* order).
|
||||
* @param int $level
|
||||
* (optional) The local tasks level to assert; 0 for primary, 1 for
|
||||
* secondary. Defaults to 0.
|
||||
*/
|
||||
protected function assertLocalTasks(array $routes, $level = 0) {
|
||||
$elements = $this->xpath('//*[contains(@class, :class)]//a', [
|
||||
':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
|
||||
]);
|
||||
$this->assertTrue(count($elements), 'Local tasks found.');
|
||||
foreach ($routes as $index => $route_info) {
|
||||
list($route_name, $route_parameters) = $route_info;
|
||||
$expected = Url::fromRoute($route_name, $route_parameters)->toString();
|
||||
$method = ($elements[$index]->getAttribute('href') == $expected ? 'pass' : 'fail');
|
||||
$this->{$method}(format_string('Task @number href @value equals @expected.', [
|
||||
'@number' => $index + 1,
|
||||
'@value' => $elements[$index]->getAttribute('href'),
|
||||
'@expected' => $expected,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some local task appears.
|
||||
*
|
||||
* @param string $title
|
||||
* The expected title.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the local task exists on the page.
|
||||
*/
|
||||
protected function assertLocalTaskAppers($title) {
|
||||
// SimpleXML gives us the unescaped text, not the actual escaped markup,
|
||||
// so use a pattern instead to check the raw content.
|
||||
// This behaviour is a bug in libxml, see
|
||||
// https://bugs.php.net/bug.php?id=49437.
|
||||
return $this->assertPattern('@<a [^>]*>' . preg_quote($title, '@') . '</a>@');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the local tasks on the specified level are not being printed.
|
||||
*
|
||||
* @param int $level
|
||||
* (optional) The local tasks level to assert; 0 for primary, 1 for
|
||||
* secondary. Defaults to 0.
|
||||
*/
|
||||
protected function assertNoLocalTasks($level = 0) {
|
||||
$elements = $this->xpath('//*[contains(@class, :class)]//a', [
|
||||
':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
|
||||
]);
|
||||
$this->assertFalse(count($elements), 'Local tasks not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the plugin based local tasks.
|
||||
*/
|
||||
public function testPluginLocalTask() {
|
||||
// Verify local tasks defined in the hook.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.tasks_default'));
|
||||
$this->assertLocalTasks([
|
||||
['menu_test.tasks_default', []],
|
||||
['menu_test.router_test1', ['bar' => 'unsafe']],
|
||||
['menu_test.router_test1', ['bar' => '1']],
|
||||
['menu_test.router_test2', ['bar' => '2']],
|
||||
]);
|
||||
|
||||
// Verify that script tags are escaped on output.
|
||||
$title = Html::escape("Task 1 <script>alert('Welcome to the jungle!')</script>");
|
||||
$this->assertLocalTaskAppers($title);
|
||||
$title = Html::escape("<script>alert('Welcome to the derived jungle!')</script>");
|
||||
$this->assertLocalTaskAppers($title);
|
||||
|
||||
// Verify that local tasks appear as defined in the router.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_view'));
|
||||
$this->assertLocalTasks([
|
||||
['menu_test.local_task_test_tasks_view', []],
|
||||
['menu_test.local_task_test_tasks_edit', []],
|
||||
['menu_test.local_task_test_tasks_settings', []],
|
||||
['menu_test.local_task_test_tasks_settings_dynamic', []],
|
||||
]);
|
||||
|
||||
$title = Html::escape("<script>alert('Welcome to the jungle!')</script>");
|
||||
$this->assertLocalTaskAppers($title);
|
||||
|
||||
// Ensure the view tab is active.
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
|
||||
$this->assertEqual(1, count($result), 'There is just a single active tab.');
|
||||
$this->assertEqual('View(active tab)', $result[0]->getText(), 'The view tab is active.');
|
||||
|
||||
// Verify that local tasks in the second level appear.
|
||||
$sub_tasks = [
|
||||
['menu_test.local_task_test_tasks_settings_sub1', []],
|
||||
['menu_test.local_task_test_tasks_settings_sub2', []],
|
||||
['menu_test.local_task_test_tasks_settings_sub3', []],
|
||||
['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']],
|
||||
['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive2']],
|
||||
];
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
|
||||
$this->assertLocalTasks($sub_tasks, 1);
|
||||
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
|
||||
$this->assertEqual(1, count($result), 'There is just a single active tab.');
|
||||
$this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings_sub1'));
|
||||
$this->assertLocalTasks($sub_tasks, 1);
|
||||
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
|
||||
$this->assertEqual(2, count($result), 'There are tabs active on both levels.');
|
||||
$this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
|
||||
$this->assertEqual('Dynamic title for TestTasksSettingsSub1(active tab)', $result[1]->getText(), 'The sub1 tab is active.');
|
||||
|
||||
$this->assertCacheTag('kittens:ragdoll');
|
||||
$this->assertCacheTag('kittens:dwarf-cat');
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']));
|
||||
$this->assertLocalTasks($sub_tasks, 1);
|
||||
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
|
||||
$this->assertEqual(2, count($result), 'There are tabs active on both levels.');
|
||||
$this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
|
||||
$this->assertEqual('Derive 1(active tab)', $result[1]->getText(), 'The derive1 tab is active.');
|
||||
|
||||
// Ensures that the local tasks contains the proper 'provider key'
|
||||
$definitions = $this->container->get('plugin.manager.menu.local_task')->getDefinitions();
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_view']['provider'], 'menu_test');
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_edit']['provider'], 'menu_test');
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_settings']['provider'], 'menu_test');
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub1']['provider'], 'menu_test');
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub2']['provider'], 'menu_test');
|
||||
$this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub3']['provider'], 'menu_test');
|
||||
|
||||
// Test that we we correctly apply the active class to tabs where one of the
|
||||
// request attributes is upcast to an entity object.
|
||||
$entity = \Drupal::entityManager()->getStorage('entity_test')->create(['bundle' => 'test']);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']));
|
||||
|
||||
$tasks = [
|
||||
['menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']],
|
||||
['menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']],
|
||||
];
|
||||
|
||||
$this->assertLocalTasks($tasks, 0);
|
||||
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
|
||||
$this->assertEqual(1, count($result), 'There is one active tab.');
|
||||
$this->assertEqual('upcasting sub1(active tab)', $result[0]->getText(), 'The "upcasting sub1" tab is active.');
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']));
|
||||
|
||||
$tasks = [
|
||||
['menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']],
|
||||
['menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']],
|
||||
];
|
||||
$this->assertLocalTasks($tasks, 0);
|
||||
|
||||
$result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
|
||||
$this->assertEqual(1, count($result), 'There is one active tab.');
|
||||
$this->assertEqual('upcasting sub2(active tab)', $result[0]->getText(), 'The "upcasting sub2" tab is active.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that local task blocks are configurable to show a specific level.
|
||||
*/
|
||||
public function testLocalTaskBlock() {
|
||||
// Remove the default block and create a new one.
|
||||
$this->sut->delete();
|
||||
|
||||
$this->sut = $this->drupalPlaceBlock('local_tasks_block', [
|
||||
'id' => 'tabs_block',
|
||||
'primary' => TRUE,
|
||||
'secondary' => FALSE,
|
||||
]);
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
|
||||
|
||||
// Verify that local tasks in the first level appear.
|
||||
$this->assertLocalTasks([
|
||||
['menu_test.local_task_test_tasks_view', []],
|
||||
['menu_test.local_task_test_tasks_edit', []],
|
||||
['menu_test.local_task_test_tasks_settings', []],
|
||||
]);
|
||||
|
||||
// Verify that local tasks in the second level doesn't appear.
|
||||
$this->assertNoLocalTasks(1);
|
||||
|
||||
$this->sut->delete();
|
||||
$this->sut = $this->drupalPlaceBlock('local_tasks_block', [
|
||||
'id' => 'tabs_block',
|
||||
'primary' => FALSE,
|
||||
'secondary' => TRUE,
|
||||
]);
|
||||
|
||||
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
|
||||
|
||||
// Verify that local tasks in the first level doesn't appear.
|
||||
$this->assertNoLocalTasks(0);
|
||||
|
||||
// Verify that local tasks in the second level appear.
|
||||
$sub_tasks = [
|
||||
['menu_test.local_task_test_tasks_settings_sub1', []],
|
||||
['menu_test.local_task_test_tasks_settings_sub2', []],
|
||||
['menu_test.local_task_test_tasks_settings_sub3', []],
|
||||
['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']],
|
||||
['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive2']],
|
||||
];
|
||||
$this->assertLocalTasks($sub_tasks, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that local tasks blocks cache is invalidated correctly.
|
||||
*/
|
||||
public function testLocalTaskBlockCache() {
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
|
||||
$this->drupalGet('/admin/structure/types/manage/page');
|
||||
|
||||
// Only the Edit task. The block avoids showing a single tab.
|
||||
$this->assertNoLocalTasks();
|
||||
|
||||
// Field UI adds the usual Manage fields etc tabs.
|
||||
\Drupal::service('module_installer')->install(['field_ui']);
|
||||
|
||||
$this->drupalGet('/admin/structure/types/manage/page');
|
||||
|
||||
$this->assertLocalTasks([
|
||||
['entity.node_type.edit_form', ['node_type' => 'page']],
|
||||
['entity.node.field_ui_fields', ['node_type' => 'page']],
|
||||
['entity.entity_form_display.node.default', ['node_type' => 'page']],
|
||||
['entity.entity_view_display.node.default', ['node_type' => 'page']],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ class MenuAccessTest extends BrowserTestBase {
|
|||
// Test that there's link rendered on the route.
|
||||
$this->drupalGet('menu_test_access_check_session');
|
||||
$this->assertLink('Test custom route access check');
|
||||
// Page still accessible but thre should not be menu link.
|
||||
// Page is still accessible but there should be no menu link.
|
||||
$this->drupalGet('menu_test_access_check_session');
|
||||
$this->assertResponse(200);
|
||||
$this->assertNoLink('Test custom route access check');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,336 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Menu;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests menu router and default menu link functionality.
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class MenuRouterTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'menu_test', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* Name of the administrative theme to use for tests.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminTheme;
|
||||
|
||||
/**
|
||||
* Name of the default theme to use for tests.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultTheme;
|
||||
|
||||
protected function setUp() {
|
||||
// Enable dummy module that implements hook_menu.
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('system_menu_block:tools');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests menu integration.
|
||||
*/
|
||||
public function testMenuIntegration() {
|
||||
$this->doTestTitleMenuCallback();
|
||||
$this->doTestMenuOptionalPlaceholders();
|
||||
$this->doTestMenuHierarchy();
|
||||
$this->doTestMenuOnRoute();
|
||||
$this->doTestMenuName();
|
||||
$this->doTestMenuLinksDiscoveredAlter();
|
||||
$this->doTestHookMenuIntegration();
|
||||
$this->doTestExoticPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test local tasks with route placeholders.
|
||||
*/
|
||||
protected function doTestHookMenuIntegration() {
|
||||
// Generate base path with random argument.
|
||||
$machine_name = $this->randomMachineName(8);
|
||||
$base_path = 'foo/' . $machine_name;
|
||||
$this->drupalGet($base_path);
|
||||
// Confirm correct controller activated.
|
||||
$this->assertText('test1');
|
||||
// Confirm local task links are displayed.
|
||||
$this->assertLink('Local task A');
|
||||
$this->assertLink('Local task B');
|
||||
$this->assertNoLink('Local task C');
|
||||
$this->assertEscaped("<script>alert('Welcome to the jungle!')</script>", ENT_QUOTES, 'UTF-8');
|
||||
// Confirm correct local task href.
|
||||
$this->assertLinkByHref(Url::fromRoute('menu_test.router_test1', ['bar' => $machine_name])->toString());
|
||||
$this->assertLinkByHref(Url::fromRoute('menu_test.router_test2', ['bar' => $machine_name])->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test title callback set to FALSE.
|
||||
*/
|
||||
protected function doTestTitleCallbackFalse() {
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertText('A title with @placeholder', 'Raw text found on the page');
|
||||
$this->assertNoText(t('A title with @placeholder', ['@placeholder' => 'some other text']), 'Text with placeholder substitutions not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests page title of MENU_CALLBACKs.
|
||||
*/
|
||||
protected function doTestTitleMenuCallback() {
|
||||
// Verify that the menu router item title is not visible.
|
||||
$this->drupalGet('');
|
||||
$this->assertNoText(t('Menu Callback Title'));
|
||||
// Verify that the menu router item title is output as page title.
|
||||
$this->drupalGet('menu_callback_title');
|
||||
$this->assertText(t('Menu Callback Title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests menu item descriptions.
|
||||
*/
|
||||
protected function doTestDescriptionMenuItems() {
|
||||
// Verify that the menu router item title is output as page title.
|
||||
$this->drupalGet('menu_callback_description');
|
||||
$this->assertText(t('Menu item description text'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for menu_name parameter for default menu links.
|
||||
*/
|
||||
protected function doTestMenuName() {
|
||||
$admin_user = $this->drupalCreateUser(['administer site configuration']);
|
||||
$this->drupalLogin($admin_user);
|
||||
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
|
||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
|
||||
$menu_link = reset($menu_links);
|
||||
$this->assertEqual($menu_link->getMenuName(), 'original', 'Menu name is "original".');
|
||||
|
||||
// Change the menu_name parameter in menu_test.module, then force a menu
|
||||
// rebuild.
|
||||
menu_test_menu_name('changed');
|
||||
$menu_link_manager->rebuild();
|
||||
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
|
||||
$menu_link = reset($menu_links);
|
||||
$this->assertEqual($menu_link->getMenuName(), 'changed', 'Menu name was successfully changed after rebuild.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests menu links added in hook_menu_links_discovered_alter().
|
||||
*/
|
||||
protected function doTestMenuLinksDiscoveredAlter() {
|
||||
// Check that machine name does not need to be defined since it is already
|
||||
// set as the key of each menu link.
|
||||
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
|
||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.custom');
|
||||
$menu_link = reset($menu_links);
|
||||
$this->assertEqual($menu_link->getPluginId(), 'menu_test.custom', 'Menu links added at hook_menu_links_discovered_alter() obtain the machine name from the $links key.');
|
||||
// Make sure that rebuilding the menu tree does not produce duplicates of
|
||||
// links added by hook_menu_links_discovered_alter().
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
$this->drupalGet('menu-test');
|
||||
$this->assertUniqueText('Custom link', 'Menu links added by hook_menu_links_discovered_alter() do not duplicate after a menu rebuild.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for menu hierarchy.
|
||||
*/
|
||||
protected function doTestMenuHierarchy() {
|
||||
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
|
||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent');
|
||||
$parent_link = reset($menu_links);
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent_child');
|
||||
$child_link = reset($menu_links);
|
||||
$menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent_child2');
|
||||
$unattached_child_link = reset($menu_links);
|
||||
$this->assertEqual($child_link->getParent(), $parent_link->getPluginId(), 'The parent of a directly attached child is correct.');
|
||||
$this->assertEqual($unattached_child_link->getParent(), $child_link->getPluginId(), 'The parent of a non-directly attached child is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test menu links that have optional placeholders.
|
||||
*/
|
||||
protected function doTestMenuOptionalPlaceholders() {
|
||||
$this->drupalGet('menu-test/optional');
|
||||
$this->assertResponse(200);
|
||||
$this->assertText('Sometimes there is no placeholder.');
|
||||
|
||||
$this->drupalGet('menu-test/optional/foobar');
|
||||
$this->assertResponse(200);
|
||||
$this->assertText("Sometimes there is a placeholder: 'foobar'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a menu on a router page.
|
||||
*/
|
||||
protected function doTestMenuOnRoute() {
|
||||
\Drupal::service('module_installer')->install(['router_test']);
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
$this->resetAll();
|
||||
|
||||
$this->drupalGet('router_test/test2');
|
||||
$this->assertLinkByHref('menu_no_title_callback');
|
||||
$this->assertLinkByHref('menu-title-test/case1');
|
||||
$this->assertLinkByHref('menu-title-test/case2');
|
||||
$this->assertLinkByHref('menu-title-test/case3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test path containing "exotic" characters.
|
||||
*/
|
||||
protected function doTestExoticPath() {
|
||||
// "Special" ASCII characters.
|
||||
$path =
|
||||
"menu-test/ -._~!$'\"()*@[]?&+%#,;=:" .
|
||||
// Characters that look like a percent-escaped string.
|
||||
"%23%25%26%2B%2F%3F" .
|
||||
// Characters from various non-ASCII alphabets.
|
||||
"éøïвβ中國書۞";
|
||||
$this->drupalGet($path);
|
||||
$this->assertRaw('This is the menuTestCallback content.');
|
||||
$this->assertNoText(t('The website encountered an unexpected error. Please try again later.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the maintenance mode can be bypassed using an EventSubscriber.
|
||||
*
|
||||
* @see \Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber::onKernelRequestMaintenance()
|
||||
*/
|
||||
public function testMaintenanceModeLoginPaths() {
|
||||
$this->container->get('state')->set('system.maintenance_mode', TRUE);
|
||||
|
||||
$offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', ['@site' => $this->config('system.site')->get('name')]);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertText($offline_message);
|
||||
$this->drupalGet('menu_login_callback');
|
||||
$this->assertText('This is TestControllers::testLogin.', 'Maintenance mode can be bypassed using an event subscriber.');
|
||||
|
||||
$this->container->get('state')->set('system.maintenance_mode', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an authenticated user hitting 'user/login' gets redirected to
|
||||
* 'user' and 'user/register' gets redirected to the user edit page.
|
||||
*/
|
||||
public function testAuthUserUserLogin() {
|
||||
$web_user = $this->drupalCreateUser([]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->drupalGet('user/login');
|
||||
// Check that we got to 'user'.
|
||||
$this->assertUrl($this->loggedInUser->url('canonical', ['absolute' => TRUE]));
|
||||
|
||||
// user/register should redirect to user/UID/edit.
|
||||
$this->drupalGet('user/register');
|
||||
$this->assertUrl($this->loggedInUser->url('edit-form', ['absolute' => TRUE]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests theme integration.
|
||||
*/
|
||||
public function testThemeIntegration() {
|
||||
$this->defaultTheme = 'bartik';
|
||||
$this->adminTheme = 'seven';
|
||||
|
||||
$theme_handler = $this->container->get('theme_handler');
|
||||
$theme_handler->install([$this->defaultTheme, $this->adminTheme]);
|
||||
$this->config('system.theme')
|
||||
->set('default', $this->defaultTheme)
|
||||
->set('admin', $this->adminTheme)
|
||||
->save();
|
||||
|
||||
$this->doTestThemeCallbackMaintenanceMode();
|
||||
|
||||
$this->doTestThemeCallbackFakeTheme();
|
||||
|
||||
$this->doTestThemeCallbackAdministrative();
|
||||
|
||||
$this->doTestThemeCallbackNoThemeRequested();
|
||||
|
||||
$this->doTestThemeCallbackOptionalTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the theme negotiation when it is set to use an administrative theme.
|
||||
*/
|
||||
protected function doTestThemeCallbackAdministrative() {
|
||||
$this->drupalGet('menu-test/theme-callback/use-admin-theme');
|
||||
$this->assertText('Active theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme negotiation.');
|
||||
$this->assertRaw('seven/css/base/elements.css', "The administrative theme's CSS appears on the page.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the theme negotiation when the site is in maintenance mode.
|
||||
*/
|
||||
protected function doTestThemeCallbackMaintenanceMode() {
|
||||
$this->container->get('state')->set('system.maintenance_mode', TRUE);
|
||||
|
||||
// For a regular user, the fact that the site is in maintenance mode means
|
||||
// we expect the theme callback system to be bypassed entirely.
|
||||
$this->drupalGet('menu-test/theme-callback/use-admin-theme');
|
||||
$this->assertRaw('bartik/css/base/elements.css', "The maintenance theme's CSS appears on the page.");
|
||||
|
||||
// An administrator, however, should continue to see the requested theme.
|
||||
$admin_user = $this->drupalCreateUser(['access site in maintenance mode']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('menu-test/theme-callback/use-admin-theme');
|
||||
$this->assertText('Active theme: seven. Actual theme: seven.', 'The theme negotiation system is correctly triggered for an administrator when the site is in maintenance mode.');
|
||||
$this->assertRaw('seven/css/base/elements.css', "The administrative theme's CSS appears on the page.");
|
||||
|
||||
$this->container->get('state')->set('system.maintenance_mode', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the theme negotiation when it is set to use an optional theme.
|
||||
*/
|
||||
protected function doTestThemeCallbackOptionalTheme() {
|
||||
// Request a theme that is not installed.
|
||||
$this->drupalGet('menu-test/theme-callback/use-test-theme');
|
||||
$this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when a theme that is not installed is requested.');
|
||||
$this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");
|
||||
|
||||
// Now install the theme and request it again.
|
||||
$theme_handler = $this->container->get('theme_handler');
|
||||
$theme_handler->install(['test_theme']);
|
||||
|
||||
$this->drupalGet('menu-test/theme-callback/use-test-theme');
|
||||
$this->assertText('Active theme: test_theme. Actual theme: test_theme.', 'The theme negotiation system uses an optional theme once it has been installed.');
|
||||
$this->assertRaw('test_theme/kitten.css', "The optional theme's CSS appears on the page.");
|
||||
|
||||
$theme_handler->uninstall(['test_theme']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the theme negotiation when it is set to use a theme that does not exist.
|
||||
*/
|
||||
protected function doTestThemeCallbackFakeTheme() {
|
||||
$this->drupalGet('menu-test/theme-callback/use-fake-theme');
|
||||
$this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when a theme that does not exist is requested.');
|
||||
$this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the theme negotiation when no theme is requested.
|
||||
*/
|
||||
protected function doTestThemeCallbackNoThemeRequested() {
|
||||
$this->drupalGet('menu-test/theme-callback/no-theme-requested');
|
||||
$this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when no theme is requested.');
|
||||
$this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,9 +13,16 @@ class ClassLoaderTest extends BrowserTestBase {
|
|||
|
||||
/**
|
||||
* The expected result from calling the module-provided class' method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $expected = 'Drupal\\module_autoload_test\\SomeClass::testMethod() was invoked.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $apcuEnsureUniquePrefix = TRUE;
|
||||
|
||||
/**
|
||||
* Tests that module-provided classes can be loaded when a module is enabled.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Enable module without dependency enabled.
|
||||
*
|
||||
* @group Module
|
||||
*/
|
||||
class DependencyTest extends ModuleTestBase {
|
||||
|
||||
/**
|
||||
* Checks functionality of project namespaces for dependencies.
|
||||
*/
|
||||
public function testProjectNamespaceForDependencies() {
|
||||
$edit = [
|
||||
'modules[filter][enable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
// Enable module with project namespace to ensure nothing breaks.
|
||||
$edit = [
|
||||
'modules[system_project_namespace_test][enable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->assertModules(['system_project_namespace_test'], TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to enable the Content Translation module without Language enabled.
|
||||
*/
|
||||
public function testEnableWithoutDependency() {
|
||||
// Attempt to enable Content Translation without Language enabled.
|
||||
$edit = [];
|
||||
$edit['modules[content_translation][enable]'] = 'content_translation';
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->assertText(t('Some required modules must be enabled'), 'Dependency required.');
|
||||
|
||||
$this->assertModules(['content_translation', 'language'], FALSE);
|
||||
|
||||
// Assert that the language tables weren't enabled.
|
||||
$this->assertTableCount('language', FALSE);
|
||||
|
||||
$this->drupalPostForm(NULL, NULL, t('Continue'));
|
||||
$this->assertText(t('2 modules have been enabled: Content Translation, Language.'), 'Modules status has been updated.');
|
||||
$this->assertModules(['content_translation', 'language'], TRUE);
|
||||
|
||||
// Assert that the language YAML files were created.
|
||||
$storage = $this->container->get('config.storage');
|
||||
$this->assertTrue(count($storage->listAll('language.entity.')) > 0, 'Language config entity files exist.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to enable a module with a missing dependency.
|
||||
*/
|
||||
public function testMissingModules() {
|
||||
// Test that the system_dependencies_test module is marked
|
||||
// as missing a dependency.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->assertRaw(t('@module (<span class="admin-missing">missing</span>)', ['@module' => Unicode::ucfirst('_missing_dependency')]), 'A module with missing dependencies is marked as such.');
|
||||
$checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[system_dependencies_test][enable]"]');
|
||||
$this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling a module that depends on an incompatible version of a module.
|
||||
*/
|
||||
public function testIncompatibleModuleVersionDependency() {
|
||||
// Test that the system_incompatible_module_version_dependencies_test is
|
||||
// marked as having an incompatible dependency.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->assertRaw(t('@module (<span class="admin-missing">incompatible with</span> version @version)', [
|
||||
'@module' => 'System incompatible module version test (>2.0)',
|
||||
'@version' => '1.0',
|
||||
]), 'A module that depends on an incompatible version of a module is marked as such.');
|
||||
$checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[system_incompatible_module_version_dependencies_test][enable]"]');
|
||||
$this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling a module that depends on a module with an incompatible core version.
|
||||
*/
|
||||
public function testIncompatibleCoreVersionDependency() {
|
||||
// Test that the system_incompatible_core_version_dependencies_test is
|
||||
// marked as having an incompatible dependency.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->assertRaw(t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', [
|
||||
'@module' => 'System incompatible core version test',
|
||||
]), 'A module that depends on a module with an incompatible core version is marked as such.');
|
||||
$checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[system_incompatible_core_version_dependencies_test][enable]"]');
|
||||
$this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests failing PHP version requirements.
|
||||
*/
|
||||
public function testIncompatiblePhpVersionDependency() {
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->assertRaw('This module requires PHP version 6502.* and is incompatible with PHP version ' . phpversion() . '.', 'User is informed when the PHP dependency requirement of a module is not met.');
|
||||
$checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[system_incompatible_php_version_test][enable]"]');
|
||||
$this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling a module that depends on a module which fails hook_requirements().
|
||||
*/
|
||||
public function testEnableRequirementsFailureDependency() {
|
||||
\Drupal::service('module_installer')->install(['comment']);
|
||||
|
||||
$this->assertModules(['requirements1_test'], FALSE);
|
||||
$this->assertModules(['requirements2_test'], FALSE);
|
||||
|
||||
// Attempt to install both modules at the same time.
|
||||
$edit = [];
|
||||
$edit['modules[requirements1_test][enable]'] = 'requirements1_test';
|
||||
$edit['modules[requirements2_test][enable]'] = 'requirements2_test';
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
|
||||
// Makes sure the modules were NOT installed.
|
||||
$this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
|
||||
$this->assertModules(['requirements1_test'], FALSE);
|
||||
$this->assertModules(['requirements2_test'], FALSE);
|
||||
|
||||
// Makes sure that already enabled modules the failing modules depend on
|
||||
// were not disabled.
|
||||
$this->assertModules(['comment'], TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that module dependencies are enabled in the correct order in the UI.
|
||||
*
|
||||
* Dependencies should be enabled before their dependents.
|
||||
*/
|
||||
public function testModuleEnableOrder() {
|
||||
\Drupal::service('module_installer')->install(['module_test'], FALSE);
|
||||
$this->resetAll();
|
||||
$this->assertModules(['module_test'], TRUE);
|
||||
\Drupal::state()->set('module_test.dependency', 'dependency');
|
||||
// module_test creates a dependency chain:
|
||||
// - color depends on config
|
||||
// - config depends on help
|
||||
$expected_order = ['help', 'config', 'color'];
|
||||
|
||||
// Enable the modules through the UI, verifying that the dependency chain
|
||||
// is correct.
|
||||
$edit = [];
|
||||
$edit['modules[color][enable]'] = 'color';
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->assertModules(['color'], FALSE);
|
||||
// Note that dependencies are sorted alphabetically in the confirmation
|
||||
// message.
|
||||
$this->assertText(t('You must enable the Configuration Manager, Help modules to install Color.'));
|
||||
|
||||
$edit['modules[config][enable]'] = 'config';
|
||||
$edit['modules[help][enable]'] = 'help';
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->assertModules(['color', 'config', 'help'], TRUE);
|
||||
|
||||
// Check the actual order which is saved by module_test_modules_enabled().
|
||||
$module_order = \Drupal::state()->get('module_test.install_order') ?: [];
|
||||
$this->assertIdentical($module_order, $expected_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests attempting to uninstall a module that has installed dependents.
|
||||
*/
|
||||
public function testUninstallDependents() {
|
||||
// Enable the forum module.
|
||||
$edit = ['modules[forum][enable]' => 'forum'];
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->drupalPostForm(NULL, [], t('Continue'));
|
||||
$this->assertModules(['forum'], TRUE);
|
||||
|
||||
// Check that the comment module cannot be uninstalled.
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$checkbox = $this->xpath('//input[@type="checkbox" and @name="uninstall[comment]" and @disabled="disabled"]');
|
||||
$this->assert(count($checkbox) == 1, 'Checkbox for uninstalling the comment module is disabled.');
|
||||
|
||||
// Delete any forum terms.
|
||||
$vid = $this->config('forum.settings')->get('vocabulary');
|
||||
// Ensure taxonomy has been loaded into the test-runner after forum was
|
||||
// enabled.
|
||||
\Drupal::moduleHandler()->load('taxonomy');
|
||||
$terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]);
|
||||
foreach ($terms as $term) {
|
||||
$term->delete();
|
||||
}
|
||||
// Uninstall the forum module, and check that taxonomy now can also be
|
||||
// uninstalled.
|
||||
$edit = ['uninstall[forum]' => 'forum'];
|
||||
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
|
||||
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
|
||||
|
||||
// Uninstall comment module.
|
||||
$edit = ['uninstall[comment]' => 'comment'];
|
||||
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
|
||||
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
/**
|
||||
* Attempts enabling a module that fails hook_requirements('install').
|
||||
*
|
||||
* @group Module
|
||||
*/
|
||||
class HookRequirementsTest extends ModuleTestBase {
|
||||
|
||||
/**
|
||||
* Assert that a module cannot be installed if it fails hook_requirements().
|
||||
*/
|
||||
public function testHookRequirementsFailure() {
|
||||
$this->assertModules(['requirements1_test'], FALSE);
|
||||
|
||||
// Attempt to install the requirements1_test module.
|
||||
$edit = [];
|
||||
$edit['modules[requirements1_test][enable]'] = 'requirements1_test';
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
|
||||
// Makes sure the module was NOT installed.
|
||||
$this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
|
||||
$this->assertModules(['requirements1_test'], FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Install/uninstall core module and confirm table creation/deletion.
|
||||
*
|
||||
* @group Module
|
||||
*/
|
||||
class InstallUninstallTest extends ModuleTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system_test', 'dblog', 'taxonomy', 'update_test_postupdate'];
|
||||
|
||||
/**
|
||||
* Tests that a fixed set of modules can be installed and uninstalled.
|
||||
*/
|
||||
public function testInstallUninstall() {
|
||||
// Set a variable so that the hook implementations in system_test.module
|
||||
// will display messages via
|
||||
// \Drupal\Core\Messenger\MessengerInterface::addStatus().
|
||||
$this->container->get('state')->set('system_test.verbose_module_hooks', TRUE);
|
||||
|
||||
// Install and uninstall module_test to ensure hook_preinstall_module and
|
||||
// hook_preuninstall_module are fired as expected.
|
||||
$this->container->get('module_installer')->install(['module_test']);
|
||||
$this->assertEqual($this->container->get('state')->get('system_test_preinstall_module'), 'module_test');
|
||||
$this->container->get('module_installer')->uninstall(['module_test']);
|
||||
$this->assertEqual($this->container->get('state')->get('system_test_preuninstall_module'), 'module_test');
|
||||
$this->resetAll();
|
||||
|
||||
$all_modules = system_rebuild_module_data();
|
||||
|
||||
// Test help on required modules, but do not test uninstalling.
|
||||
$required_modules = array_filter($all_modules, function ($module) {
|
||||
if (!empty($module->info['required']) || $module->status == TRUE) {
|
||||
if ($module->info['package'] != 'Testing' && empty($module->info['hidden'])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
});
|
||||
|
||||
$required_modules['help'] = $all_modules['help'];
|
||||
|
||||
// Test uninstalling without hidden, required, and already enabled modules.
|
||||
$all_modules = array_filter($all_modules, function ($module) {
|
||||
if (!empty($module->info['hidden']) || !empty($module->info['required']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
});
|
||||
|
||||
// Install the Help module, and verify it installed successfully.
|
||||
unset($all_modules['help']);
|
||||
$this->assertModuleNotInstalled('help');
|
||||
$edit = [];
|
||||
$edit["modules[help][enable]"] = TRUE;
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
$this->assertText('has been enabled', 'Modules status has been updated.');
|
||||
$this->assertText(t('hook_modules_installed fired for help'));
|
||||
$this->assertModuleSuccessfullyInstalled('help');
|
||||
|
||||
// Test help for the required modules.
|
||||
foreach ($required_modules as $name => $module) {
|
||||
$this->assertHelp($name, $module->info['name']);
|
||||
}
|
||||
|
||||
// Go through each module in the list and try to install and uninstall
|
||||
// it with its dependencies.
|
||||
foreach ($all_modules as $name => $module) {
|
||||
$was_installed_list = \Drupal::moduleHandler()->getModuleList();
|
||||
|
||||
// Start a list of modules that we expect to be installed this time.
|
||||
$modules_to_install = [$name];
|
||||
foreach (array_keys($module->requires) as $dependency) {
|
||||
if (isset($all_modules[$dependency])) {
|
||||
$modules_to_install[] = $dependency;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each module is not yet enabled and does not have any
|
||||
// database tables yet.
|
||||
foreach ($modules_to_install as $module_to_install) {
|
||||
$this->assertModuleNotInstalled($module_to_install);
|
||||
}
|
||||
|
||||
// Install the module.
|
||||
$edit = [];
|
||||
$package = $module->info['package'];
|
||||
$edit['modules[' . $name . '][enable]'] = TRUE;
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
|
||||
// Handle experimental modules, which require a confirmation screen.
|
||||
if ($package == 'Core (Experimental)') {
|
||||
$this->assertText('Are you sure you wish to enable experimental modules?');
|
||||
if (count($modules_to_install) > 1) {
|
||||
// When there are experimental modules, needed dependencies do not
|
||||
// result in the same page title, but there will be expected text
|
||||
// indicating they need to be enabled.
|
||||
$this->assertText('You must enable');
|
||||
}
|
||||
$this->drupalPostForm(NULL, [], t('Continue'));
|
||||
}
|
||||
// Handle the case where modules were installed along with this one and
|
||||
// where we therefore hit a confirmation screen.
|
||||
elseif (count($modules_to_install) > 1) {
|
||||
// Verify that we are on the correct form and that the expected text
|
||||
// about enabling dependencies appears.
|
||||
$this->assertText('Some required modules must be enabled');
|
||||
$this->assertText('You must enable');
|
||||
$this->drupalPostForm(NULL, [], t('Continue'));
|
||||
}
|
||||
|
||||
// List the module display names to check the confirmation message.
|
||||
$module_names = [];
|
||||
foreach ($modules_to_install as $module_to_install) {
|
||||
$module_names[] = $all_modules[$module_to_install]->info['name'];
|
||||
}
|
||||
$expected_text = \Drupal::translation()->formatPlural(count($module_names), 'Module @name has been enabled.', '@count modules have been enabled: @names.', [
|
||||
'@name' => $module_names[0],
|
||||
'@names' => implode(', ', $module_names),
|
||||
]);
|
||||
$this->assertText($expected_text, 'Modules status has been updated.');
|
||||
|
||||
// Check that hook_modules_installed() was invoked with the expected list
|
||||
// of modules, that each module's database tables now exist, and that
|
||||
// appropriate messages appear in the logs.
|
||||
foreach ($modules_to_install as $module_to_install) {
|
||||
$this->assertText(t('hook_modules_installed fired for @module', ['@module' => $module_to_install]));
|
||||
$this->assertLogMessage('system', "%module module installed.", ['%module' => $module_to_install], RfcLogLevel::INFO);
|
||||
$this->assertInstallModuleUpdates($module_to_install);
|
||||
$this->assertModuleSuccessfullyInstalled($module_to_install);
|
||||
}
|
||||
|
||||
// Verify the help page.
|
||||
$this->assertHelp($name, $module->info['name']);
|
||||
|
||||
// Uninstall the original module, plus everything else that was installed
|
||||
// with it.
|
||||
if ($name == 'forum') {
|
||||
// Forum has an extra step to be able to uninstall it.
|
||||
$this->preUninstallForum();
|
||||
}
|
||||
|
||||
// Delete all workspaces before uninstall.
|
||||
if ($name == 'workspaces') {
|
||||
$workspaces = Workspace::loadMultiple();
|
||||
\Drupal::entityTypeManager()->getStorage('workspace')->delete($workspaces);
|
||||
}
|
||||
|
||||
$now_installed_list = \Drupal::moduleHandler()->getModuleList();
|
||||
$added_modules = array_diff(array_keys($now_installed_list), array_keys($was_installed_list));
|
||||
while ($added_modules) {
|
||||
$initial_count = count($added_modules);
|
||||
foreach ($added_modules as $to_uninstall) {
|
||||
// See if we can currently uninstall this module (if its dependencies
|
||||
// have been uninstalled), and do so if we can.
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$field_name = "uninstall[$to_uninstall]";
|
||||
$has_checkbox = $this->xpath('//input[@type="checkbox" and @name="' . $field_name . '"]');
|
||||
$disabled = $this->xpath('//input[@type="checkbox" and @name="' . $field_name . '" and @disabled="disabled"]');
|
||||
|
||||
if (!empty($has_checkbox) && empty($disabled)) {
|
||||
// This one is eligible for being uninstalled.
|
||||
$package = $all_modules[$to_uninstall]->info['package'];
|
||||
$this->assertSuccessfulUninstall($to_uninstall, $package);
|
||||
$added_modules = array_diff($added_modules, [$to_uninstall]);
|
||||
}
|
||||
}
|
||||
|
||||
// If we were not able to find a module to uninstall, fail and exit the
|
||||
// loop.
|
||||
$final_count = count($added_modules);
|
||||
if ($initial_count == $final_count) {
|
||||
$this->fail('Remaining modules could not be uninstalled for ' . $name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall the help module and put it back into the list of modules.
|
||||
$all_modules['help'] = $required_modules['help'];
|
||||
$this->assertSuccessfulUninstall('help', $required_modules['help']->info['package']);
|
||||
|
||||
// Now that all modules have been tested, go back and try to enable them
|
||||
// all again at once. This tests two things:
|
||||
// - That each module can be successfully enabled again after being
|
||||
// uninstalled.
|
||||
// - That enabling more than one module at the same time does not lead to
|
||||
// any errors.
|
||||
$edit = [];
|
||||
$experimental = FALSE;
|
||||
foreach ($all_modules as $name => $module) {
|
||||
$edit['modules[' . $name . '][enable]'] = TRUE;
|
||||
// Track whether there is at least one experimental module.
|
||||
if ($module->info['package'] == 'Core (Experimental)') {
|
||||
$experimental = TRUE;
|
||||
}
|
||||
}
|
||||
$this->drupalPostForm('admin/modules', $edit, t('Install'));
|
||||
|
||||
// If there are experimental modules, click the confirm form.
|
||||
if ($experimental) {
|
||||
$this->assertText('Are you sure you wish to enable experimental modules?');
|
||||
$this->drupalPostForm(NULL, [], t('Continue'));
|
||||
}
|
||||
// The string tested here is translatable but we are only using a part of it
|
||||
// so using a translated string is wrong. Doing so would create a new string
|
||||
// to translate.
|
||||
$this->assertText(new FormattableMarkup('@count modules have been enabled: ', ['@count' => count($all_modules)]), 'Modules status has been updated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a module is not yet installed.
|
||||
*
|
||||
* @param string $name
|
||||
* Name of the module to check.
|
||||
*/
|
||||
protected function assertModuleNotInstalled($name) {
|
||||
$this->assertModules([$name], FALSE);
|
||||
$this->assertModuleTablesDoNotExist($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a module was successfully installed.
|
||||
*
|
||||
* @param string $name
|
||||
* Name of the module to check.
|
||||
*/
|
||||
protected function assertModuleSuccessfullyInstalled($name) {
|
||||
$this->assertModules([$name], TRUE);
|
||||
$this->assertModuleTablesExist($name);
|
||||
$this->assertModuleConfig($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls a module and asserts that it was done correctly.
|
||||
*
|
||||
* @param string $module
|
||||
* The name of the module to uninstall.
|
||||
* @param string $package
|
||||
* (optional) The package of the module to uninstall. Defaults
|
||||
* to 'Core'.
|
||||
*/
|
||||
protected function assertSuccessfulUninstall($module, $package = 'Core') {
|
||||
$edit = [];
|
||||
$edit['uninstall[' . $module . ']'] = TRUE;
|
||||
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
|
||||
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
|
||||
$this->assertModules([$module], FALSE);
|
||||
|
||||
// Check that the appropriate hook was fired and the appropriate log
|
||||
// message appears. (But don't check for the log message if the dblog
|
||||
// module was just uninstalled, since the {watchdog} table won't be there
|
||||
// anymore.)
|
||||
$this->assertText(t('hook_modules_uninstalled fired for @module', ['@module' => $module]));
|
||||
$this->assertLogMessage('system', "%module module uninstalled.", ['%module' => $module], RfcLogLevel::INFO);
|
||||
|
||||
// Check that the module's database tables no longer exist.
|
||||
$this->assertModuleTablesDoNotExist($module);
|
||||
// Check that the module's config files no longer exist.
|
||||
$this->assertNoModuleConfig($module);
|
||||
$this->assertUninstallModuleUpdates($module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the module post update functions after install.
|
||||
*
|
||||
* @param string $module
|
||||
* The module that got installed.
|
||||
*/
|
||||
protected function assertInstallModuleUpdates($module) {
|
||||
/** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
|
||||
$post_update_registry = \Drupal::service('update.post_update_registry');
|
||||
$all_update_functions = $post_update_registry->getPendingUpdateFunctions();
|
||||
$empty_result = TRUE;
|
||||
foreach ($all_update_functions as $function) {
|
||||
list($function_module,) = explode('_post_update_', $function);
|
||||
if ($module === $function_module) {
|
||||
$empty_result = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($empty_result, 'Ensures that no pending post update functions are available.');
|
||||
|
||||
$existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
|
||||
switch ($module) {
|
||||
case 'block':
|
||||
$this->assertFalse(array_diff(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates));
|
||||
break;
|
||||
case 'update_test_postupdate':
|
||||
$this->assertFalse(array_diff(['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0'], $existing_updates));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the module post update functions after uninstall.
|
||||
*
|
||||
* @param string $module
|
||||
* The module that got installed.
|
||||
*/
|
||||
protected function assertUninstallModuleUpdates($module) {
|
||||
/** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
|
||||
$post_update_registry = \Drupal::service('update.post_update_registry');
|
||||
$all_update_functions = $post_update_registry->getPendingUpdateFunctions();
|
||||
|
||||
switch ($module) {
|
||||
case 'block':
|
||||
$this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $all_update_functions), 'Asserts that no pending post update functions are available.');
|
||||
|
||||
$existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
|
||||
$this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates), 'Asserts that no post update functions are stored in keyvalue store.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a module's help.
|
||||
*
|
||||
* Verifies that the module help page from hook_help() exists and can be
|
||||
* displayed, and that it contains the phrase "Foo Bar module", where "Foo
|
||||
* Bar" is the name of the module from the .info.yml file.
|
||||
*
|
||||
* @param string $module
|
||||
* Machine name of the module to verify.
|
||||
* @param string $name
|
||||
* Human-readable name of the module to verify.
|
||||
*/
|
||||
protected function assertHelp($module, $name) {
|
||||
$this->drupalGet('admin/help/' . $module);
|
||||
$this->assertResponse(200, "Help for $module displayed successfully");
|
||||
$this->assertText($name . ' module', "'$name module' is on the help page for $module");
|
||||
$this->assertLink('online documentation for the ' . $name . ' module', 0, "Correct online documentation link is in the help page for $module");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes forum taxonomy terms, so Forum can be uninstalled.
|
||||
*/
|
||||
protected function preUninstallForum() {
|
||||
// There only should be a 'General discussion' term in the 'forums'
|
||||
// vocabulary, but just delete any terms there in case the name changes.
|
||||
$query = \Drupal::entityQuery('taxonomy_term');
|
||||
$query->condition('vid', 'forums');
|
||||
$ids = $query->execute();
|
||||
$storage = \Drupal::entityManager()->getStorage('taxonomy_term');
|
||||
$terms = $storage->loadMultiple($ids);
|
||||
$storage->delete($terms);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -56,8 +56,9 @@ abstract class ModuleTestBase extends BrowserTestBase {
|
|||
public function assertModuleTablesExist($module) {
|
||||
$tables = array_keys(drupal_get_module_schema($module));
|
||||
$tables_exist = TRUE;
|
||||
$schema = Database::getConnection()->schema();
|
||||
foreach ($tables as $table) {
|
||||
if (!db_table_exists($table)) {
|
||||
if (!$schema->tableExists($table)) {
|
||||
$tables_exist = FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,8 +74,9 @@ abstract class ModuleTestBase extends BrowserTestBase {
|
|||
public function assertModuleTablesDoNotExist($module) {
|
||||
$tables = array_keys(drupal_get_module_schema($module));
|
||||
$tables_exist = FALSE;
|
||||
$schema = Database::getConnection()->schema();
|
||||
foreach ($tables as $table) {
|
||||
if (db_table_exists($table)) {
|
||||
if ($schema->tableExists($table)) {
|
||||
$tables_exist = TRUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -87,8 +89,10 @@ abstract class ModuleTestBase extends BrowserTestBase {
|
|||
* @param string $module
|
||||
* The name of the module.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if configuration has been installed, FALSE otherwise.
|
||||
* @return bool|null
|
||||
* TRUE if configuration has been installed, FALSE otherwise. Returns NULL
|
||||
* if the module configuration directory does not exist or does not contain
|
||||
* any configuration files.
|
||||
*/
|
||||
public function assertModuleConfig($module) {
|
||||
$module_config_dir = drupal_get_path('module', $module) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
||||
|
|
@ -107,16 +111,18 @@ abstract class ModuleTestBase extends BrowserTestBase {
|
|||
}
|
||||
$this->assertTrue($all_names);
|
||||
|
||||
$module_config_dependencies = \Drupal::service('config.manager')->findConfigEntityDependents('module', [$module]);
|
||||
// Look up each default configuration object name in the active
|
||||
// configuration, and if it exists, remove it from the stack.
|
||||
// Only default config that belongs to $module is guaranteed to exist; any
|
||||
// other default config depends on whether other modules are enabled. Thus,
|
||||
// list all default config once more, but filtered by $module.
|
||||
$names = $module_file_storage->listAll($module . '.');
|
||||
$names = $module_file_storage->listAll();
|
||||
foreach ($names as $key => $name) {
|
||||
if ($this->config($name)->get()) {
|
||||
unset($names[$key]);
|
||||
}
|
||||
// All configuration in a module's config/install directory should depend
|
||||
// on the module as it must be removed on uninstall or the module will not
|
||||
// be re-installable.
|
||||
$this->assertTrue(strpos($name, $module . '.') === 0 || isset($module_config_dependencies[$name]), "Configuration $name provided by $module in its config/install directory does not depend on it.");
|
||||
}
|
||||
// Verify that all configuration has been installed (which means that $names
|
||||
// is empty).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* Tests that modules which provide entity types can be uninstalled.
|
||||
*
|
||||
* @group Module
|
||||
*/
|
||||
class PrepareUninstallTest extends BrowserTestBase {
|
||||
|
||||
use TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* An array of node objects.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface[]
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
/**
|
||||
* An array of taxonomy term objects.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface[]
|
||||
*/
|
||||
protected $terms;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node', 'taxonomy', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(['administer modules']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create 10 nodes.
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$this->nodes[] = $this->drupalCreateNode(['type' => 'page']);
|
||||
$this->nodes[] = $this->drupalCreateNode(['type' => 'article']);
|
||||
}
|
||||
|
||||
// Create 3 top-level taxonomy terms, each with 11 children.
|
||||
$vocabulary = $this->createVocabulary();
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$term = $this->createTerm($vocabulary);
|
||||
$this->terms[] = $term;
|
||||
for ($j = 1; $j <= 11; $j++) {
|
||||
$this->terms[] = $this->createTerm($vocabulary, ['parent' => ['target_id' => $term->id()]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Node and Taxonomy can be uninstalled.
|
||||
*/
|
||||
public function testUninstall() {
|
||||
// Check that Taxonomy cannot be uninstalled yet.
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertText('Remove content items');
|
||||
$this->assertLinkByHref('admin/modules/uninstall/entity/taxonomy_term');
|
||||
|
||||
// Delete Taxonomy term data.
|
||||
$this->drupalGet('admin/modules/uninstall/entity/taxonomy_term');
|
||||
$term_count = count($this->terms);
|
||||
for ($i = 1; $i < 11; $i++) {
|
||||
$this->assertText($this->terms[$term_count - $i]->label());
|
||||
}
|
||||
$term_count = $term_count - 10;
|
||||
$this->assertText("And $term_count more taxonomy terms.");
|
||||
$this->assertText('This action cannot be undone.');
|
||||
$this->assertText('Make a backup of your database if you want to be able to restore these items.');
|
||||
$this->drupalPostForm(NULL, [], t('Delete all taxonomy terms'));
|
||||
|
||||
// Check that we are redirected to the uninstall page and data has been
|
||||
// removed.
|
||||
$this->assertUrl('admin/modules/uninstall', []);
|
||||
$this->assertText('All taxonomy terms have been deleted.');
|
||||
|
||||
// Check that there is no more data to be deleted, Taxonomy is ready to be
|
||||
// uninstalled.
|
||||
$this->assertText('Enables the categorization of content.');
|
||||
$this->assertNoLinkByHref('admin/modules/uninstall/entity/taxonomy_term');
|
||||
|
||||
// Uninstall the Taxonomy module.
|
||||
$this->drupalPostForm('admin/modules/uninstall', ['uninstall[taxonomy]' => TRUE], t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, [], t('Uninstall'));
|
||||
$this->assertText('The selected modules have been uninstalled.');
|
||||
$this->assertNoText('Enables the categorization of content.');
|
||||
|
||||
// Check Node cannot be uninstalled yet, there is content to be removed.
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertText('Remove content items');
|
||||
$this->assertLinkByHref('admin/modules/uninstall/entity/node');
|
||||
|
||||
// Delete Node data.
|
||||
$this->drupalGet('admin/modules/uninstall/entity/node');
|
||||
// All 10 nodes should be listed.
|
||||
foreach ($this->nodes as $node) {
|
||||
$this->assertText($node->label());
|
||||
}
|
||||
|
||||
// Ensures there is no more count when not necessary.
|
||||
$this->assertNoText('And 0 more content');
|
||||
$this->assertText('This action cannot be undone.');
|
||||
$this->assertText('Make a backup of your database if you want to be able to restore these items.');
|
||||
|
||||
// Create another node so we have 11.
|
||||
$this->nodes[] = $this->drupalCreateNode(['type' => 'page']);
|
||||
$this->drupalGet('admin/modules/uninstall/entity/node');
|
||||
// Ensures singular case is used when a single entity is left after listing
|
||||
// the first 10's labels.
|
||||
$this->assertText('And 1 more content item.');
|
||||
|
||||
// Create another node so we have 12.
|
||||
$this->nodes[] = $this->drupalCreateNode(['type' => 'article']);
|
||||
$this->drupalGet('admin/modules/uninstall/entity/node');
|
||||
// Ensures singular case is used when a single entity is left after listing
|
||||
// the first 10's labels.
|
||||
$this->assertText('And 2 more content items.');
|
||||
|
||||
$this->drupalPostForm(NULL, [], t('Delete all content items'));
|
||||
|
||||
// Check we are redirected to the uninstall page and data has been removed.
|
||||
$this->assertUrl('admin/modules/uninstall', []);
|
||||
$this->assertText('All content items have been deleted.');
|
||||
|
||||
// Check there is no more data to be deleted, Node is ready to be
|
||||
// uninstalled.
|
||||
$this->assertText('Allows content to be submitted to the site and displayed on pages.');
|
||||
$this->assertNoLinkByHref('admin/modules/uninstall/entity/node');
|
||||
|
||||
// Uninstall Node module.
|
||||
$this->drupalPostForm('admin/modules/uninstall', ['uninstall[node]' => TRUE], t('Uninstall'));
|
||||
$this->drupalPostForm(NULL, [], t('Uninstall'));
|
||||
$this->assertText('The selected modules have been uninstalled.');
|
||||
$this->assertNoText('Allows content to be submitted to the site and displayed on pages.');
|
||||
|
||||
// Ensure the proper response when accessing a non-existent entity type.
|
||||
$this->drupalGet('admin/modules/uninstall/entity/node');
|
||||
$this->assertResponse(404, 'Entity types that do not exist result in a 404.');
|
||||
|
||||
// Test an entity type which does not have any existing entities.
|
||||
$this->drupalGet('admin/modules/uninstall/entity/entity_test_no_label');
|
||||
$this->assertText('There are 0 entity test without label entities to delete.');
|
||||
$button_xpath = '//input[@type="submit"][@value="Delete all entity test without label entities"]';
|
||||
$this->assertNoFieldByXPath($button_xpath, NULL, 'Button with value "Delete all entity test without label entities" not found');
|
||||
|
||||
// Test an entity type without a label.
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity.manager')
|
||||
->getStorage('entity_test_no_label');
|
||||
$storage->create([
|
||||
'id' => mb_strtolower($this->randomMachineName()),
|
||||
'name' => $this->randomMachineName(),
|
||||
])->save();
|
||||
$this->drupalGet('admin/modules/uninstall/entity/entity_test_no_label');
|
||||
$this->assertText('This will delete 1 entity test without label.');
|
||||
$this->assertFieldByXPath($button_xpath, NULL, 'Button with value "Delete all entity test without label entities" found');
|
||||
$storage->create([
|
||||
'id' => mb_strtolower($this->randomMachineName()),
|
||||
'name' => $this->randomMachineName(),
|
||||
])->save();
|
||||
$this->drupalGet('admin/modules/uninstall/entity/entity_test_no_label');
|
||||
$this->assertText('This will delete 2 entity test without label entities.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Entity\EntityMalformedException;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
|
@ -57,6 +57,17 @@ class UninstallTest extends BrowserTestBase {
|
|||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertTitle(t('Uninstall') . ' | Drupal');
|
||||
|
||||
foreach (\Drupal::service('extension.list.module')->getAllInstalledInfo() as $module => $info) {
|
||||
$field_name = "uninstall[$module]";
|
||||
if (!empty($info['required'])) {
|
||||
// A required module should not be listed on the uninstall page.
|
||||
$this->assertSession()->fieldNotExists($field_name);
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->fieldExists($field_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Be sure labels are rendered properly.
|
||||
// @see regression https://www.drupal.org/node/2512106
|
||||
$this->assertRaw('<label for="edit-uninstall-node" class="module-name table-filter-text-source">Node</label>');
|
||||
|
|
@ -103,7 +114,7 @@ class UninstallTest extends BrowserTestBase {
|
|||
// cleared during the uninstall.
|
||||
\Drupal::cache()->set('uninstall_test', 'test_uninstall_page', Cache::PERMANENT);
|
||||
$cached = \Drupal::cache()->get('uninstall_test');
|
||||
$this->assertEqual($cached->data, 'test_uninstall_page', SafeMarkup::format('Cache entry found: @bin', ['@bin' => $cached->data]));
|
||||
$this->assertEqual($cached->data, 'test_uninstall_page', new FormattableMarkup('Cache entry found: @bin', ['@bin' => $cached->data]));
|
||||
|
||||
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
|
||||
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Module;
|
||||
|
||||
/**
|
||||
* Tests module version dependencies.
|
||||
*
|
||||
* @group Module
|
||||
*/
|
||||
class VersionTest extends ModuleTestBase {
|
||||
|
||||
/**
|
||||
* Test version dependencies.
|
||||
*/
|
||||
public function testModuleVersions() {
|
||||
$dependencies = [
|
||||
// Alternating between being compatible and incompatible with 8.x-2.4-beta3.
|
||||
// The first is always a compatible.
|
||||
'common_test',
|
||||
// Branch incompatibility.
|
||||
'common_test (1.x)',
|
||||
// Branch compatibility.
|
||||
'common_test (2.x)',
|
||||
// Another branch incompatibility.
|
||||
'common_test (>2.x)',
|
||||
// Another branch compatibility.
|
||||
'common_test (<=2.x)',
|
||||
// Another branch incompatibility.
|
||||
'common_test (<2.x)',
|
||||
// Another branch compatibility.
|
||||
'common_test (>=2.x)',
|
||||
// Nonsense, misses a dash. Incompatible with everything.
|
||||
'common_test (=8.x2.x, >=2.4)',
|
||||
// Core version is optional. Compatible.
|
||||
'common_test (=8.x-2.x, >=2.4-alpha2)',
|
||||
// Test !=, explicitly incompatible.
|
||||
'common_test (=2.x, !=2.4-beta3)',
|
||||
// Three operations. Compatible.
|
||||
'common_test (=2.x, !=2.3, <2.5)',
|
||||
// Testing extra version. Incompatible.
|
||||
'common_test (<=2.4-beta2)',
|
||||
// Testing extra version. Compatible.
|
||||
'common_test (>2.4-beta2)',
|
||||
// Testing extra version. Incompatible.
|
||||
'common_test (>2.4-rc0)',
|
||||
];
|
||||
\Drupal::state()->set('system_test.dependencies', $dependencies);
|
||||
$n = count($dependencies);
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$this->drupalGet('admin/modules');
|
||||
$checkbox = $this->xpath('//input[@id="edit-modules-module-test-enable"]');
|
||||
$this->assertEqual(!empty($checkbox[0]->getAttribute('disabled')), $i % 2, $dependencies[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Page;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests default HTML metatags on a page.
|
||||
*
|
||||
* @group Page
|
||||
*/
|
||||
class DefaultMetatagsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Tests meta tags.
|
||||
*/
|
||||
public function testMetaTag() {
|
||||
$this->drupalGet('');
|
||||
// Ensures that the charset metatag is on the page.
|
||||
$result = $this->xpath('//meta[@charset="utf-8"]');
|
||||
$this->assertEqual(count($result), 1);
|
||||
|
||||
// Ensure that the charset one is the first metatag.
|
||||
$result = $this->xpath('//meta');
|
||||
$this->assertEqual((string) $result[0]->getAttribute('charset'), 'utf-8');
|
||||
|
||||
// Ensure that the shortcut icon is on the page.
|
||||
$result = $this->xpath('//link[@rel = "shortcut icon"]');
|
||||
$this->assertEqual(count($result), 1, 'The shortcut icon is present.');
|
||||
}
|
||||
|
||||
}
|
||||
333
web/core/modules/system/tests/src/Functional/Pager/PagerTest.php
Normal file
333
web/core/modules/system/tests/src/Functional/Pager/PagerTest.php
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Pager;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests pager functionality.
|
||||
*
|
||||
* @group Pager
|
||||
*/
|
||||
class PagerTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['dblog', 'pager_test'];
|
||||
|
||||
/**
|
||||
* A user with permission to access site reports.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
protected $profile = 'testing';
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Insert 300 log messages.
|
||||
$logger = $this->container->get('logger.factory')->get('pager_test');
|
||||
for ($i = 0; $i < 300; $i++) {
|
||||
$logger->debug($this->randomString());
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access site reports',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests markup and CSS classes of pager links.
|
||||
*/
|
||||
public function testActiveClass() {
|
||||
// Verify first page.
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$current_page = 0;
|
||||
$this->assertPagerItems($current_page);
|
||||
|
||||
// Verify any page but first/last.
|
||||
$current_page++;
|
||||
$this->drupalGet('admin/reports/dblog', ['query' => ['page' => $current_page]]);
|
||||
$this->assertPagerItems($current_page);
|
||||
|
||||
// Verify last page.
|
||||
$elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--last']);
|
||||
preg_match('@page=(\d+)@', $elements[0]->getAttribute('href'), $matches);
|
||||
$current_page = (int) $matches[1];
|
||||
$this->drupalGet($GLOBALS['base_root'] . parse_url($this->getUrl())['path'] . $elements[0]->getAttribute('href'), ['external' => TRUE]);
|
||||
$this->assertPagerItems($current_page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test proper functioning of the query parameters and the pager cache context.
|
||||
*/
|
||||
public function testPagerQueryParametersAndCacheContext() {
|
||||
// First page.
|
||||
$this->drupalGet('pager-test/query-parameters');
|
||||
$this->assertText(t('Pager calls: 0'), 'Initial call to pager shows 0 calls.');
|
||||
$this->assertText('[url.query_args.pagers:0]=0.0');
|
||||
$this->assertCacheContext('url.query_args');
|
||||
|
||||
// Go to last page, the count of pager calls need to go to 1.
|
||||
$elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--last']);
|
||||
$elements[0]->click();
|
||||
$this->assertText(t('Pager calls: 1'), 'First link call to pager shows 1 calls.');
|
||||
$this->assertText('[url.query_args.pagers:0]=0.60');
|
||||
$this->assertCacheContext('url.query_args');
|
||||
|
||||
// Reset counter to 0.
|
||||
$this->drupalGet('pager-test/query-parameters');
|
||||
// Go back to first page, the count of pager calls need to go to 2.
|
||||
$elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--last']);
|
||||
$elements[0]->click();
|
||||
$elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--first']);
|
||||
$elements[0]->click();
|
||||
$this->assertText(t('Pager calls: 2'), 'Second link call to pager shows 2 calls.');
|
||||
$this->assertText('[url.query_args.pagers:0]=0.0');
|
||||
$this->assertCacheContext('url.query_args');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test proper functioning of multiple pagers.
|
||||
*/
|
||||
public function testMultiplePagers() {
|
||||
// First page.
|
||||
$this->drupalGet('pager-test/multiple-pagers');
|
||||
|
||||
// Test data.
|
||||
// Expected URL query string param is 0-indexed.
|
||||
// Expected page per pager is 1-indexed.
|
||||
$test_data = [
|
||||
// With no query, all pagers set to first page.
|
||||
[
|
||||
'input_query' => NULL,
|
||||
'expected_page' => [0 => '1', 1 => '1', 4 => '1'],
|
||||
'expected_query' => '?page=0,0,,,0',
|
||||
],
|
||||
// Blanks around page numbers should not be relevant.
|
||||
[
|
||||
'input_query' => '?page=2 , 10,,, 5 ,,',
|
||||
'expected_page' => [0 => '3', 1 => '11', 4 => '6'],
|
||||
'expected_query' => '?page=2,10,,,5',
|
||||
],
|
||||
// Blanks within page numbers should lead to only the first integer
|
||||
// to be considered.
|
||||
[
|
||||
'input_query' => '?page=2 , 3 0,,, 4 13 ,,',
|
||||
'expected_page' => [0 => '3', 1 => '4', 4 => '5'],
|
||||
'expected_query' => '?page=2,3,,,4',
|
||||
],
|
||||
// If floats are passed as page numbers, only the integer part is
|
||||
// returned.
|
||||
[
|
||||
'input_query' => '?page=2.1,6.999,,,5.',
|
||||
'expected_page' => [0 => '3', 1 => '7', 4 => '6'],
|
||||
'expected_query' => '?page=2,6,,,5',
|
||||
],
|
||||
// Partial page fragment, undefined pagers set to first page.
|
||||
[
|
||||
'input_query' => '?page=5,2',
|
||||
'expected_page' => [0 => '6', 1 => '3', 4 => '1'],
|
||||
'expected_query' => '?page=5,2,,,0',
|
||||
],
|
||||
// Partial page fragment, undefined pagers set to first page.
|
||||
[
|
||||
'input_query' => '?page=,2',
|
||||
'expected_page' => [0 => '1', 1 => '3', 4 => '1'],
|
||||
'expected_query' => '?page=0,2,,,0',
|
||||
],
|
||||
// Partial page fragment, undefined pagers set to first page.
|
||||
[
|
||||
'input_query' => '?page=,',
|
||||
'expected_page' => [0 => '1', 1 => '1', 4 => '1'],
|
||||
'expected_query' => '?page=0,0,,,0',
|
||||
],
|
||||
// With overflow pages, all pagers set to max page.
|
||||
[
|
||||
'input_query' => '?page=99,99,,,99',
|
||||
'expected_page' => [0 => '16', 1 => '16', 4 => '16'],
|
||||
'expected_query' => '?page=15,15,,,15',
|
||||
],
|
||||
// Wrong value for the page resets pager to first page.
|
||||
[
|
||||
'input_query' => '?page=bar,5,foo,qux,bet',
|
||||
'expected_page' => [0 => '1', 1 => '6', 4 => '1'],
|
||||
'expected_query' => '?page=0,5,,,0',
|
||||
],
|
||||
];
|
||||
|
||||
// We loop through the page with the test data query parameters, and check
|
||||
// that the active page for each pager element has the expected page
|
||||
// (1-indexed) and resulting query parameter
|
||||
foreach ($test_data as $data) {
|
||||
$input_query = str_replace(' ', '%20', $data['input_query']);
|
||||
$this->drupalGet($GLOBALS['base_root'] . parse_url($this->getUrl())['path'] . $input_query, ['external' => TRUE]);
|
||||
foreach ([0, 1, 4] as $pager_element) {
|
||||
$active_page = $this->cssSelect("div.test-pager-{$pager_element} ul.pager__items li.is-active:contains('{$data['expected_page'][$pager_element]}')");
|
||||
$destination = str_replace('%2C', ',', $active_page[0]->find('css', 'a')->getAttribute('href'));
|
||||
$this->assertEqual($destination, $data['expected_query']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test proper functioning of the ellipsis.
|
||||
*/
|
||||
public function testPagerEllipsis() {
|
||||
// Insert 100 extra log messages to get 9 pages.
|
||||
$logger = $this->container->get('logger.factory')->get('pager_test');
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$logger->debug($this->randomString());
|
||||
}
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$elements = $this->cssSelect(".pager__item--ellipsis:contains('…')");
|
||||
$this->assertEqual(count($elements), 0, 'No ellipsis has been set.');
|
||||
|
||||
// Insert an extra 50 log messages to get 10 pages.
|
||||
$logger = $this->container->get('logger.factory')->get('pager_test');
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
$logger->debug($this->randomString());
|
||||
}
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$elements = $this->cssSelect(".pager__item--ellipsis:contains('…')");
|
||||
$this->assertEqual(count($elements), 1, 'Found the ellipsis.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts pager items and links.
|
||||
*
|
||||
* @param int $current_page
|
||||
* The current pager page the internal browser is on.
|
||||
*/
|
||||
protected function assertPagerItems($current_page) {
|
||||
$elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']);
|
||||
$this->assertTrue(!empty($elements), 'Pager found.');
|
||||
|
||||
// Make current page 1-based.
|
||||
$current_page++;
|
||||
|
||||
// Extract first/previous and next/last items.
|
||||
// first/previous only exist, if the current page is not the first.
|
||||
if ($current_page > 1) {
|
||||
$first = array_shift($elements);
|
||||
$previous = array_shift($elements);
|
||||
}
|
||||
// next/last always exist, unless the current page is the last.
|
||||
if ($current_page != count($elements)) {
|
||||
$last = array_pop($elements);
|
||||
$next = array_pop($elements);
|
||||
}
|
||||
|
||||
// We remove elements from the $elements array in the following code, so
|
||||
// we store the total number of pages for verifying the "last" link.
|
||||
$total_pages = count($elements);
|
||||
|
||||
// Verify items and links to pages.
|
||||
foreach ($elements as $page => $element) {
|
||||
// Make item/page index 1-based.
|
||||
$page++;
|
||||
|
||||
if ($current_page == $page) {
|
||||
$this->assertClass($element, 'is-active', 'Element for current page has .is-active class.');
|
||||
$link = $element->find('css', 'a');
|
||||
$this->assertTrue($link, 'Element for current page has link.');
|
||||
$destination = $link->getAttribute('href');
|
||||
// URL query string param is 0-indexed.
|
||||
$this->assertEqual($destination, '?page=' . ($page - 1));
|
||||
}
|
||||
else {
|
||||
$this->assertNoClass($element, 'is-active', "Element for page $page has no .is-active class.");
|
||||
$this->assertClass($element, 'pager__item', "Element for page $page has .pager__item class.");
|
||||
$link = $element->find('css', 'a');
|
||||
$this->assertTrue($link, "Link to page $page found.");
|
||||
$destination = $link->getAttribute('href');
|
||||
$this->assertEqual($destination, '?page=' . ($page - 1));
|
||||
}
|
||||
unset($elements[--$page]);
|
||||
}
|
||||
// Verify that no other items remain untested.
|
||||
$this->assertTrue(empty($elements), 'All expected items found.');
|
||||
|
||||
// Verify first/previous and next/last items and links.
|
||||
if (isset($first)) {
|
||||
$this->assertClass($first, 'pager__item--first', 'Element for first page has .pager__item--first class.');
|
||||
$link = $first->find('css', 'a');
|
||||
$this->assertTrue($link, 'Link to first page found.');
|
||||
$this->assertNoClass($link, 'is-active', 'Link to first page is not active.');
|
||||
$destination = $link->getAttribute('href');
|
||||
$this->assertEqual($destination, '?page=0');
|
||||
}
|
||||
if (isset($previous)) {
|
||||
$this->assertClass($previous, 'pager__item--previous', 'Element for first page has .pager__item--previous class.');
|
||||
$link = $previous->find('css', 'a');
|
||||
$this->assertTrue($link, 'Link to previous page found.');
|
||||
$this->assertNoClass($link, 'is-active', 'Link to previous page is not active.');
|
||||
$destination = $link->getAttribute('href');
|
||||
// URL query string param is 0-indexed, $current_page is 1-indexed.
|
||||
$this->assertEqual($destination, '?page=' . ($current_page - 2));
|
||||
}
|
||||
if (isset($next)) {
|
||||
$this->assertClass($next, 'pager__item--next', 'Element for next page has .pager__item--next class.');
|
||||
$link = $next->find('css', 'a');
|
||||
$this->assertTrue($link, 'Link to next page found.');
|
||||
$this->assertNoClass($link, 'is-active', 'Link to next page is not active.');
|
||||
$destination = $link->getAttribute('href');
|
||||
// URL query string param is 0-indexed, $current_page is 1-indexed.
|
||||
$this->assertEqual($destination, '?page=' . $current_page);
|
||||
}
|
||||
if (isset($last)) {
|
||||
$link = $last->find('css', 'a');
|
||||
$this->assertClass($last, 'pager__item--last', 'Element for last page has .pager__item--last class.');
|
||||
$this->assertTrue($link, 'Link to last page found.');
|
||||
$this->assertNoClass($link, 'is-active', 'Link to last page is not active.');
|
||||
$destination = $link->getAttribute('href');
|
||||
// URL query string param is 0-indexed.
|
||||
$this->assertEqual($destination, '?page=' . ($total_pages - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an element has a given class.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $element
|
||||
* The element to test.
|
||||
* @param string $class
|
||||
* The class to assert.
|
||||
* @param string $message
|
||||
* (optional) A verbose message to output.
|
||||
*/
|
||||
protected function assertClass(NodeElement $element, $class, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = "Class .$class found.";
|
||||
}
|
||||
$this->assertTrue($element->hasClass($class) !== FALSE, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an element does not have a given class.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $element
|
||||
* The element to test.
|
||||
* @param string $class
|
||||
* The class to assert.
|
||||
* @param string $message
|
||||
* (optional) A verbose message to output.
|
||||
*/
|
||||
protected function assertNoClass(NodeElement $element, $class, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = "Class .$class not found.";
|
||||
}
|
||||
$this->assertTrue($element->hasClass($class) === FALSE, $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -67,9 +67,9 @@ class AjaxPageStateTest extends BrowserTestBase {
|
|||
"query" =>
|
||||
[
|
||||
'ajax_page_state' => [
|
||||
'libraries' => 'core/html5shiv'
|
||||
]
|
||||
]
|
||||
'libraries' => 'core/html5shiv',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->assertNoRaw(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Render;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional tests for HtmlResponseAttachmentsProcessor.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class HtmlResponseAttachmentsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['render_attached_test'];
|
||||
|
||||
/**
|
||||
* Test rendering of ['#attached'].
|
||||
*/
|
||||
public function testAttachments() {
|
||||
// Test ['#attached']['http_header] = ['Status', $code].
|
||||
$this->drupalGet('/render_attached_test/teapot');
|
||||
$this->assertResponse(418);
|
||||
$this->assertHeader('X-Drupal-Cache', 'MISS');
|
||||
// Repeat for the cache.
|
||||
$this->drupalGet('/render_attached_test/teapot');
|
||||
$this->assertResponse(418);
|
||||
$this->assertHeader('X-Drupal-Cache', 'HIT');
|
||||
|
||||
// Test ['#attached']['http_header'] with various replacement rules.
|
||||
$this->drupalGet('/render_attached_test/header');
|
||||
$this->assertTeapotHeaders();
|
||||
$this->assertHeader('X-Drupal-Cache', 'MISS');
|
||||
// Repeat for the cache.
|
||||
$this->drupalGet('/render_attached_test/header');
|
||||
$this->assertHeader('X-Drupal-Cache', 'HIT');
|
||||
|
||||
// Test ['#attached']['feed'].
|
||||
$this->drupalGet('/render_attached_test/feed');
|
||||
$this->assertHeader('X-Drupal-Cache', 'MISS');
|
||||
$this->assertFeed();
|
||||
// Repeat for the cache.
|
||||
$this->drupalGet('/render_attached_test/feed');
|
||||
$this->assertHeader('X-Drupal-Cache', 'HIT');
|
||||
|
||||
// Test ['#attached']['html_head'].
|
||||
$this->drupalGet('/render_attached_test/head');
|
||||
$this->assertHeader('X-Drupal-Cache', 'MISS');
|
||||
$this->assertHead();
|
||||
// Repeat for the cache.
|
||||
$this->drupalGet('/render_attached_test/head');
|
||||
$this->assertHeader('X-Drupal-Cache', 'HIT');
|
||||
|
||||
// Test ['#attached']['html_head_link'] when outputted as HTTP header.
|
||||
$this->drupalGet('/render_attached_test/html_header_link');
|
||||
$expected_link_headers = [
|
||||
'</foo?bar=<baz>&baz=false>; rel="alternate"',
|
||||
'</foo/bar>; hreflang="nl"; rel="alternate"',
|
||||
];
|
||||
$this->assertEqual($this->getSession()->getResponseHeaders()['Link'], $expected_link_headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test caching of ['#attached'].
|
||||
*/
|
||||
public function testRenderCachedBlock() {
|
||||
// Make sure our test block is visible.
|
||||
$this->drupalPlaceBlock('attached_rendering_block', ['region' => 'content']);
|
||||
|
||||
// Get the front page, which should now have our visible block.
|
||||
$this->drupalGet('');
|
||||
// Make sure our block is visible.
|
||||
$this->assertText('Markup from attached_rendering_block.');
|
||||
// Test that all our attached items are present.
|
||||
$this->assertFeed();
|
||||
$this->assertHead();
|
||||
$this->assertResponse(418);
|
||||
$this->assertTeapotHeaders();
|
||||
|
||||
// Reload the page, to test caching.
|
||||
$this->drupalGet('');
|
||||
// Make sure our block is visible.
|
||||
$this->assertText('Markup from attached_rendering_block.');
|
||||
// The header should be present again.
|
||||
$this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make assertions about added HTTP headers.
|
||||
*/
|
||||
protected function assertTeapotHeaders() {
|
||||
$headers = $this->getSession()->getResponseHeaders();
|
||||
$this->assertEquals($headers['X-Test-Teapot'], ['Teapot Mode Active']);
|
||||
$this->assertEquals($headers['X-Test-Teapot-Replace'], ['Teapot replaced']);
|
||||
$this->assertEquals($headers['X-Test-Teapot-No-Replace'], ['This value is not replaced', 'This one is added']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make assertions about the presence of an RSS feed.
|
||||
*/
|
||||
protected function assertFeed() {
|
||||
// Discover the DOM element for the feed link.
|
||||
$test_meta = $this->xpath('//head/link[@href="test://url"]');
|
||||
$this->assertEqual(1, count($test_meta), 'Link has URL.');
|
||||
// Reconcile the other attributes.
|
||||
$test_meta_attributes = [
|
||||
'href' => 'test://url',
|
||||
'rel' => 'alternate',
|
||||
'type' => 'application/rss+xml',
|
||||
'title' => 'Your RSS feed.',
|
||||
];
|
||||
$test_meta = reset($test_meta);
|
||||
if (empty($test_meta)) {
|
||||
$this->fail('Unable to find feed link.');
|
||||
}
|
||||
else {
|
||||
foreach ($test_meta_attributes as $attribute => $value) {
|
||||
$this->assertEquals($value, $test_meta->getAttribute($attribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make assertions about HTML head elements.
|
||||
*/
|
||||
protected function assertHead() {
|
||||
// Discover the DOM element for the meta link.
|
||||
$test_meta = $this->xpath('//head/meta[@test-attribute="testvalue"]');
|
||||
$this->assertEqual(1, count($test_meta), 'There\'s only one test attribute.');
|
||||
// Grab the only DOM element.
|
||||
$test_meta = reset($test_meta);
|
||||
if (empty($test_meta)) {
|
||||
$this->fail('Unable to find the head meta.');
|
||||
}
|
||||
else {
|
||||
$this->assertEqual($test_meta->getAttribute('test-attribute'), 'testvalue');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Render;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional test verifying that render array throws 406 for non-HTML requests.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class RenderArrayNonHtmlSubscriberTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['render_array_non_html_subscriber_test'];
|
||||
|
||||
/**
|
||||
* Tests handling of responses by events subscriber.
|
||||
*/
|
||||
public function testResponses() {
|
||||
// Test that event subscriber does not interfere with normal requests.
|
||||
$url = Url::fromRoute('render_array_non_html_subscriber_test.render_array');
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertRaw(t('Controller response successfully rendered.'));
|
||||
|
||||
// Test that correct response code is returned for any non-HTML format.
|
||||
foreach (['json', 'hal+json', 'xml', 'foo'] as $format) {
|
||||
$url = Url::fromRoute('render_array_non_html_subscriber_test.render_array', [
|
||||
'_format' => $format,
|
||||
]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(406);
|
||||
$this->assertNoRaw(t('Controller response successfully rendered.'));
|
||||
}
|
||||
|
||||
// Test that event subscriber does not interfere with raw string responses.
|
||||
$url = Url::fromRoute('render_array_non_html_subscriber_test.raw_string', [
|
||||
'_format' => 'foo',
|
||||
]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertRaw(t('Raw controller response.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Render;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests that URL bubbleable metadata is correctly bubbled.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class UrlBubbleableMetadataBubblingTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['cache_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that URL bubbleable metadata is correctly bubbled.
|
||||
*/
|
||||
public function testUrlBubbleableMetadataBubbling() {
|
||||
// Test that regular URLs bubble up bubbleable metadata when converted to
|
||||
// string.
|
||||
$url = Url::fromRoute('cache_test.url_bubbling');
|
||||
$this->drupalGet($url);
|
||||
$this->assertCacheContext('url.site');
|
||||
$this->assertRaw($url->setAbsolute()->toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ActionJsonAnonTest extends ActionResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ActionJsonBasicAuthTest extends ActionResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ActionJsonCookieTest extends ActionResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
abstract class ActionResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'action';
|
||||
|
||||
/**
|
||||
* @var \Drupal\system\ActionConfigEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer actions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$action = Action::create([
|
||||
'id' => 'user_add_role_action.' . RoleInterface::ANONYMOUS_ID,
|
||||
'type' => 'user',
|
||||
'label' => t('Add the anonymous role to the selected users'),
|
||||
'configuration' => [
|
||||
'rid' => RoleInterface::ANONYMOUS_ID,
|
||||
],
|
||||
'plugin' => 'user_add_role_action',
|
||||
]);
|
||||
$action->save();
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'configuration' => [
|
||||
'rid' => 'anonymous',
|
||||
],
|
||||
'dependencies' => [
|
||||
'config' => ['user.role.anonymous'],
|
||||
'module' => ['user'],
|
||||
],
|
||||
'id' => 'user_add_role_action.anonymous',
|
||||
'label' => 'Add the anonymous role to the selected users',
|
||||
'langcode' => 'en',
|
||||
'plugin' => 'user_add_role_action',
|
||||
'status' => TRUE,
|
||||
'type' => 'user',
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue