Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663

This commit is contained in:
Greg Anderson 2015-10-08 11:40:12 -07:00
parent eb34d130a8
commit f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\AssertConfigTrait.
*/
namespace Drupal\KernelTests;
use Drupal\Component\Diff\Diff;
/**
* Trait to help with diffing config.
*/
trait AssertConfigTrait {
/**
* Ensures that a specific config diff does not contain unwanted changes.
*
* @param \Drupal\Component\Diff\Diff $result
* The diff result for the passed in config name.
* @param string $config_name
* The config name to check.
* @param array $skipped_config
* An array of skipped config, keyed by string. If the value is TRUE, the
* entire file will be ignored, otherwise it's an array of strings which are
* ignored.
*
* @throws \Exception
* Thrown when a configuration is different.
*/
protected function assertConfigDiff(Diff $result, $config_name, array $skipped_config) {
foreach ($result->getEdits() as $op) {
switch (get_class($op)) {
case 'Drupal\Component\Diff\Engine\DiffOpCopy':
// Nothing to do, a copy is what we expect.
break;
case 'Drupal\Component\Diff\Engine\DiffOpDelete':
case 'Drupal\Component\Diff\Engine\DiffOpChange':
// It is not part of the skipped config, so we can directly throw the
// exception.
if (!in_array($config_name, array_keys($skipped_config))) {
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
}
// Allow to skip entire config files.
if ($skipped_config[$config_name] === TRUE) {
continue;
}
// Allow to skip some specific lines of imported config files.
// Ensure that the only changed lines are the ones we marked as
// skipped.
$all_skipped = TRUE;
$changes = get_class($op) == 'Drupal\Component\Diff\Engine\DiffOpDelete' ? $op->orig : $op->closing;
foreach ($changes as $closing) {
// Skip some of the changes, as they are caused by module install
// code.
$found = FALSE;
if (!empty($skipped_config[$config_name])) {
foreach ($skipped_config[$config_name] as $line) {
if (strpos($closing, $line) !== FALSE) {
$found = TRUE;
break;
}
}
}
$all_skipped = $all_skipped && $found;
}
if (!$all_skipped) {
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
}
break;
case 'Drupal\Component\Diff\Engine\DiffOpAdd':
foreach ($op->closing as $closing) {
// The UUIDs don't exist in the default config.
if (strpos($closing, 'uuid: ') === 0) {
continue;
}
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
}
break;
default:
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
break;
}
}
}
}

View file

@ -0,0 +1,153 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Component\Utility\SafeMarkupKernelTest.
*/
namespace Drupal\KernelTests\Component\Utility;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\KernelTests\KernelTestBase;
/**
* Provides a test covering integration of SafeMarkup with other systems.
*
* @group Utility
*/
class SafeMarkupKernelTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
$this->container->get('router.builder')->rebuild();
}
/**
* Gets arguments for SafeMarkup::format() based on Url::fromUri() parameters.
*
* @param string $uri
* The URI of the resource.
*
* @param array $options
* The options to pass to Url::fromUri().
*
* @return array
* Array containing:
* - ':url': A URL string.
*/
protected static function getSafeMarkupUriArgs($uri, $options = []) {
$args[':url'] = Url::fromUri($uri, $options)->toString();
return $args;
}
/**
* Tests URL ":placeholders" in SafeMarkup::format().
*
* @dataProvider providerTestSafeMarkupUri
*/
public function testSafeMarkupUri($string, $uri, $options, $expected) {
$args = self::getSafeMarkupUriArgs($uri, $options);
$this->assertEquals($expected, SafeMarkup::format($string, $args));
}
/**
* @return array
*/
public function providerTestSafeMarkupUri() {
$data = [];
$data['routed-url'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'route:system.admin',
[],
'Hey giraffe <a href="/admin">MUUUH</a>',
];
$data['routed-with-query'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'route:system.admin',
['query' => ['bar' => 'baz#']],
'Hey giraffe <a href="/admin?bar=baz%23">MUUUH</a>',
];
$data['routed-with-fragment'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'route:system.admin',
['fragment' => 'bar&lt;'],
'Hey giraffe <a href="/admin#bar&amp;lt;">MUUUH</a>',
];
$data['unrouted-url'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'base://foo',
[],
'Hey giraffe <a href="/foo">MUUUH</a>',
];
$data['unrouted-with-query'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'base://foo',
['query' => ['bar' => 'baz#']],
'Hey giraffe <a href="/foo?bar=baz%23">MUUUH</a>',
];
$data['unrouted-with-fragment'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'base://foo',
['fragment' => 'bar&lt;'],
'Hey giraffe <a href="/foo#bar&amp;lt;">MUUUH</a>',
];
$data['mailto-protocol'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'mailto:test@example.com',
[],
'Hey giraffe <a href="mailto:test@example.com">MUUUH</a>',
];
return $data;
}
/**
* @dataProvider providerTestSafeMarkupUriWithException
* @expectedException \InvalidArgumentException
*/
public function testSafeMarkupUriWithExceptionUri($string, $uri) {
// Should throw an \InvalidArgumentException, due to Uri::toString().
$args = self::getSafeMarkupUriArgs($uri);
SafeMarkup::format($string, $args);
}
/**
* @return array
*/
public function providerTestSafeMarkupUriWithException() {
$data = [];
$data['js-protocol'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
"javascript:alert('xss')",
];
$data['js-with-fromCharCode'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
"javascript:alert(String.fromCharCode(88,83,83))",
];
$data['non-url-with-colon'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
"llamas: they are not URLs",
];
$data['non-url-with-html'] = [
'Hey giraffe <a href=":url">MUUUH</a>',
'<span>not a url</span>',
];
return $data;
}
}

View file

@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Config\DefaultConfigTest.
*/
namespace Drupal\KernelTests\Config;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\AssertConfigTrait;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that the installed config matches the default config.
*
* @group Config
*/
class DefaultConfigTest extends KernelTestBase {
use AssertConfigTrait;
/**
* {@inheritdoc}
*/
protected static $timeLimit = 500;
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// @todo ModuleInstaller calls system_rebuild_module_data which is part of
// system.module, see https://www.drupal.org/node/2208429.
include_once $this->root . '/core/modules/system/system.module';
// Set up the state values so we know where to find the files when running
// drupal_get_filename().
// @todo Remove as part of https://www.drupal.org/node/2186491
system_rebuild_module_data();
$this->installSchema('system', 'router');
}
/**
* Tests if installed config is equal to the exported config.
*
* @dataProvider providerTestModuleConfig
*/
public function testModuleConfig($module) {
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
$module_installer = $this->container->get('module_installer');
/** @var \Drupal\Core\Config\StorageInterface $active_config_storage */
$active_config_storage = $this->container->get('config.storage');
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = $this->container->get('config.manager');
$module_installer->install([$module]);
// System and user are required in order to be able to install some of the
// other modules. Therefore they are put into static::$modules, which though
// doesn't install config files, so import those config files explicitly.
switch ($module) {
case 'system':
case 'user':
$this->installConfig([$module]);
break;
}
$default_install_path = drupal_get_path('module', $module) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
$module_config_storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION);
// The following config entries are changed on module install, so compare
// them doesn't make sense.
$skipped_config = [];
$skipped_config['locale.settings'][] = 'path: ';
$skipped_config['syslog.settings'][] = 'facility: ';
// @todo Figure out why simpletest.settings is not installed.
$skipped_config['simpletest.settings'] = TRUE;
// Compare the installed config with the one in the module directory.
foreach ($module_config_storage->listAll() as $config_name) {
$result = $config_manager->diff($module_config_storage, $active_config_storage, $config_name);
$this->assertConfigDiff($result, $config_name, $skipped_config);
}
}
/**
* Test data provider for ::testModuleConfig().
*
* @return array
* An array of module names to test.
*/
public function providerTestModuleConfig() {
$module_dirs = array_keys(iterator_to_array(new \FilesystemIterator(__DIR__ . '/../../../../modules/')));
$module_names = array_map(function($path) {
return str_replace(__DIR__ . '/../../../../modules/', '', $path);
}, $module_dirs);
$modules_keyed = array_combine($module_names, $module_names);
$data = array_map(function ($module) {
return [$module];
}, $modules_keyed);
return $data;
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\Common\DrupalSetMessageTest.
*/
namespace Drupal\KernelTests\Core\Common;
use Drupal\KernelTests\KernelTestBase;
/**
* @covers ::drupal_set_message
* @group PHPUnit
*/
class DrupalSetMessageTest extends KernelTestBase {
/**
* The basic functionality of drupal_set_message().
*/
public function testDrupalSetMessage() {
drupal_set_message(t('A message: @foo', ['@foo' => 'bar']));
$messages = drupal_get_messages();
$this->assertInstanceOf('Drupal\Core\Render\Markup', $messages['status'][0]);
$this->assertEquals('A message: bar', (string) $messages['status'][0]);
}
public function tearDown() {
// Clear session to prevent global leakage.
unset($_SESSION['messages']);
parent::tearDown();
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\StringTranslation\TranslationStringTest.
*/
namespace Drupal\KernelTests\Core\StringTranslation;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the TranslatableMarkup class.
*
* @group StringTranslation
*/
class TranslationStringTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'language'
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ConfigurableLanguage::createFromLangcode('de')->save();
}
/**
* Tests that TranslatableMarkup objects can be compared.
*/
public function testComparison() {
$this->rebootAndPrepareSettings();
$a = \Drupal::service('string_translation')->translate('Example @number', ['@number' => 42], ['langcode' => 'de']);
$this->rebootAndPrepareSettings();
$b = \Drupal::service('string_translation')->translate('Example @number', ['@number' => 42], ['langcode' => 'de']);
$c = \Drupal::service('string_translation')->translate('Example @number', ['@number' => 43], ['langcode' => 'de']);
$d = \Drupal::service('string_translation')->translate('Example @number', ['@number' => 42], ['langcode' => 'en']);
// The two objects have the same settings so == comparison will work.
$this->assertEquals($a, $b);
// The two objects are not the same object.
$this->assertNotSame($a, $b);
// TranslationWrappers which have different settings are not equal.
$this->assertNotEquals($a, $c);
$this->assertNotEquals($a, $d);
}
/**
* Reboots the kernel to set custom translations in Settings.
*/
protected function rebootAndPrepareSettings() {
// Reboot the container so that different services are injected and the new
// settings are picked.
$kernel = $this->container->get('kernel');
$kernel->shutdown();
$kernel->boot();
$settings = Settings::getAll();
$settings['locale_custom_strings_de'] = ['' => ['Example @number' => 'Example @number translated']];
// Recreate the settings static.
new Settings($settings);
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\Theme\ThemeRenderAndAutoescapeTest.
*/
namespace Drupal\KernelTests\Core\Theme;
use Drupal\Component\Utility\Html;
use Drupal\Core\Link;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\Markup;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the theme_render_and_autoescape() function.
*
* @group Theme
*/
class ThemeRenderAndAutoescapeTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
}
/**
* @dataProvider providerTestThemeRenderAndAutoescape
*/
public function testThemeRenderAndAutoescape($arg, $expected) {
if (is_array($arg) && isset($arg['#type']) && $arg['#type'] === 'link') {
$arg = Link::createFromRoute($arg['#title'], $arg['#url']);
}
$context = new RenderContext();
// Use a closure here since we need to render with a render context.
$theme_render_and_autoescape = function () use ($arg) {
return theme_render_and_autoescape($arg);
};
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$output = $renderer->executeInRenderContext($context, $theme_render_and_autoescape);
$this->assertEquals($expected, $output);
$this-> assertInternalType('string', $output);
}
/**
* Provide test examples.
*/
public function providerTestThemeRenderAndAutoescape() {
return [
'empty string unchanged' => ['', ''],
'simple string unchanged' => ['ab', 'ab'],
'int (scalar) cast to string' => [111, '111'],
'float (scalar) cast to string' => [2.10, '2.10'],
'> is escaped' => ['>', '&gt;'],
'Markup EM tag is unchanged' => [Markup::create('<em>hi</em>'), '<em>hi</em>'],
'Markup SCRIPT tag is unchanged' => [Markup::create('<script>alert("hi");</script>'), '<script>alert("hi");</script>'],
'EM tag in string is escaped' => ['<em>hi</em>', Html::escape('<em>hi</em>')],
'type link render array is rendered' => [['#type' => 'link', '#title' => 'Text', '#url' => '<none>'], '<a href="">Text</a>'],
'type markup with EM tags is rendered' => [['#markup' => '<em>hi</em>'], '<em>hi</em>'],
'SCRIPT tag in string is escaped' => [
'<script>alert(123)</script>',
Html::escape('<script>alert(123)</script>')
],
'type plain_text render array EM tag is escaped' => [['#plain_text' => '<em>hi</em>'], Html::escape('<em>hi</em>')],
'type hidden render array is rendered' => [['#type' => 'hidden', '#name' => 'foo', '#value' => 'bar'], "<input type=\"hidden\" name=\"foo\" value=\"bar\" />\n"],
];
}
/**
* Ensures invalid content is handled correctly.
*
* @expectedException \Exception
*/
public function testThemeEscapeAndRenderNotPrintable() {
theme_render_and_autoescape(new NonPrintable());
}
}
class NonPrintable { }

View file

@ -7,6 +7,9 @@
namespace Drupal\KernelTests;
use Drupal\Component\FileCache\ApcuFileCacheBackend;
use Drupal\Component\FileCache\FileCache;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigImporter;
@ -20,6 +23,7 @@ use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Site\Settings;
use Drupal\simpletest\AssertContentTrait;
use Drupal\simpletest\AssertHelperTrait;
use Drupal\simpletest\RandomGeneratorTrait;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
@ -52,6 +56,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
use AssertLegacyTrait;
use AssertContentTrait;
use AssertHelperTrait;
use RandomGeneratorTrait;
/**
@ -212,6 +217,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
parent::setUp();
$this->root = static::getDrupalRoot();
$this->initFileCache();
$this->bootEnvironment();
$this->bootKernel();
}
@ -254,8 +260,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
$this->siteDirectory = vfsStream::url('root/sites/simpletest/' . $suffix);
mkdir($this->siteDirectory . '/files', 0775);
mkdir($this->siteDirectory . '/files/config/' . CONFIG_ACTIVE_DIRECTORY, 0775, TRUE);
mkdir($this->siteDirectory . '/files/config/' . CONFIG_STAGING_DIRECTORY, 0775, TRUE);
mkdir($this->siteDirectory . '/files/config/' . CONFIG_SYNC_DIRECTORY, 0775, TRUE);
// Ensure that all code that relies on drupal_valid_test_ua() can still be
// safely executed. This primarily affects the (test) site directory
@ -273,8 +278,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
new Settings($settings);
$GLOBALS['config_directories'] = array(
CONFIG_ACTIVE_DIRECTORY => $this->siteDirectory . '/files/config/active',
CONFIG_STAGING_DIRECTORY => $this->siteDirectory . '/files/config/staging',
CONFIG_SYNC_DIRECTORY => $this->siteDirectory . '/files/config/sync',
);
foreach (Database::getAllConnectionInfo() as $key => $targets) {
@ -343,6 +347,14 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
// register() is only called if a new container was built/compiled.
$this->container = $kernel->getContainer();
// Ensure database tasks have been run.
require_once __DIR__ . '/../../../includes/install.inc';
$connection = Database::getConnection();
$errors = db_installer_object($connection->driver())->runTasks();
if (!empty($errors)) {
$this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
}
if ($modules) {
$this->container->get('module_handler')->loadAll();
}
@ -394,17 +406,17 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
protected function getDatabaseConnectionInfo() {
// If the test is run with argument dburl then use it.
$db_url = getenv('SIMPLETEST_DB');
if (!empty($db_url)) {
if (empty($db_url)) {
$this->markTestSkipped('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh. See https://www.drupal.org/node/2116263#skipped-tests for more information.');
}
else {
$database = Database::convertDbUrlToConnectionInfo($db_url, $this->root);
Database::addConnectionInfo('default', 'default', $database);
}
// Clone the current connection and replace the current prefix.
$connection_info = Database::getConnectionInfo('default');
if (is_null($connection_info)) {
throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable, like "sqlite://localhost//tmp/test.sqlite", to run PHPUnit based functional tests outside of run-tests.sh.');
}
else {
if (!empty($connection_info)) {
Database::renameConnection('default', 'simpletest_original_default');
foreach ($connection_info as $target => $value) {
// Replace the full table prefix definition to ensure that no table
@ -479,6 +491,32 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
return $container;
}
/**
* Initializes the FileCache component.
*
* We can not use the Settings object in a component, that's why we have to do
* it here instead of \Drupal\Component\FileCache\FileCacheFactory.
*/
protected function initFileCache() {
$configuration = Settings::get('file_cache');
// Provide a default configuration, if not set.
if (!isset($configuration['default'])) {
$configuration['default'] = [
'class' => FileCache::class,
'cache_backend_class' => NULL,
'cache_backend_configuration' => [],
];
// @todo Use extension_loaded('apcu') for non-testbot
// https://www.drupal.org/node/2447753.
if (function_exists('apc_fetch')) {
$configuration['default']['cache_backend_class'] = ApcuFileCacheBackend::class;
}
}
FileCacheFactory::setConfiguration($configuration);
FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root));
}
/**
* Returns Extension objects for $modules to enable.
*
@ -625,6 +663,9 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
}
}
// Clean FileCache cache.
FileCache::reset();
// Clean up statics, container, and settings.
if (function_exists('drupal_static_reset')) {
drupal_static_reset();
@ -863,8 +904,14 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
* The rendered string output (typically HTML).
*/
protected function render(array &$elements) {
$content = $this->container->get('renderer')->render($elements);
drupal_process_attached($elements);
// Use the bare HTML page renderer to render our links.
$renderer = $this->container->get('bare_html_page_renderer');
$response = $renderer->renderBarePage(
$elements, '', $this->container->get('theme.manager')->getActiveTheme()->getName()
);
// Glean the content from the response object.
$content = $response->getContent();
$this->setRawContent($content);
$this->verbose('<pre style="white-space: pre-wrap">' . Html::escape($content));
return $content;
@ -896,7 +943,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
if (!$this->configImporter) {
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.staging'),
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
@ -1025,7 +1072,7 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name), E_USER_DEPRECATED);
switch ($name) {
case 'public_files_directory':
return Settings::get('file_public_path', conf_path() . '/files');
return Settings::get('file_public_path', \Drupal::service('site.path') . '/files');
case 'private_files_directory':
return $this->container->get('config.factory')->get('system.file')->get('path.private');
@ -1034,15 +1081,14 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
return file_directory_temp();
case 'translation_files_directory':
return Settings::get('file_public_path', conf_path() . '/translations');
return Settings::get('file_public_path', \Drupal::service('site.path') . '/translations');
}
}
if ($name === 'configDirectories') {
trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name), E_USER_DEPRECATED);
return array(
CONFIG_ACTIVE_DIRECTORY => config_get_config_directory(CONFIG_ACTIVE_DIRECTORY),
CONFIG_STAGING_DIRECTORY => config_get_config_directory(CONFIG_STAGING_DIRECTORY),
CONFIG_SYNC_DIRECTORY => config_get_config_directory(CONFIG_SYNC_DIRECTORY),
);
}

View file

@ -7,6 +7,7 @@
namespace Drupal\KernelTests;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Core\Database\Database;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
@ -38,8 +39,7 @@ class KernelTestBaseTest extends KernelTestBase {
substr($this->databasePrefix, 10) => array(
'files' => array(
'config' => array(
'active' => array(),
'staging' => array(),
'sync' => array(),
),
),
),
@ -74,8 +74,8 @@ class KernelTestBaseTest extends KernelTestBase {
$GLOBALS['destroy-me'] = TRUE;
$this->assertArrayHasKey('destroy-me', $GLOBALS);
$schema = $this->container->get('database')->schema();
$schema->createTable('foo', array(
$database = $this->container->get('database');
$database->schema()->createTable('foo', array(
'fields' => array(
'number' => array(
'type' => 'int',
@ -84,7 +84,16 @@ class KernelTestBaseTest extends KernelTestBase {
),
),
));
$this->assertTrue($schema->tableExists('foo'));
$this->assertTrue($database->schema()->tableExists('foo'));
// Ensure that the database tasks have been run during set up. Neither MySQL
// nor SQLite make changes that are testable.
if ($database->driver() == 'pgsql') {
$this->assertEquals('on', $database->query("SHOW standard_conforming_strings")->fetchField());
$this->assertEquals('escape', $database->query("SHOW bytea_output")->fetchField());
}
$this->assertNotNull(FileCacheFactory::getPrefix());
}
/**

View file

@ -31,12 +31,36 @@ class InspectorTest extends PHPUnit_Framework_TestCase {
* Tests asserting all members are strings.
*
* @covers ::assertAllStrings
* @dataProvider providerTestAssertAllStrings
*/
public function testAssertAllStrings() {
$this->assertTrue(Inspector::assertAllStrings([]));
$this->assertTrue(Inspector::assertAllStrings(['foo', 'bar']));
$this->assertFalse(Inspector::assertAllStrings('foo'));
$this->assertFalse(Inspector::assertAllStrings(['foo', new StringObject()]));
public function testAssertAllStrings($input, $expected) {
$this->assertSame($expected, Inspector::assertAllStrings($input));
}
public function providerTestAssertAllStrings() {
$data = [
'empty-array' => [[], TRUE],
'array-with-strings' => [['foo', 'bar'], TRUE],
'string' => ['foo', FALSE],
'array-with-strings-with-colon' => [['foo', 'bar', 'llama:2001988', 'baz', 'llama:14031991'], TRUE],
'with-FALSE' => [[FALSE], FALSE],
'with-TRUE' => [[TRUE], FALSE],
'with-string-and-boolean' => [['foo', FALSE], FALSE],
'with-NULL' => [[NULL], FALSE],
'string-with-NULL' => [['foo', NULL], FALSE],
'integer' => [[1337], FALSE],
'string-and-integer' => [['foo', 1337], FALSE],
'double' => [[3.14], FALSE],
'string-and-double' => [['foo', 3.14], FALSE],
'array' => [[[]], FALSE],
'string-and-array' => [['foo', []], FALSE],
'string-and-nested-array' => [['foo', ['bar']], FALSE],
'object' => [[new \stdClass()], FALSE],
'string-and-object' => [['foo', new StringObject()], FALSE],
];
return $data;
}
/**

View file

@ -7,7 +7,11 @@
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry;
use Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface;
use Drupal\plugin_test\Plugin\plugin_test\fruit\Kale;
use Drupal\Tests\UnitTestCase;
/**
@ -17,41 +21,110 @@ use Drupal\Tests\UnitTestCase;
class DefaultFactoryTest extends UnitTestCase {
/**
* Tests getPluginClass() with a valid plugin.
* Tests getPluginClass() with a valid array plugin definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithValidPlugin() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry';
public function testGetPluginClassWithValidArrayPluginDefinition() {
$plugin_class = Cherry::class;
$class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class]);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a valid object plugin definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithValidObjectPluginDefinition() {
$plugin_class = Cherry::class;
$plugin_definition = $this->getMock(PluginDefinitionInterface::class);
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$class = DefaultFactory::getPluginClass('cherry', $plugin_definition);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a missing class definition.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage The plugin (cherry) did not specify an instance class.
*/
public function testGetPluginClassWithMissingClass() {
public function testGetPluginClassWithMissingClassWithArrayPluginDefinition() {
DefaultFactory::getPluginClass('cherry', []);
}
/**
* Tests getPluginClass() with a missing class definition.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage The plugin (cherry) did not specify an instance class.
*/
public function testGetPluginClassWithMissingClassWithObjectPluginDefinition() {
$plugin_definition = $this->getMock(PluginDefinitionInterface::class);
DefaultFactory::getPluginClass('cherry', $plugin_definition);
}
/**
* Tests getPluginClass() with a not existing class definition.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage Plugin (kiwifruit) instance class "\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit" does not exist.
*/
public function testGetPluginClassWithNotExistingClass() {
public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition() {
DefaultFactory::getPluginClass('kiwifruit', ['class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit']);
}
/**
* Tests getPluginClass() with a required interface.
* Tests getPluginClass() with a not existing class definition.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
*/
public function testGetPluginClassWithInterface() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry';
$class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition() {
$plugin_class = '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit';
$plugin_definition = $this->getMock(PluginDefinitionInterface::class);
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
DefaultFactory::getPluginClass('kiwifruit', $plugin_definition);
}
/**
* Tests getPluginClass() with a required interface.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceWithArrayPluginDefinition() {
$plugin_class = Cherry::class;
$class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class], FruitInterface::class);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a required interface.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceWithObjectPluginDefinition() {
$plugin_class = Cherry::class;
$plugin_definition = $this->getMock(PluginDefinitionInterface::class);
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$class = DefaultFactory::getPluginClass('cherry', $plugin_definition, FruitInterface::class);
$this->assertEquals($plugin_class, $class);
}
@ -59,12 +132,30 @@ class DefaultFactoryTest extends UnitTestCase {
/**
* Tests getPluginClass() with a required interface but no implementation.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface.
* @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface.
*/
public function testGetPluginClassWithInterfaceAndInvalidClass() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale';
DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class, 'provider' => 'core'], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
public function testGetPluginClassWithInterfaceAndInvalidClassWithArrayPluginDefinition() {
$plugin_class = Kale::class;
DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class, 'provider' => 'core'], FruitInterface::class);
}
/**
* Tests getPluginClass() with a required interface but no implementation.
*
* @covers ::getPluginClass
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
*/
public function testGetPluginClassWithInterfaceAndInvalidClassWithObjectPluginDefinition() {
$plugin_class = Kale::class;
$plugin_definition = $this->getMock(PluginDefinitionInterface::class);
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
DefaultFactory::getPluginClass('cherry', $plugin_definition, FruitInterface::class);
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Render\FormattableMarkupTest.
*/
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Tests\UnitTestCase;
/**
* Tests the TranslatableMarkup class.
*
* @coversDefaultClass \Drupal\Component\Render\FormattableMarkup
* @group utility
*/
class FormattableMarkupTest extends UnitTestCase {
/**
* @covers ::__toString
* @covers ::jsonSerialize
*/
public function testToString() {
$string = 'Can I please have a @replacement';
$formattable_string = new FormattableMarkup($string, ['@replacement' => 'kitten']);
$text = (string) $formattable_string;
$this->assertEquals('Can I please have a kitten', $text);
$text = $formattable_string->jsonSerialize();
$this->assertEquals('Can I please have a kitten', $text);
}
/**
* @covers ::count
*/
public function testCount() {
$string = 'Can I please have a @replacement';
$formattable_string = new FormattableMarkup($string, ['@replacement' => 'kitten']);
$this->assertEquals(strlen($string), $formattable_string->count());
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Render\HtmlEscapedTextTest.
*/
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Tests\UnitTestCase;
/**
* Tests the HtmlEscapedText class.
*
* @coversDefaultClass \Drupal\Component\Render\HtmlEscapedText
* @group utility
*/
class HtmlEscapedTextTest extends UnitTestCase {
/**
* @covers ::__toString
* @covers ::jsonSerialize
*
* @dataProvider providerToString
*/
public function testToString($text, $expected, $message) {
$escapeable_string = new HtmlEscapedText($text);
$this->assertEquals($expected, (string) $escapeable_string, $message);
$this->assertEquals($expected, $escapeable_string->jsonSerialize());
}
/**
* Data provider for testToString().
*
* @see testToString()
*/
function providerToString() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"');
$tests[] = array("\xc2\"", '<27>&quot;', 'Escapes invalid sequence "\xc2\""');
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"');
// Checks that special characters are escaped.
$script_tag = $this->prophesize(MarkupInterface::class);
$script_tag->__toString()->willReturn('<script>');
$script_tag = $script_tag->reveal();
$tests[] = array($script_tag, '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.');
$tests[] = array("<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.');
$specialchars = $this->prophesize(MarkupInterface::class);
$specialchars->__toString()->willReturn('<>&"\'');
$specialchars = $specialchars->reveal();
$tests[] = array($specialchars, '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.');
return $tests;
}
/**
* @covers ::count
*/
public function testCount() {
$string = 'Can I please have a <em>kitten</em>';
$escapeable_string = new HtmlEscapedText($string);
$this->assertEquals(strlen($string), $escapeable_string->count());
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Render\PlainTextOutputTest.
*/
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Render\PlainTextOutput
* @group Utility
*/
class PlainTextOutputTest extends UnitTestCase {
/**
* Tests ::renderFromHtml().
*
* @param $expected
* The expected formatted value.
* @param $string
* A string to be formatted.
* @param array $args
* (optional) An associative array of replacements to make. Defaults to
* none.
*
* @covers ::renderFromHtml
* @dataProvider providerRenderFromHtml
*/
public function testRenderFromHtml($expected, $string, $args = []) {
$markup = SafeMarkup::format($string, $args);
$output = PlainTextOutput::renderFromHtml($markup);
$this->assertSame($expected, $output);
}
/**
* Data provider for ::testRenderFromHtml()
*/
public function providerRenderFromHtml() {
$data = [];
$data['simple-text'] = ['Giraffes and wombats', 'Giraffes and wombats'];
$data['simple-html'] = ['Giraffes and wombats', '<a href="/muh">Giraffes</a> and <strong>wombats</strong>'];
$data['html-with-quote'] = ['Giraffes and quote"s', '<a href="/muh">Giraffes</a> and <strong>quote"s</strong>'];
$expected = 'The <em> tag makes your text look like "this".';
$string = 'The &lt;em&gt; tag makes your text look like <em>"this"</em>.';
$data['escaped-html-with-quotes'] = [$expected, $string];
$safe_string = $this->prophesize(MarkupInterface::class);
$safe_string->__toString()->willReturn('<em>"this"</em>');
$safe_string = $safe_string->reveal();
$data['escaped-html-with-quotes-and-placeholders'] = [$expected, 'The @tag tag makes your text look like @result.', ['@tag' =>'<em>', '@result' => $safe_string]];
$safe_string = $this->prophesize(MarkupInterface::class);
$safe_string->__toString()->willReturn($string);
$safe_string = $safe_string->reveal();
$data['safe-string'] = [$expected, $safe_string];
return $data;
}
}

View file

@ -79,7 +79,9 @@ class HtmlTest extends UnitTestCase {
// replaced.
array('__cssidentifier', '-1cssidentifier', array()),
// Verify that an identifier starting with two hyphens is replaced.
array('__cssidentifier', '--cssidentifier', array())
array('__cssidentifier', '--cssidentifier', array()),
// Verify that passing double underscores as a filter is processed.
array('_cssidentifier', '__cssidentifier', array('__' => '_')),
);
}
@ -231,7 +233,7 @@ class HtmlTest extends UnitTestCase {
/**
* Data provider for testDecodeEntities().
*
* @see testCheckPlain()
* @see testDecodeEntities()
*/
public function providerDecodeEntities() {
return array(
@ -272,7 +274,7 @@ class HtmlTest extends UnitTestCase {
/**
* Data provider for testEscape().
*
* @see testCheckPlain()
* @see testEscape()
*/
public function providerEscape() {
return array(
@ -310,4 +312,17 @@ class HtmlTest extends UnitTestCase {
$this->assertSame('&lt;em&gt;répété&lt;/em&gt;', $escaped);
}
}
/**
* Tests Html::serialize().
*
* Resolves an issue by where an empty DOMDocument object sent to serialization would
* cause errors in getElementsByTagName() in the serialization function.
*
* @covers ::serialize
*/
public function testSerialize() {
$document = new \DOMDocument();
$result = Html::serialize($document);
$this->assertSame('', $result);
}
}

View file

@ -7,9 +7,11 @@
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Component\Utility\SafeStringTrait;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Render\MarkupTrait;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Tests\UnitTestCase;
/**
@ -21,81 +23,12 @@ use Drupal\Tests\UnitTestCase;
class SafeMarkupTest extends UnitTestCase {
/**
* Helper function to add a string to the safe list for testing.
*
* @param string $string
* The content to be marked as secure.
* @param string $strategy
* The escaping strategy used for this string. Two values are supported
* by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
*
* @return string
* The input string that was marked as safe.
* {@inheritdoc}
*/
protected function safeMarkupSet($string, $strategy = 'html') {
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$current_value = $reflected_property->getValue();
$current_value[$string][$strategy] = TRUE;
$reflected_property->setValue($current_value);
return $string;
}
protected function tearDown() {
parent::tearDown();
/**
* Tests SafeMarkup::isSafe() with different providers.
*
* @covers ::isSafe
*/
public function testStrategy() {
$returned = $this->safeMarkupSet('string0', 'html');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "html" provider is safe for default (html)');
$returned = $this->safeMarkupSet('string1', 'all');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "all" provider is safe for default (html)');
$returned = $this->safeMarkupSet('string2', 'css');
$this->assertFalse(SafeMarkup::isSafe($returned), 'String set with "css" provider is not safe for default (html)');
$returned = $this->safeMarkupSet('string3');
$this->assertFalse(SafeMarkup::isSafe($returned, 'all'), 'String set with "html" provider is not safe for "all"');
}
/**
* Data provider for testSet().
*/
public function providerSet() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array(
'Foo<6F>barbaz',
'SafeMarkup::setMarkup() functions with valid sequence "Foo<6F>barbaz"',
TRUE
);
$tests[] = array(
"Fooÿñ",
'SafeMarkup::setMarkup() functions with valid sequence "Fooÿñ"'
);
$tests[] = array("<div>", 'SafeMarkup::setMultiple() does not escape HTML');
return $tests;
}
/**
* Tests SafeMarkup::setMultiple().
* @dataProvider providerSet
*
* @param string $text
* The text or object to provide to SafeMarkup::setMultiple().
* @param string $message
* The message to provide as output for the test.
*
* @covers ::setMultiple
*/
public function testSet($text, $message) {
SafeMarkup::setMultiple([$text => ['html' => TRUE]]);
$this->assertTrue(SafeMarkup::isSafe($text), $message);
UrlHelper::setAllowedProtocols(['http', 'https']);
}
/**
@ -104,44 +37,12 @@ class SafeMarkupTest extends UnitTestCase {
* @covers ::isSafe
*/
public function testIsSafe() {
$safe_string = $this->getMock('\Drupal\Component\Utility\SafeStringInterface');
$safe_string = $this->getMock('\Drupal\Component\Render\MarkupInterface');
$this->assertTrue(SafeMarkup::isSafe($safe_string));
$string_object = new SafeMarkupTestString('test');
$this->assertFalse(SafeMarkup::isSafe($string_object));
}
/**
* Tests SafeMarkup::setMultiple().
*
* @covers ::setMultiple
*/
public function testSetMultiple() {
$texts = array(
'multistring0' => array('html' => TRUE),
'multistring1' => array('all' => TRUE),
);
SafeMarkup::setMultiple($texts);
foreach ($texts as $string => $providers) {
$this->assertTrue(SafeMarkup::isSafe($string), 'The value has been marked as safe for html');
}
}
/**
* Tests SafeMarkup::setMultiple().
*
* Only TRUE may be passed in as the value.
*
* @covers ::setMultiple
*
* @expectedException \UnexpectedValueException
*/
public function testInvalidSetMultiple() {
$texts = array(
'invalidstring0' => array('html' => 1),
);
SafeMarkup::setMultiple($texts);
}
/**
* Tests SafeMarkup::checkPlain().
*
@ -154,28 +55,49 @@ class SafeMarkupTest extends UnitTestCase {
* The expected output from the function.
* @param string $message
* The message to provide as output for the test.
* @param bool $ignorewarnings
* Whether or not to ignore PHP 5.3+ invalid multibyte sequence warnings.
*/
function testCheckPlain($text, $expected, $message, $ignorewarnings = FALSE) {
$result = $ignorewarnings ? @SafeMarkup::checkPlain($text) : SafeMarkup::checkPlain($text);
function testCheckPlain($text, $expected, $message) {
$result = SafeMarkup::checkPlain($text);
$this->assertTrue($result instanceof HtmlEscapedText);
$this->assertEquals($expected, $result, $message);
}
/**
* Data provider for testCheckPlain().
* Tests Drupal\Component\Render\HtmlEscapedText.
*
* Verifies that the result of SafeMarkup::checkPlain() is the same as using
* HtmlEscapedText directly.
*
* @dataProvider providerCheckPlain
*
* @param string $text
* The text to provide to the HtmlEscapedText constructor.
* @param string $expected
* The expected output from the function.
* @param string $message
* The message to provide as output for the test.
*/
function testHtmlEscapedText($text, $expected, $message) {
$result = new HtmlEscapedText($text);
$this->assertEquals($expected, $result, $message);
}
/**
* Data provider for testCheckPlain() and testEscapeString().
*
* @see testCheckPlain()
*/
function providerCheckPlain() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'SafeMarkup::checkPlain() escapes invalid sequence "Foo\xC0barbaz"', TRUE);
$tests[] = array("\xc2\"", '<27>&quot;', 'SafeMarkup::checkPlain() escapes invalid sequence "\xc2\""', TRUE);
$tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() does not escape valid sequence "Fooÿñ"');
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"');
$tests[] = array("\xc2\"", '<27>&quot;', 'Escapes invalid sequence "\xc2\""');
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"');
// Checks that special characters are escaped.
$tests[] = array("<script>", '&lt;script&gt;', 'SafeMarkup::checkPlain() escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'SafeMarkup::checkPlain() escapes reserved HTML characters.');
$tests[] = array(SafeMarkupTestMarkup::create("<script>"), '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.');
$tests[] = array("<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.');
$tests[] = array(SafeMarkupTestMarkup::create('<>&"\''), '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.');
return $tests;
}
@ -198,12 +120,14 @@ class SafeMarkupTest extends UnitTestCase {
* Whether the result is expected to be safe for HTML display.
*/
public function testFormat($string, array $args, $expected, $message, $expected_is_safe) {
UrlHelper::setAllowedProtocols(['http', 'https', 'mailto']);
$result = SafeMarkup::format($string, $args);
$this->assertEquals($expected, $result, $message);
$this->assertEquals($expected_is_safe, SafeMarkup::isSafe($result), 'SafeMarkup::format correctly sets the result as safe or not safe.');
foreach ($args as $arg) {
$this->assertSame($arg instanceof SafeMarkupTestSafeString, SafeMarkup::isSafe($arg));
$this->assertSame($arg instanceof SafeMarkupTestMarkup, SafeMarkup::isSafe($arg));
}
}
@ -215,11 +139,23 @@ class SafeMarkupTest extends UnitTestCase {
function providerFormat() {
$tests[] = array('Simple text', array(), 'Simple text', 'SafeMarkup::format leaves simple text alone.', TRUE);
$tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: &lt;script&gt;', 'SafeMarkup::format replaces and escapes string.', TRUE);
$tests[] = array('Escaped text: @value', array('@value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE);
$tests[] = array('Escaped text: @value', array('@value' => SafeMarkupTestMarkup::create('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE);
$tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE);
$tests[] = array('Placeholder text: %value', array('%value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.', TRUE);
$tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'SafeMarkup::format replaces verbatim string as-is.', FALSE);
$tests[] = array('Verbatim text: !value', array('!value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'SafeMarkup::format replaces verbatim string as-is.', TRUE);
$tests[] = array('Placeholder text: %value', array('%value' => SafeMarkupTestMarkup::create('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.', TRUE);
$tests['javascript-protocol-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'javascript://example.com?foo&bar'], 'Simple text <a href="//example.com?foo&amp;bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['external-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'http://example.com?foo&bar'], 'Simple text <a href="http://example.com?foo&amp;bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['relative-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => '/node/1?foo&bar'], 'Simple text <a href="/node/1?foo&amp;bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['fragment-with-special-chars'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'http://example.com/#&lt;'], 'Simple text <a href="http://example.com/#&amp;lt;">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['mailto-protocol'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => 'mailto:test@example.com'], 'Hey giraffe <a href="mailto:test@example.com">MUUUH</a>', '', TRUE];
$tests['js-with-fromCharCode'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "javascript:alert(String.fromCharCode(88,83,83))"], 'Hey giraffe <a href="alert(String.fromCharCode(88,83,83))">MUUUH</a>', '', TRUE];
// Test some "URL" values that are not RFC 3986 compliant URLs. The result
// of SafeMarkup::format() should still be valid HTML (other than the
// value of the "href" attribute not being a valid URL), and not
// vulnerable to XSS.
$tests['non-url-with-colon'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "llamas: they are not URLs"], 'Hey giraffe <a href=" they are not URLs">MUUUH</a>', '', TRUE];
$tests['non-url-with-html'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "<span>not a url</span>"], 'Hey giraffe <a href="&lt;span&gt;not a url&lt;/span&gt;">MUUUH</a>', '', TRUE];
return $tests;
}
@ -241,11 +177,8 @@ class SafeMarkupTestString {
}
/**
* Marks text as safe.
*
* SafeMarkupTestSafeString is used to mark text as safe because
* SafeMarkup::$safeStrings is a global static that affects all tests.
* Marks an object's __toString() method as returning markup.
*/
class SafeMarkupTestSafeString implements SafeStringInterface {
use SafeStringTrait;
class SafeMarkupTestMarkup implements MarkupInterface {
use MarkupTrait;
}

View file

@ -291,6 +291,7 @@ class UnicodeTest extends UnitTestCase {
return array(
array('tHe QUIcK bRoWn', 15),
array('ÜBER-åwesome', 12),
array('以呂波耳・ほへとち。リヌルヲ。', 15),
);
}
@ -529,4 +530,46 @@ class UnicodeTest extends UnitTestCase {
);
}
/**
* Tests multibyte strpos.
*
* @dataProvider providerStrpos
* @covers ::strpos
*/
public function testStrpos($haystack, $needle, $offset, $expected) {
// Run through multibyte code path.
Unicode::setStatus(Unicode::STATUS_MULTIBYTE);
$this->assertEquals($expected, Unicode::strpos($haystack, $needle, $offset));
// Run through singlebyte code path.
Unicode::setStatus(Unicode::STATUS_SINGLEBYTE);
$this->assertEquals($expected, Unicode::strpos($haystack, $needle, $offset));
}
/**
* Data provider for testStrpos().
*
* @see testStrpos()
*
* @return array
* An array containing:
* - The haystack string to be searched in.
* - The needle string to search for.
* - The offset integer to start at.
* - The expected integer/FALSE result.
*/
public function providerStrpos() {
return array(
array('frànçAIS is über-åwesome', 'frànçAIS is über-åwesome', 0, 0),
array('frànçAIS is über-åwesome', 'rànçAIS is über-åwesome', 0, 1),
array('frànçAIS is über-åwesome', 'not in string', 0, FALSE),
array('frànçAIS is über-åwesome', 'r', 0, 1),
array('frànçAIS is über-åwesome', 'nçAIS', 0, 3),
array('frànçAIS is über-åwesome', 'nçAIS', 2, 3),
array('frànçAIS is über-åwesome', 'nçAIS', 3, 3),
array('以呂波耳・ほへとち。リヌルヲ。', '波耳', 0, 2),
array('以呂波耳・ほへとち。リヌルヲ。', '波耳', 1, 2),
array('以呂波耳・ほへとち。リヌルヲ。', '波耳', 2, 2),
);
}
}

View file

@ -38,6 +38,7 @@ class AccessResultTest extends UnitTestCase {
->disableOriginalConstructor()
->getMock();
$this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
\Drupal::setContainer($container);

View file

@ -45,9 +45,6 @@ class TranslationTest extends UnitTestCase {
$options = isset($values['context']) ? array(
'context' => $values['context'],
) : array();
$this->translationManager->expects($this->once())
->method('translate')
->with($values['value'], $arguments, $options);
$annotation = new Translation($values);
@ -69,9 +66,9 @@ class TranslationTest extends UnitTestCase {
$random_html_entity = '&' . $random;
$data[] = array(
array(
'value' => 'Foo !bar @baz %qux',
'value' => 'Foo @bar @baz %qux',
'arguments' => array(
'!bar' => $random,
'@bar' => $random,
'@baz' => $random_html_entity,
'%qux' => $random_html_entity,
),

View file

@ -94,7 +94,7 @@ class LibraryDiscoveryCollectorTest extends UnitTestCase {
$this->activeTheme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$this->themeManager->expects($this->once())
$this->themeManager->expects($this->exactly(3))
->method('getActiveTheme')
->willReturn($this->activeTheme);
$this->activeTheme->expects($this->once())
@ -120,7 +120,7 @@ class LibraryDiscoveryCollectorTest extends UnitTestCase {
$this->activeTheme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$this->themeManager->expects($this->once())
$this->themeManager->expects($this->exactly(3))
->method('getActiveTheme')
->willReturn($this->activeTheme);
$this->activeTheme->expects($this->once())

View file

@ -73,6 +73,15 @@ class LibraryDiscoveryParserTest extends UnitTestCase {
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
$mock_active_theme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$mock_active_theme->expects($this->any())
->method('getLibrariesOverride')
->willReturn([]);
$this->themeManager->expects($this->any())
->method('getActiveTheme')
->willReturn($mock_active_theme);
$this->libraryDiscoveryParser = new TestLibraryDiscoveryParser($this->root, $this->moduleHandler, $this->themeManager);
}

View file

@ -10,6 +10,8 @@ namespace Drupal\Tests\Core\Breadcrumb;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbManager;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
/**
@ -18,6 +20,13 @@ use Drupal\Tests\UnitTestCase;
*/
class BreadcrumbManagerTest extends UnitTestCase {
/**
* The dependency injection container.
*
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
*/
protected $container;
/**
* The breadcrumb object.
*
@ -46,6 +55,13 @@ class BreadcrumbManagerTest extends UnitTestCase {
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->breadcrumbManager = new BreadcrumbManager($this->moduleHandler);
$this->breadcrumb = new Breadcrumb();
$this->container = new ContainerBuilder();
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
$cache_contexts_manager->reveal();
$this->container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($this->container);
}
/**
@ -71,7 +87,7 @@ class BreadcrumbManagerTest extends UnitTestCase {
$builder = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
$links = array('<a href="/example">Test</a>');
$this->breadcrumb->setLinks($links);
$this->breadcrumb->setCacheContexts(['foo'])->setCacheTags(['bar']);
$this->breadcrumb->addCacheContexts(['foo'])->addCacheTags(['bar']);
$builder->expects($this->once())
->method('applies')
@ -108,7 +124,7 @@ class BreadcrumbManagerTest extends UnitTestCase {
$builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
$links2 = array('<a href="/example2">Test2</a>');
$this->breadcrumb->setLinks($links2);
$this->breadcrumb->setCacheContexts(['baz'])->setCacheTags(['qux']);
$this->breadcrumb->addCacheContexts(['baz'])->addCacheTags(['qux']);
$builder2->expects($this->once())
->method('applies')
->will($this->returnValue(TRUE));
@ -146,7 +162,7 @@ class BreadcrumbManagerTest extends UnitTestCase {
$builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
$links2 = ['<a href="/example2">Test2</a>'];
$this->breadcrumb->setLinks($links2);
$this->breadcrumb->setCacheContexts(['baz'])->setCacheTags(['qux']);
$this->breadcrumb->addCacheContexts(['baz'])->addCacheTags(['qux']);
$builder2->expects($this->once())
->method('applies')
->will($this->returnValue(TRUE));

View file

@ -20,8 +20,7 @@ class CacheTagsInvalidatorTest extends UnitTestCase {
/**
* @covers ::invalidateTags
*
* @expectedException \LogicException
* @expectedExceptionMessage Cache tags must be strings, array given.
* @expectedException \AssertionError
*/
public function testInvalidateTagsWithInvalidTags() {
$cache_tags_invalidator = new CacheTagsInvalidator();

View file

@ -7,6 +7,7 @@
namespace Drupal\Tests\Core\Cache;
use Drupal\Component\Assertion\Inspector;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -59,6 +60,7 @@ class CacheTest extends UnitTestCase {
$this->assertNull(Cache::validateTags($tags));
}
/**
* Provides a list of pairs of cache tags arrays to be merged.
*

View file

@ -35,6 +35,8 @@ class CacheableMetadataTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);
@ -57,6 +59,7 @@ class CacheableMetadataTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);

View file

@ -106,8 +106,7 @@ class CacheContextsManagerTest extends UnitTestCase {
/**
* @covers ::convertTokensToKeys
*
* @expectedException \LogicException
* @expectedExceptionMessage "non-cache-context" is not a valid cache context ID.
* @expectedException \AssertionError
*/
public function testInvalidContext() {
$container = $this->getMockContainer();

View file

@ -8,6 +8,7 @@
namespace Drupal\Tests\Core\Config;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Render\Markup;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Config\Config;
use Drupal\Component\Utility\SafeMarkup;
@ -528,4 +529,26 @@ class ConfigTest extends UnitTestCase {
}
}
/**
* @covers ::setData
* @covers ::set
* @covers ::initWithData
*/
public function testSafeStringHandling() {
// Safe strings are cast when using ::set().
$safe_string = Markup::create('bar');
$this->config->set('foo', $safe_string);
$this->assertSame('bar', $this->config->get('foo'));
$this->config->set('foo', ['bar' => $safe_string]);
$this->assertSame('bar', $this->config->get('foo.bar'));
// Safe strings are cast when using ::setData().
$this->config->setData(['bar' => $safe_string]);
$this->assertSame('bar', $this->config->get('bar'));
// Safe strings are not cast when using ::initWithData().
$this->config->initWithData(['bar' => $safe_string]);
$this->assertSame($safe_string, $this->config->get('bar'));
}
}

View file

@ -139,19 +139,19 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
/**
* @covers ::calculateDependencies
* @covers ::getDependencies
*/
public function testCalculateDependencies() {
// Calculating dependencies will reset the dependencies array.
$this->entity->set('dependencies', array('module' => array('node')));
$this->assertEmpty($this->entity->calculateDependencies());
$this->assertEmpty($this->entity->calculateDependencies()->getDependencies());
// Calculating dependencies will reset the dependencies array using enforced
// dependencies.
$this->entity->set('dependencies', array('module' => array('node'), 'enforced' => array('module' => 'views')));
$dependencies = $this->entity->calculateDependencies();
$dependencies = $this->entity->calculateDependencies()->getDependencies();
$this->assertContains('views', $dependencies['module']);
$this->assertNotContains('node', $dependencies['module']);
$this->assertContains('views', $dependencies['enforced']['module']);
}
/**
@ -213,6 +213,7 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
}
/**
* @covers ::getDependencies
* @covers ::calculateDependencies
*
* @dataProvider providerCalculateDependenciesWithPluginCollections
@ -244,7 +245,7 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
->method('getPluginCollections')
->will($this->returnValue(array($pluginCollection)));
$this->assertEquals($expected_dependencies, $this->entity->calculateDependencies());
$this->assertEquals($expected_dependencies, $this->entity->calculateDependencies()->getDependencies());
}
/**
@ -289,6 +290,7 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
/**
* @covers ::calculateDependencies
* @covers ::getDependencies
* @covers ::onDependencyRemoval
*/
public function testCalculateDependenciesWithThirdPartySettings() {
@ -297,12 +299,12 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
$this->entity->setThirdPartySetting('test_provider2', 'test', 'test');
$this->entity->setThirdPartySetting($this->provider, 'test', 'test');
$this->assertEquals(array('test_provider', 'test_provider2'), $this->entity->calculateDependencies()['module']);
$this->assertEquals(array('test_provider', 'test_provider2'), $this->entity->calculateDependencies()->getDependencies()['module']);
$changed = $this->entity->onDependencyRemoval(['module' => ['test_provider2']]);
$this->assertTrue($changed, 'Calling onDependencyRemoval with an existing third party dependency provider returns TRUE.');
$changed = $this->entity->onDependencyRemoval(['module' => ['test_provider3']]);
$this->assertFalse($changed, 'Calling onDependencyRemoval with a non-existing third party dependency provider returns FALSE.');
$this->assertEquals(array('test_provider'), $this->entity->calculateDependencies()['module']);
$this->assertEquals(array('test_provider'), $this->entity->calculateDependencies()->getDependencies()['module']);
}
/**

View file

@ -98,7 +98,7 @@ class EntityDisplayModeBaseUnitTest extends UnitTestCase {
->setMethods(array('getFilterFormat'))
->getMock();
$dependencies = $this->entity->calculateDependencies();
$dependencies = $this->entity->calculateDependencies()->getDependencies();
$this->assertContains('test_module', $dependencies['module']);
}

View file

@ -0,0 +1,168 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Database\ConditionTest.
*/
namespace Drupal\Tests\Core\Database;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\Query\PlaceholderInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\Core\Database\Query\Condition
*
* @group Database
*/
class ConditionTest extends UnitTestCase {
/**
* @covers ::compile
*/
public function testSimpleCondition() {
$connection = $this->prophesize(Connection::class);
$connection->escapeField('name')->will(function ($args) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
});
$connection->mapConditionOperator('=')->willReturn(['operator' => '=']);
$connection = $connection->reveal();
$query_placeholder = $this->prophesize(PlaceholderInterface::class);
$counter = 0;
$query_placeholder->nextPlaceholder()->will(function() use (&$counter) {
return $counter++;
});
$query_placeholder->uniqueIdentifier()->willReturn(4);
$query_placeholder = $query_placeholder->reveal();
$condition = new Condition('AND');
$condition->condition('name', ['value']);
$condition->compile($connection, $query_placeholder);
$this->assertEquals(' (name = :db_condition_placeholder_0) ', $condition->__toString());
$this->assertEquals([':db_condition_placeholder_0' => 'value'], $condition->arguments());
}
/**
* @covers ::compile
*
* @dataProvider dataProviderTestCompileWithKnownOperators()
*
* @param string $expected
* The expected generated SQL condition.
* @param string $field
* The field to pass into the condition() method.
* @param mixed $value
* The value to pass into the condition() method.
* @param string $operator
* The operator to pass into the condition() method.
* @param mixed $expected_arguments
* (optional) The expected set arguments.
*/
public function testCompileWithKnownOperators($expected, $field, $value, $operator, $expected_arguments = NULL) {
$connection = $this->prophesize(Connection::class);
$connection->escapeField(Argument::any())->will(function ($args) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
});
$connection->mapConditionOperator(Argument::any())->willReturn(NULL);
$connection = $connection->reveal();
$query_placeholder = $this->prophesize(PlaceholderInterface::class);
$counter = 0;
$query_placeholder->nextPlaceholder()->will(function() use (&$counter) {
return $counter++;
});
$query_placeholder->uniqueIdentifier()->willReturn(4);
$query_placeholder = $query_placeholder->reveal();
$condition = new Condition('AND');
$condition->condition($field, $value, $operator);
$condition->compile($connection, $query_placeholder);
$this->assertEquals($expected, $condition->__toString());
if (isset($expected_arguments)) {
$this->assertEquals($expected_arguments, $condition->arguments());
}
}
/**
* Provides a list of known operations and the expected output.
*
* @return array
*/
public function dataProviderTestCompileWithKnownOperators() {
// Below are a list of commented out test cases, which should work but
// aren't directly supported by core, but instead need manual handling with
// prefix/suffix at the moment.
$data = [];
$data[] = [' (name = :db_condition_placeholder_0) ', 'name', 'value', '='];
$data[] = [' (name != :db_condition_placeholder_0) ', 'name', 'value', '!='];
$data[] = [' (name <> :db_condition_placeholder_0) ', 'name', 'value', '<>'];
$data[] = [' (name >= :db_condition_placeholder_0) ', 'name', 'value', '>='];
$data[] = [' (name > :db_condition_placeholder_0) ', 'name', 'value', '>'];
$data[] = [' (name <= :db_condition_placeholder_0) ', 'name', 'value', '<='];
$data[] = [' (name < :db_condition_placeholder_0) ', 'name', 'value', '<'];
// $data[] = [' ( GREATEST (1, 2, 3) ) ', '', [1, 2, 3], 'GREATEST'];
$data[] = [' (name IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)) ', 'name', ['1', '2', '3'], 'IN'];
$data[] = [' (name NOT IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)) ', 'name', ['1', '2', '3'], 'NOT IN'];
// $data[] = [' ( INTERVAL (1, 2, 3) ) ', '', [1, 2, 3], 'INTERVAL'];
$data[] = [' (name IS NULL ) ', 'name', NULL, 'IS NULL'];
$data[] = [' (name IS NOT NULL ) ', 'name', NULL, 'IS NOT NULL'];
$data[] = [' (name IS :db_condition_placeholder_0) ', 'name', 'TRUE', 'IS'];
// $data[] = [' ( LEAST (1, 2, 3) ) ', '', [1, 2, 3], 'LEAST'];
$data[] = [" (name LIKE :db_condition_placeholder_0 ESCAPE '\\\\') ", 'name', '%muh%', 'LIKE', [':db_condition_placeholder_0' => '%muh%']];
$data[] = [" (name NOT LIKE :db_condition_placeholder_0 ESCAPE '\\\\') ", 'name', '%muh%', 'NOT LIKE', [':db_condition_placeholder_0' => '%muh%']];
$data[] = [" (name BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1) ", 'name', [1, 2], 'BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]];
// $data[] = [" (name NOT BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1) ", 'name', [1, 2], 'NOT BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]];
// $data[] = [' ( STRCMP (name, :db_condition_placeholder_0) ) ', '', ['test-string'], 'STRCMP', [':db_condition_placeholder_0' => 'test-string']];
// $data[] = [' (EXISTS ) ', '', NULL, 'EXISTS'];
// $data[] = [' (name NOT EXISTS ) ', 'name', NULL, 'NOT EXISTS'];
return $data;
}
/**
* @covers ::compile
*
* @expectedException \PHPUnit_Framework_Error
* @dataProvider providerTestCompileWithSqlInjectionForOperator
*/
public function testCompileWithSqlInjectionForOperator($operator) {
$connection = $this->prophesize(Connection::class);
$connection->escapeField(Argument::any())->will(function ($args) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
});
$connection->mapConditionOperator(Argument::any())->willReturn(NULL);
$connection = $connection->reveal();
$query_placeholder = $this->prophesize(PlaceholderInterface::class);
$counter = 0;
$query_placeholder->nextPlaceholder()->will(function() use (&$counter) {
return $counter++;
});
$query_placeholder->uniqueIdentifier()->willReturn(4);
$query_placeholder = $query_placeholder->reveal();
$condition = new Condition('AND');
$condition->condition('name', 'value', $operator);
$condition->compile($connection, $query_placeholder);
}
public function providerTestCompileWithSqlInjectionForOperator() {
$data = [];
$data[] = ["IS NOT NULL) ;INSERT INTO {test} (name) VALUES ('test12345678'); -- "];
$data[] = ["IS NOT NULL) UNION ALL SELECT name, pass FROM {users_field_data} -- "];
$data[] = ["IS NOT NULL) UNION ALL SELECT name FROM {TEST_UPPERCASE} -- "];
$data[] = ["= 1 UNION ALL SELECT password FROM user WHERE uid ="];
return $data;
}
}

View file

@ -254,11 +254,11 @@ class ConnectionTest extends UnitTestCase {
array(''),
),
array(
'/* Exploit * / DROP TABLE node; -- */ ',
'/* Exploit * / DROP TABLE node. -- */ ',
array('Exploit * / DROP TABLE node; --'),
),
array(
'/* Exploit * / DROP TABLE node; --; another comment */ ',
'/* Exploit * / DROP TABLE node. --. another comment */ ',
array('Exploit * / DROP TABLE node; --', 'another comment'),
),
);
@ -286,8 +286,8 @@ class ConnectionTest extends UnitTestCase {
public function providerFilterComments() {
return array(
array('', ''),
array('Exploit * / DROP TABLE node; --', 'Exploit * / DROP TABLE node; --'),
array('Exploit * / DROP TABLE node; --', 'Exploit */ DROP TABLE node; --'),
array('Exploit * / DROP TABLE node. --', 'Exploit * / DROP TABLE node; --'),
array('Exploit * / DROP TABLE node. --', 'Exploit */ DROP TABLE node; --'),
);
}

View file

@ -196,7 +196,7 @@ class BaseFieldDefinitionTest extends UnitTestCase {
// Set default value with NULL.
$definition->setDefaultValue(NULL);
$this->assertEquals(NULL, $definition->getDefaultValue($entity));
$this->assertEquals([], $definition->getDefaultValue($entity));
}
/**

View file

@ -35,7 +35,9 @@ class EntityCreateAccessCheckTest extends UnitTestCase {
protected function setUp() {
parent::setUp();
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
$cache_contexts_manager->reveal();
$container = new Container();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);

View file

@ -9,6 +9,8 @@ namespace Drupal\Tests\Core\Entity;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Tests\UnitTestCase;
/**
@ -283,6 +285,44 @@ class EntityTypeTest extends UnitTestCase {
$this->assertEquals($id, $entity_type->id());
}
/**
* @covers ::getLabel
*/
public function testGetLabel() {
$translatable_label = new TranslatableMarkup($this->randomMachineName());
$entity_type = $this->setUpEntityType(array('label' => $translatable_label));
$this->assertSame($translatable_label, $entity_type->getLabel());
$label = $this->randomMachineName();
$entity_type = $this->setUpEntityType(array('label' => $label));
$this->assertSame($label, $entity_type->getLabel());
}
/**
* @covers ::getGroupLabel
*/
public function testGetGroupLabel() {
$translatable_group_label = new TranslatableMarkup($this->randomMachineName());
$entity_type = $this->setUpEntityType(array('group_label' => $translatable_group_label));
$this->assertSame($translatable_group_label, $entity_type->getGroupLabel());
$default_label = $this->randomMachineName();
$entity_type = $this->setUpEntityType(array('group_label' => $default_label));
$this->assertSame($default_label, $entity_type->getGroupLabel());
$default_label = new TranslatableMarkup('Other', array(), array('context' => 'Entity type group'));
$entity_type = $this->setUpEntityType([]);
$string_translation = $this->getMock(TranslationInterface::class);
$string_translation->expects($this->atLeastOnce())
->method('translate')
->with('Other', array(), array('context' => 'Entity type group'))
->willReturn($default_label);
$entity_type->setStringTranslation($string_translation);
$this->assertSame($default_label, $entity_type->getGroupLabel());
}
/**
* Gets a mock controller class name.
*

View file

@ -518,6 +518,8 @@ class EntityUnitTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);

View file

@ -66,10 +66,13 @@ class EntityUrlTest extends UnitTestCase {
$this->assertEquals($langcode, $uri->getOption('language')->getId());
}
else {
// The expected langcode for a config entity is 'en', because it sets the
// value as default property.
$expected_langcode = $entity instanceof ConfigEntityInterface ? 'en' : LanguageInterface::LANGCODE_NOT_SPECIFIED;
$this->assertEquals($expected_langcode, $uri->getOption('language')->getId());
if ($entity instanceof ConfigEntityInterface) {
// Config entities do not provide a language with their URIs.
$this->assertEquals(NULL, $uri->getOption('language'));
}
else {
$this->assertEquals(LanguageInterface::LANGCODE_NOT_SPECIFIED, $uri->getOption('language')->getId());
}
}
}

View file

@ -326,6 +326,42 @@ class ActiveLinkResponseFilterTest extends UnitTestCase {
5 => $front_special_link_active . ' ' . $front_path_link_active,
];
// Test cases to verify that links to the front page do not get the
// 'is-active' class when not on the front page.
$other_link = '<a data-drupal-link-system-path="otherpage">Other page</a>';
$other_link_active = '<a data-drupal-link-system-path="otherpage" class="is-active">Other page</a>';
$data['<front>-and-other-link-on-other-path'] = [
0 => $front_special_link . ' ' . $other_link,
1 => 'otherpage',
2 => FALSE,
3 => 'en',
4 => [],
5 => $front_special_link . ' ' . $other_link_active,
];
$data['front-and-other-link-on-other-path'] = [
0 => $front_path_link . ' ' . $other_link,
1 => 'otherpage',
2 => FALSE,
3 => 'en',
4 => [],
5 => $front_path_link . ' ' . $other_link_active,
];
$data['other-and-<front>-link-on-other-path'] = [
0 => $other_link . ' ' . $front_special_link,
1 => 'otherpage',
2 => FALSE,
3 => 'en',
4 => [],
5 => $other_link_active . ' ' . $front_special_link,
];
$data['other-and-front-link-on-other-path'] = [
0 => $other_link . ' ' . $front_path_link,
1 => 'otherpage',
2 => FALSE,
3 => 'en',
4 => [],
5 => $other_link_active . ' ' . $front_path_link,
];
return $data;
}

View file

@ -8,8 +8,8 @@
namespace Drupal\Tests\Core\EventSubscriber;
use Drupal\Core\EventSubscriber\RedirectResponseSubscriber;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\EventDispatcher\EventDispatcher;
@ -33,6 +33,13 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
*/
protected $requestContext;
/**
* The mocked request context.
*
* @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $urlAssembler;
/**
* {@inheritdoc}
*/
@ -46,6 +53,17 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
->method('getCompleteBaseUrl')
->willReturn('http://example.com/drupal');
$this->urlAssembler = $this->getMock(UnroutedUrlAssemblerInterface::class);
$this->urlAssembler
->expects($this->any())
->method('assemble')
->willReturnMap([
['base:test', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/test'],
['base:example.com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example.com'],
['base:example:com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example:com'],
['base:javascript:alert(0)', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/javascript:alert(0)'],
]);
$container = new Container();
$container->set('router.request_context', $this->requestContext);
\Drupal::setContainer($container);
@ -66,27 +84,9 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new RedirectResponse('http://example.com/drupal');
$url_generator = $this->getMockBuilder('Drupal\Core\Routing\UrlGenerator')
->disableOriginalConstructor()
->setMethods(array('generateFromPath'))
->getMock();
if ($expected) {
$url_generator
->expects($this->any())
->method('generateFromPath')
->willReturnMap([
['test', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/test'],
['example.com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example.com'],
['example:com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example:com'],
['javascript:alert(0)', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/javascript:alert(0)'],
['/test', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/test'],
]);
}
$request->headers->set('HOST', 'example.com');
$listener = new RedirectResponseSubscriber($url_generator, $this->requestContext);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'checkRedirectUrl'));
$event = new FilterResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST, $response);
$dispatcher->dispatch(KernelEvents::RESPONSE, $event);
@ -127,32 +127,8 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new RedirectResponse('http://other-example.com');
$url_generator = $this->getMockBuilder('Drupal\Core\Routing\UrlGenerator')
->disableOriginalConstructor()
->setMethods(array('generateFromPath'))
->getMock();
$request_context = $this->getMockBuilder('Drupal\Core\Routing\RequestContext')
->disableOriginalConstructor()
->getMock();
$request_context->expects($this->any())
->method('getCompleteBaseUrl')
->willReturn('http://example.com/drupal');
if ($expected) {
$url_generator
->expects($this->any())
->method('generateFromPath')
->willReturnMap([
['test', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/test'],
['example.com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example.com'],
['example:com', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/example:com'],
['javascript:alert(0)', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/drupal/javascript:alert(0)'],
['/test', ['query' => [], 'fragment' => '', 'absolute' => TRUE], FALSE, 'http://example.com/test'],
]);
}
$listener = new RedirectResponseSubscriber($url_generator, $request_context);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'checkRedirectUrl'));
$event = new FilterResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST, $response);
$dispatcher->dispatch(KernelEvents::RESPONSE, $event);
@ -167,22 +143,10 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new TrustedRedirectResponse('http://external-url.com');
$url_generator = $this->getMockBuilder('Drupal\Core\Routing\UrlGenerator')
->disableOriginalConstructor()
->setMethods(array('generateFromPath'))
->getMock();
$request_context = $this->getMockBuilder('Drupal\Core\Routing\RequestContext')
->disableOriginalConstructor()
->getMock();
$request_context->expects($this->any())
->method('getCompleteBaseUrl')
->willReturn('http://example.com/drupal');
$request = Request::create('');
$request->headers->set('HOST', 'example.com');
$listener = new RedirectResponseSubscriber($url_generator, $request_context);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'checkRedirectUrl'));
$event = new FilterResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST, $response);
$dispatcher->dispatch(KernelEvents::RESPONSE, $event);
@ -214,13 +178,8 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new RedirectResponse('http://example.com/drupal');
$url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$request_context = $this->getMockBuilder('Drupal\Core\Routing\RequestContext')
->disableOriginalConstructor()
->getMock();
$listener = new RedirectResponseSubscriber($url_generator, $request_context);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'checkRedirectUrl'));
$event = new FilterResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST, $response);
$dispatcher->dispatch(KernelEvents::RESPONSE, $event);
@ -257,9 +216,7 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$request = new Request();
$request->query->set('destination', $input);
$url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$request_context = new RequestContext();
$listener = new RedirectResponseSubscriber($url_generator, $request_context);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
@ -283,9 +240,7 @@ class RedirectResponseSubscriberTest extends UnitTestCase {
$request = new Request();
$request->request->set('destination', $input);
$url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$request_context = new RequestContext();
$listener = new RedirectResponseSubscriber($url_generator, $request_context);
$listener = new RedirectResponseSubscriber($this->urlAssembler, $this->requestContext);
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);

View file

@ -265,6 +265,11 @@ class ModuleHandlerTest extends UnitTestCase {
/**
* @covers ::loadInclude
*
* Note we load code, so isolate the test.
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testLoadInclude() {
// Include exists.

View file

@ -7,6 +7,7 @@
namespace Drupal\Tests\Core\Extension;
use Drupal\simpletest\AssertHelperTrait;
use Drupal\Tests\UnitTestCase;
/**
@ -15,6 +16,8 @@ use Drupal\Tests\UnitTestCase;
*/
class RequiredModuleUninstallValidatorTest extends UnitTestCase {
use AssertHelperTrait;
/**
* @var \Drupal\Core\Extension\RequiredModuleUninstallValidator|\PHPUnit_Framework_MockObject_MockObject
*/
@ -73,7 +76,7 @@ class RequiredModuleUninstallValidatorTest extends UnitTestCase {
$expected = ["The $module module is required"];
$reasons = $this->uninstallValidator->validate($module);
$this->assertSame($expected, $reasons);
$this->assertSame($expected, $this->castSafeStrings($reasons));
}
}

View file

@ -2,30 +2,30 @@
/**
* @file
* Contains \Drupal\Tests\Core\Field\FieldFilteredStringTest.
* Contains \Drupal\Tests\Core\Field\FieldFilteredMarkupTest.
*/
namespace Drupal\Tests\Core\Field;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Field\FieldFilteredString;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Core\Field\FieldFilteredMarkup;
use Drupal\Component\Render\MarkupInterface;
/**
* @coversDefaultClass \Drupal\Core\Field\FieldFilteredString
* @coversDefaultClass \Drupal\Core\Field\FieldFilteredMarkup
* @group Field
*/
class FieldFilteredStringTest extends UnitTestCase {
class FieldFilteredMarkupTest extends UnitTestCase {
/**
* @covers ::create
* @dataProvider providerTestCreate
*/
public function testCreate($string, $expected, $instance_of_check) {
$filtered_string = FieldFilteredString::create($string);
$filtered_string = FieldFilteredMarkup::create($string);
if ($instance_of_check) {
$this->assertInstanceOf(FieldFilteredString::class, $filtered_string);
$this->assertInstanceOf(FieldFilteredMarkup::class, $filtered_string);
}
$this->assertSame($expected, (string) $filtered_string);
}
@ -44,7 +44,7 @@ class FieldFilteredStringTest extends UnitTestCase {
$data[] = ['<em>teststring', '<em>teststring</em>', TRUE];
// Even safe strings will be escaped.
$safe_string = $this->prophesize(SafeStringInterface::class);
$safe_string = $this->prophesize(MarkupInterface::class);
$safe_string->__toString()->willReturn('<script>teststring</script>');
$data[] = [$safe_string->reveal(), 'teststring', TRUE];
@ -57,7 +57,7 @@ class FieldFilteredStringTest extends UnitTestCase {
public function testdisplayAllowedTags() {
$expected = '<a> <b> <big> <code> <del> <em> <i> <ins> <pre> <q> <small> <span> <strong> <sub> <sup> <tt> <ol> <ul> <li> <p> <br> <img>';
$this->assertSame($expected, FieldFilteredString::displayAllowedTags());
$this->assertSame($expected, FieldFilteredMarkup::displayAllowedTags());
}
}

View file

@ -8,8 +8,10 @@
namespace Drupal\Tests\Core\Field;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Form\FormState;
use Drupal\Tests\UnitTestCase;
/**
@ -155,4 +157,60 @@ class FieldItemListTest extends UnitTestCase {
$this->assertEquals(TRUE, $field_list_a->equals($field_list_b));
}
/**
* @covers ::defaultValuesForm
*/
public function testDefaultValuesForm() {
$field_definition = $this->getMock(FieldDefinitionInterface::class);
$field_definition->expects($this->any())
->method('getType')
->willReturn('field_type');
/** @var \Drupal\Core\Field\FieldItemList|\PHPUnit_Framework_MockObject_MockObject $field_list */
$field_list = $this->getMock(FieldItemList::class, ['defaultValueWidget'], [$field_definition]);
$field_list->expects($this->any())
->method('defaultValueWidget')
->willReturn(NULL);
$form = [];
$form_state = new FormState();
$string_translation = $this->getStringTranslationStub();
$field_list->setStringTranslation($string_translation);
$this->assertEquals('No widget available for: <em class="placeholder">field_type</em>.', $field_list->defaultValuesForm($form, $form_state)['#markup']);
}
/**
* @covers ::defaultValuesFormValidate
*/
public function testDefaultValuesFormValidate() {
$field_definition = $this->getMock(FieldDefinitionInterface::class);
/** @var \Drupal\Core\Field\FieldItemList|\PHPUnit_Framework_MockObject_MockObject $field_list */
$field_list = $this->getMock(FieldItemList::class, ['defaultValueWidget', 'validate'], [$field_definition]);
$field_list->expects($this->any())
->method('defaultValueWidget')
->willReturn(NULL);
$field_list->expects($this->never())
->method('validate');
$form = [];
$form_state = new FormState();
$field_list->defaultValuesFormValidate([], $form, $form_state);
}
/**
* @covers ::defaultValuesFormSubmit
*/
public function testDefaultValuesFormSubmit() {
$field_definition = $this->getMock(FieldDefinitionInterface::class);
/** @var \Drupal\Core\Field\FieldItemList|\PHPUnit_Framework_MockObject_MockObject $field_list */
$field_list = $this->getMock(FieldItemList::class, ['defaultValueWidget', 'getValue'], [$field_definition]);
$field_list->expects($this->any())
->method('defaultValueWidget')
->willReturn(NULL);
$form = [];
$form_state = new FormState();
$field_list->expects($this->never())
->method('getValue');
$this->assertNull($field_list->defaultValuesFormSubmit([], $form, $form_state));
}
}

View file

@ -33,6 +33,7 @@ class ConfirmFormHelperTest extends UnitTestCase {
$link = ConfirmFormHelper::buildCancelLink($form, new Request());
$this->assertSame($cancel_text, $link['#title']);
$this->assertSame(['contexts' => ['url.query_args:destination']], $link['#cache']);
}
/**
@ -49,6 +50,7 @@ class ConfirmFormHelperTest extends UnitTestCase {
->will($this->returnValue($cancel_route));
$link = ConfirmFormHelper::buildCancelLink($form, new Request());
$this->assertEquals(Url::fromRoute($route_name), $link['#url']);
$this->assertSame(['contexts' => ['url.query_args:destination']], $link['#cache']);
}
/**
@ -64,6 +66,7 @@ class ConfirmFormHelperTest extends UnitTestCase {
->will($this->returnValue($expected));
$link = ConfirmFormHelper::buildCancelLink($form, new Request());
$this->assertEquals($expected, $link['#url']);
$this->assertSame(['contexts' => ['url.query_args:destination']], $link['#cache']);
}
/**
@ -86,6 +89,7 @@ class ConfirmFormHelperTest extends UnitTestCase {
->will($this->returnValue($cancel_route));
$link = ConfirmFormHelper::buildCancelLink($form, new Request());
$this->assertSame($cancel_route, $link['#url']);
$this->assertSame(['contexts' => ['url.query_args:destination']], $link['#cache']);
}
/**
@ -112,6 +116,7 @@ class ConfirmFormHelperTest extends UnitTestCase {
$link = ConfirmFormHelper::buildCancelLink($form, new Request($query));
$this->assertSame($url, $link['#url']);
$this->assertSame(['contexts' => ['url.query_args:destination']], $link['#cache']);
}
/**

View file

@ -7,9 +7,10 @@
namespace Drupal\Tests\Core\Form;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\EnforcedResponseException;
use Drupal\Core\Form\FormBuilder;
@ -19,11 +20,10 @@ use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @coversDefaultClass \Drupal\Core\Form\FormBuilder
@ -306,6 +306,53 @@ class FormBuilderTest extends FormTestBase {
$this->assertArrayHasKey('#id', $form);
}
/**
* Tests whether the triggering element is properly identified.
*
* @param string $element_value
* The input element "#value" value.
* @param string $input_value
* The corresponding submitted input value.
*
* @covers ::buildForm
*
* @dataProvider providerTestBuildFormWithTriggeringElement
*/
public function testBuildFormWithTriggeringElement($element_value, $input_value) {
$form_id = 'test_form_id';
$expected_form = $form_id();
$expected_form['actions']['other_submit'] = [
'#type' => 'submit',
'#value' => $element_value,
];
$form_arg = $this->getMockForm($form_id, $expected_form, 2);
$form_state = new FormState();
$form_state->setProcessInput();
$form_state->setUserInput(['form_id' => $form_id, 'op' => $input_value]);
$this->request->setMethod('POST');
$this->formBuilder->buildForm($form_arg, $form_state);
$this->assertEquals($expected_form['actions']['other_submit']['#value'], $form_state->getTriggeringElement()['#value']);
}
/**
* Data provider for ::testBuildFormWithTriggeringElement().
*/
public function providerTestBuildFormWithTriggeringElement() {
$plain_text = 'Other submit value';
$markup = 'Other submit <input> value';
return [
'plain-text' => [$plain_text, $plain_text],
'markup' => [$markup, $markup],
// Note: The input is always decoded, see
// \Drupal\Core\Form\FormBuilder::buttonWasClicked, so we do not need to
// escape the input.
'escaped-markup' => [Html::escape($markup), $markup],
];
}
/**
* Tests the rebuildForm() method for a POST submission.
*/
@ -736,10 +783,7 @@ class FormBuilderTest extends FormTestBase {
->willReturnArgument(0);
$this->csrfToken->expects($this->atLeastOnce())
->method('validate')
->will($this->returnValueMap([
[$form_token, $form_id, $valid_token],
[$form_id, $form_id, $valid_token],
]));
->willReturn($valid_token);
}
$current_user = $this->prophesize(AccountInterface::class);
@ -773,7 +817,7 @@ class FormBuilderTest extends FormTestBase {
*
* @dataProvider providerTestFormTokenCacheability
*/
function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability) {
public function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability, $method) {
$user = $this->prophesize(AccountProxyInterface::class);
$user->isAuthenticated()
->willReturn($is_authenticated);
@ -782,6 +826,7 @@ class FormBuilderTest extends FormTestBase {
$form_id = 'test_form_id';
$form = $form_id();
$form['#method'] = $method;
if (isset($token)) {
$form['#token'] = $token;
@ -797,7 +842,7 @@ class FormBuilderTest extends FormTestBase {
$form_state = new FormState();
$built_form = $this->formBuilder->buildForm($form_arg, $form_state);
if (!isset($expected_form_cacheability)) {
if (!isset($expected_form_cacheability) || ($method == 'get' && !is_string($token))) {
$this->assertFalse(isset($built_form['#cache']));
}
else {
@ -820,10 +865,12 @@ class FormBuilderTest extends FormTestBase {
*/
function providerTestFormTokenCacheability() {
return [
'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0]],
'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL],
'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL],
'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL],
'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'post'],
'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'post'],
'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL, 'post'],
'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL, 'post'],
'token:none,authenticated:false,method:get' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'get'],
'token:test_form_id,authenticated:false,method:get' => ['test_form_id', TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'get'],
];
}

View file

@ -321,34 +321,6 @@ class FormCacheTest extends UnitTestCase {
$this->formCache->getCache($form_build_id, $form_state);
}
/**
* @covers ::loadCachedFormState
*/
public function testLoadCachedFormStateWithSafeStrings() {
$this->assertEmpty(SafeMarkup::getAll());
$form_build_id = 'the_form_build_id';
$form_state = new FormState();
$cached_form = ['#cache_token' => NULL];
$this->formCacheStore->expects($this->once())
->method('get')
->with($form_build_id)
->willReturn($cached_form);
$this->account->expects($this->once())
->method('isAnonymous')
->willReturn(TRUE);
$cached_form_state = ['build_info' => ['safe_strings' => [
'a_safe_string' => ['html' => TRUE],
]]];
$this->formStateCacheStore->expects($this->once())
->method('get')
->with($form_build_id)
->willReturn($cached_form_state);
$this->formCache->getCache($form_build_id, $form_state);
}
/**
* @covers ::setCache
*/
@ -364,7 +336,6 @@ class FormCacheTest extends UnitTestCase {
->with($form_build_id, $form, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -384,7 +355,6 @@ class FormCacheTest extends UnitTestCase {
->method('setWithExpire');
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -408,7 +378,6 @@ class FormCacheTest extends UnitTestCase {
->with($form_build_id, $form_data, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -423,34 +392,6 @@ class FormCacheTest extends UnitTestCase {
$this->formCache->setCache($form_build_id, $form, $form_state);
}
/**
* @covers ::setCache
*/
public function testSetCacheWithSafeStrings() {
// A call to SafeMarkup::format() is appropriate in this test as a way to
// add a string to the safe list in the simplest way possible.
SafeMarkup::format('@value', ['@value' => 'a_safe_string']);
$form_build_id = 'the_form_build_id';
$form = [
'#form_id' => 'the_form_id'
];
$form_state = new FormState();
$this->formCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [
'a_safe_string' => ['html' => TRUE],
];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
$this->formCache->setCache($form_build_id, $form, $form_state);
}
/**
* @covers ::setCache
*/

View file

@ -21,34 +21,28 @@ class FormErrorHandlerTest extends UnitTestCase {
* @covers ::displayErrorMessages
*/
public function testDisplayErrorMessages() {
$link_generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
$link_generator->expects($this->any())
->method('generate')
->willReturnArgument(0);
$renderer = $this->getMock('\Drupal\Core\Render\RendererInterface');
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
->setConstructorArgs([$this->getStringTranslationStub(), $link_generator, $renderer])
->setMethods(['drupalSetMessage'])
->getMock();
$form_error_handler->expects($this->at(0))
->method('drupalSetMessage')
->with('no title given', 'error');
->with('invalid', 'error');
$form_error_handler->expects($this->at(1))
->method('drupalSetMessage')
->with('element is invisible', 'error');
->with('invalid', 'error');
$form_error_handler->expects($this->at(2))
->method('drupalSetMessage')
->with('this missing element is invalid', 'error');
->with('invalid', 'error');
$form_error_handler->expects($this->at(3))
->method('drupalSetMessage')
->with('3 errors have been found: <ul-comma-list-mock><li-mock>Test 1</li-mock><li-mock>Test 2 &amp; a half</li-mock><li-mock>Test 3</li-mock></ul-comma-list-mock>', 'error');
$renderer->expects($this->any())
->method('renderPlain')
->will($this->returnCallback(function ($render_array) {
return $render_array[0]['#markup'] . '<ul-comma-list-mock><li-mock>' . implode(array_map('htmlspecialchars', $render_array[1]['#items']), '</li-mock><li-mock>') . '</li-mock></ul-comma-list-mock>';
}));
->with('no title given', 'error');
$form_error_handler->expects($this->at(4))
->method('drupalSetMessage')
->with('element is invisible', 'error');
$form_error_handler->expects($this->at(5))
->method('drupalSetMessage')
->with('this missing element is invalid', 'error');
$form = [
'#parents' => [],
@ -74,13 +68,6 @@ class FormErrorHandlerTest extends UnitTestCase {
'#id' => 'edit-test3',
],
];
$form['test4'] = [
'#type' => 'textfield',
'#title' => 'Test 4',
'#parents' => ['test4'],
'#id' => 'edit-test4',
'#error_no_message' => TRUE,
];
$form['test5'] = [
'#type' => 'textfield',
'#parents' => ['test5'],
@ -96,7 +83,6 @@ class FormErrorHandlerTest extends UnitTestCase {
$form_state->setErrorByName('test1', 'invalid');
$form_state->setErrorByName('test2', 'invalid');
$form_state->setErrorByName('fieldset][test3', 'invalid');
$form_state->setErrorByName('test4', 'no error message');
$form_state->setErrorByName('test5', 'no title given');
$form_state->setErrorByName('test6', 'element is invisible');
$form_state->setErrorByName('missing_element', 'this missing element is invalid');
@ -110,7 +96,6 @@ class FormErrorHandlerTest extends UnitTestCase {
*/
public function testSetElementErrorsFromFormState() {
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
->setConstructorArgs([$this->getStringTranslationStub(), $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'), $this->getMock('\Drupal\Core\Render\RendererInterface')])
->setMethods(['drupalSetMessage'])
->getMock();

View file

@ -9,7 +9,9 @@ namespace Drupal\Tests\Core\Form;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Form\FormState;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@ -28,12 +30,20 @@ class FormSubmitterTest extends UnitTestCase {
*/
protected $urlGenerator;
/**
* The mocked unrouted URL assembler.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Utility\UnroutedUrlAssemblerInterface
*/
protected $unroutedUrlAssembler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$this->urlGenerator = $this->getMock(UrlGeneratorInterface::class);
$this->unroutedUrlAssembler = $this->getMock(UnroutedUrlAssemblerInterface::class);
}
/**
@ -186,10 +196,14 @@ class FormSubmitterTest extends UnitTestCase {
*/
public function testRedirectWithoutResult() {
$form_submitter = $this->getFormSubmitter();
$this->urlGenerator->expects($this->never())
->method('generateFromPath');
$this->urlGenerator->expects($this->never())
->method('generateFromRoute');
$this->unroutedUrlAssembler->expects($this->never())
->method('assemble');
$container = new ContainerBuilder();
$container->set('url_generator', $this->urlGenerator);
$container->set('unrouted_url_assembler', $this->unroutedUrlAssembler);
\Drupal::setContainer($container);
$form_state = $this->getMock('Drupal\Core\Form\FormStateInterface');
$form_state->expects($this->once())
->method('getRedirect')

View file

@ -8,7 +8,7 @@
namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Menu\ContextualLinkDefault;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
@ -71,11 +71,10 @@ class ContextualLinkDefaultTest extends UnitTestCase {
*/
public function testGetTitle() {
$title = 'Example';
$this->pluginDefinition['title'] = (new TranslationWrapper($title))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup($title, [], [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with($title, array(), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated'));
$this->setupContextualLinkDefault();
@ -87,11 +86,10 @@ class ContextualLinkDefaultTest extends UnitTestCase {
*/
public function testGetTitleWithContext() {
$title = 'Example';
$this->pluginDefinition['title'] = (new TranslationWrapper($title, array(), array('context' => 'context')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup($title, array(), array('context' => 'context'), $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with($title, array(), array('context' => 'context'))
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated with context'));
$this->setupContextualLinkDefault();
@ -103,11 +101,10 @@ class ContextualLinkDefaultTest extends UnitTestCase {
*/
public function testGetTitleWithTitleArguments() {
$title = 'Example @test';
$this->pluginDefinition['title'] = (new TranslationWrapper($title, array('@test' => 'value')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup($title, array('@test' => 'value'), [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with($title, array('@test' => 'value'), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example value'));
$this->setupContextualLinkDefault();

View file

@ -73,13 +73,18 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
$this->accessManager = $this->getMock('\Drupal\Core\Access\AccessManagerInterface');
$this->currentUser = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->currentUser->method('isAuthenticated')
->willReturn(TRUE);
$this->queryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
->disableOriginalConstructor()
->getMock();
$this->defaultMenuTreeManipulators = new DefaultMenuLinkTreeManipulators($this->accessManager, $this->currentUser, $this->queryFactory);
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
$cache_contexts_manager->reveal();
$container = new Container();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);
@ -97,6 +102,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
* - 7
* - 6
* - 8
* - 9
*
* With link 6 being the only external link.
*/
@ -110,6 +116,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '')),
7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')),
8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '')),
9 => DynamicMenuLinkMock::create(array('id' => 'test.example9', 'parent' => ''))->setCurrentUser($this->currentUser),
);
$this->originalTree = array();
$this->originalTree[1] = new MenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, array());
@ -123,6 +130,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
));
$this->originalTree[6] = new MenuLinkTreeElement($this->links[6], FALSE, 1, FALSE, array());
$this->originalTree[8] = new MenuLinkTreeElement($this->links[8], FALSE, 1, FALSE, array());
$this->originalTree[9] = new MenuLinkTreeElement($this->links[9], FALSE, 1, FALSE, array());
}
/**
@ -156,16 +164,17 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
*/
public function testCheckAccess() {
// Those menu links that are non-external will have their access checks
// performed. 8 routes, but 1 is external, 2 already have their 'access'
// property set, and 1 is a child if an inaccessible menu link, so only 4
// performed. 9 routes, but 1 is external, 2 already have their 'access'
// property set, and 1 is a child if an inaccessible menu link, so only 5
// calls will be made.
$this->accessManager->expects($this->exactly(4))
$this->accessManager->expects($this->exactly(5))
->method('checkNamedRoute')
->will($this->returnValueMap(array(
array('example1', array(), $this->currentUser, TRUE, AccessResult::forbidden()),
array('example2', array('foo' => 'bar'), $this->currentUser, TRUE, AccessResult::allowed()->cachePerPermissions()),
array('example3', array('baz' => 'qux'), $this->currentUser, TRUE, AccessResult::neutral()),
array('example5', array(), $this->currentUser, TRUE, AccessResult::allowed()),
array('user.logout', array(), $this->currentUser, TRUE, AccessResult::allowed()),
)));
$this->mockTree();
@ -227,7 +236,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
*/
public function testCheckAccessWithLinkToAnyPagePermission() {
$this->mockTree();
$this->currentUser->expects($this->exactly(8))
$this->currentUser->expects($this->exactly(9))
->method('hasPermission')
->with('link to any page')
->willReturn(TRUE);
@ -243,6 +252,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
$this->assertEquals($expected_access_result, $this->originalTree[5]->subtree[7]->access);
$this->assertEquals($expected_access_result, $this->originalTree[6]->access);
$this->assertEquals($expected_access_result, $this->originalTree[8]->access);
$this->assertEquals($expected_access_result, $this->originalTree[9]->access);
}
/**
@ -253,8 +263,8 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
public function testFlatten() {
$this->mockTree();
$tree = $this->defaultMenuTreeManipulators->flatten($this->originalTree);
$this->assertEquals(array(1, 2, 5, 6, 8), array_keys($this->originalTree));
$this->assertEquals(array(1, 2, 5, 6, 8, 3, 4, 7), array_keys($tree));
$this->assertEquals(array(1, 2, 5, 6, 8, 9), array_keys($this->originalTree));
$this->assertEquals(array(1, 2, 5, 6, 8, 9, 3, 4, 7), array_keys($tree));
}
/**

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Menu\DynamicMenuLinkMock.
*/
namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Session\AccountInterface;
/**
* Defines a mock implementation of a dynamic menu link used in tests only.
*
* Has a dynamic route and title. This is rather contrived, but there are valid
* use cases.
*
* @see \Drupal\user\Plugin\Menu\LoginLogoutMenuLink
*/
class DynamicMenuLinkMock extends MenuLinkMock {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Sets the current user.
*
* Allows the menu link to return the right title and route.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*
* @return $this
*/
public function setCurrentUser(AccountInterface $current_user) {
$this->currentUser = $current_user;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
if ($this->currentUser->isAuthenticated()) {
return 'Log out';
}
else {
return 'Log in';
}
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
if ($this->currentUser->isAuthenticated()) {
return 'user.logout';
}
else {
return 'user.login';
}
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['user.roles:authenticated'];
}
}

View file

@ -8,7 +8,7 @@
namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Menu\LocalActionDefault;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
@ -83,11 +83,10 @@ class LocalActionDefaultTest extends UnitTestCase {
* @see \Drupal\Core\Menu\LocalTaskDefault::getTitle()
*/
public function testGetTitle() {
$this->pluginDefinition['title'] = (new TranslationWrapper('Example'))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup('Example', [], [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with('Example', array(), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated'));
$this->setupLocalActionDefault();
@ -100,11 +99,10 @@ class LocalActionDefaultTest extends UnitTestCase {
* @see \Drupal\Core\Menu\LocalTaskDefault::getTitle()
*/
public function testGetTitleWithContext() {
$this->pluginDefinition['title'] = (new TranslationWrapper('Example', array(), array('context' => 'context')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup('Example', array(), array('context' => 'context'), $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with('Example', array(), array('context' => 'context'))
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated with context'));
$this->setupLocalActionDefault();
@ -115,11 +113,10 @@ class LocalActionDefaultTest extends UnitTestCase {
* Tests the getTitle method with title arguments.
*/
public function testGetTitleWithTitleArguments() {
$this->pluginDefinition['title'] = (new TranslationWrapper('Example @test', array('@test' => 'value')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup('Example @test', array('@test' => 'value'), [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with('Example @test', array('@test' => 'value'), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example value'));
$this->setupLocalActionDefault();

View file

@ -10,7 +10,7 @@ namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Menu\LocalTaskDefault;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
@ -233,11 +233,10 @@ class LocalTaskDefaultTest extends UnitTestCase {
* @covers ::getTitle
*/
public function testGetTitle() {
$this->pluginDefinition['title'] = (new TranslationWrapper('Example'))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup('Example', [], [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with('Example', array(), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated'));
$this->setupLocalTaskDefault();
@ -249,11 +248,10 @@ class LocalTaskDefaultTest extends UnitTestCase {
*/
public function testGetTitleWithContext() {
$title = 'Example';
$this->pluginDefinition['title'] = (new TranslationWrapper($title, array(), array('context' => 'context')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup($title, array(), array('context' => 'context'), $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with($title, array(), array('context' => 'context'))
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example translated with context'));
$this->setupLocalTaskDefault();
@ -264,12 +262,10 @@ class LocalTaskDefaultTest extends UnitTestCase {
* @covers ::getTitle
*/
public function testGetTitleWithTitleArguments() {
$title = 'Example @test';
$this->pluginDefinition['title'] = (new TranslationWrapper('Example @test', array('@test' => 'value')))
->setStringTranslation($this->stringTranslation);
$this->pluginDefinition['title'] = (new TranslatableMarkup('Example @test', array('@test' => 'value'), [], $this->stringTranslation));
$this->stringTranslation->expects($this->once())
->method('translate')
->with($title, array('@test' => 'value'), array())
->method('translateString')
->with($this->pluginDefinition['title'])
->will($this->returnValue('Example value'));
$this->setupLocalTaskDefault();
@ -301,6 +297,23 @@ class LocalTaskDefaultTest extends UnitTestCase {
), $this->localTaskBase->getOptions($route_match));
}
/**
* @covers ::getCacheContexts
* @covers ::getCacheTags
* @covers ::getCacheMaxAge
*/
public function testCacheabilityMetadata() {
$this->pluginDefinition['cache_contexts'] = ['route'];
$this->pluginDefinition['cache_tags'] = ['kitten'];
$this->pluginDefinition['cache_max_age'] = 3600;
$this->setupLocalTaskDefault();
$this->assertEquals(['route'], $this->localTaskBase->getCacheContexts());
$this->assertEquals(['kitten'], $this->localTaskBase->getCacheTags());
$this->assertEquals(3600, $this->localTaskBase->getCacheMaxAge());
}
}
class TestLocalTaskDefault extends LocalTaskDefault {

View file

@ -7,10 +7,18 @@
namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\Language;
use Drupal\Core\Menu\LocalTaskInterface;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Zend\Stdlib\ArrayObject;
@ -84,6 +92,13 @@ class LocalTaskManagerTest extends UnitTestCase {
*/
protected $routeMatch;
/**
* The mocked account.
*
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $account;
/**
* {@inheritdoc}
*/
@ -98,8 +113,10 @@ class LocalTaskManagerTest extends UnitTestCase {
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
$this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->setupLocalTaskManager();
$this->setupNullCacheabilityMetadataValidation();
}
/**
@ -249,8 +266,7 @@ class LocalTaskManagerTest extends UnitTestCase {
->method('getCurrentLanguage')
->will($this->returnValue(new Language(array('id' => 'en'))));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $account);
$this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $this->account);
$property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
$property->setAccessible(TRUE);
@ -393,5 +409,92 @@ class LocalTaskManagerTest extends UnitTestCase {
return $local_tasks;
}
/**
* @covers ::getTasksBuild
*/
public function testGetTasksBuildWithCacheabilityMetadata() {
$definitions = $this->getLocalTaskFixtures();
$this->pluginDiscovery->expects($this->once())
->method('getDefinitions')
->will($this->returnValue($definitions));
// Set up some cacheablity metadata and ensure its merged together.
$definitions['menu_local_task_test_tasks_settings']['cache_tags'] = ['tag.example1'];
$definitions['menu_local_task_test_tasks_settings']['cache_contexts'] = ['context.example1'];
$definitions['menu_local_task_test_tasks_edit']['cache_tags'] = ['tag.example2'];
$definitions['menu_local_task_test_tasks_edit']['cache_contexts'] = ['context.example2'];
// Test the cacheability metadata of access checking.
$definitions['menu_local_task_test_tasks_view_child1']['access'] = AccessResult::allowed()->addCacheContexts(['user.permissions']);
$this->setupFactoryAndLocalTaskPlugins($definitions, 'menu_local_task_test_tasks_view');
$this->setupLocalTaskManager();
$this->controllerResolver->expects($this->any())
->method('getArguments')
->willReturn([]);
$this->routeMatch->expects($this->any())
->method('getRouteName')
->willReturn('menu_local_task_test_tasks_view');
$this->routeMatch->expects($this->any())
->method('getRawParameters')
->willReturn(new ParameterBag());
$cacheability = new CacheableMetadata();
$local_tasks = $this->manager->getTasksBuild('menu_local_task_test_tasks_view', $cacheability);
// Ensure that all cacheability metadata is merged together.
$this->assertEquals(['tag.example1', 'tag.example2'], $cacheability->getCacheTags());
$this->assertEquals(['context.example1', 'context.example2', 'route', 'user.permissions'], $cacheability->getCacheContexts());
}
protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) {
$map = [];
$access_manager_map = [];
foreach ($definitions as $plugin_id => $info) {
$info += ['access' => AccessResult::allowed()];
$mock = $this->prophesize(LocalTaskInterface::class);
$mock->willImplement(CacheableDependencyInterface::class);
$mock->getRouteName()->willReturn($info['route_name']);
$mock->getTitle()->willReturn($info['title']);
$mock->getRouteParameters(Argument::cetera())->willReturn([]);
$mock->getOptions(Argument::cetera())->willReturn([]);
$mock->getActive()->willReturn($plugin_id === $active_plugin_id);
$mock->getWeight()->willReturn(isset($info['weight']) ? $info['weight'] : 0);
$mock->getCacheContexts()->willReturn(isset($info['cache_contexts']) ? $info['cache_contexts'] : []);
$mock->getCacheTags()->willReturn(isset($info['cache_tags']) ? $info['cache_tags'] : []);
$mock->getCacheMaxAge()->willReturn(isset($info['cache_max_age']) ? $info['cache_max_age'] : Cache::PERMANENT);
$access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']];
$map[] = [$info['id'], [], $mock->reveal()];
}
$this->accessManager->expects($this->any())
->method('checkNamedRoute')
->willReturnMap($access_manager_map);
$this->factory->expects($this->any())
->method('createInstance')
->will($this->returnValueMap($map));
}
protected function setupNullCacheabilityMetadataValidation() {
$container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
$cache_context_manager = $this->prophesize(CacheContextsManager::class);
foreach ([NULL, ['user.permissions'], ['route'], ['route', 'context.example1'], ['context.example1', 'route'], ['context.example1', 'route', 'context.example2'], ['context.example1', 'context.example2', 'route'], ['context.example1', 'context.example2', 'route', 'user.permissions']] as $argument) {
$cache_context_manager->assertValidTokens($argument)->willReturn(TRUE);
}
$container->set('cache_contexts_manager', $cache_context_manager->reveal());
\Drupal::setContainer($container);
}
}

View file

@ -62,6 +62,7 @@ class PathProcessorTest extends UnitTestCase {
$method_definitions = array(
LanguageNegotiationUrl::METHOD_ID => array(
'class' => '\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl',
'weight' => 9,
),
);
@ -134,7 +135,10 @@ class PathProcessorTest extends UnitTestCase {
->getMock();
$negotiator->expects($this->any())
->method('getNegotiationMethods')
->will($this->returnValue(array(LanguageNegotiationUrl::METHOD_ID => array('class' => 'Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl'))));
->will($this->returnValue(array(LanguageNegotiationUrl::METHOD_ID => array(
'class' => 'Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl',
'weight' => 9,
))));
$method = new LanguageNegotiationUrl();
$method->setConfig($config_factory_stub);
$method->setLanguageManager($this->languageManager);

View file

@ -83,10 +83,8 @@ class ContextTest extends UnitTestCase {
->setMethods(array('getDefaultValue', 'getDataDefinition'))
->getMockForAbstractClass();
$context = new Context($this->contextDefinition);
$context->setTypedDataManager($this->typedDataManager);
$typed_data = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$context->setContextValue($typed_data);
$context = new Context($this->contextDefinition, $typed_data);
$this->assertSame($typed_data, $context->getContextData());
}
@ -120,7 +118,7 @@ class ContextTest extends UnitTestCase {
->method('getCacheMaxAge')
->willReturn(60);
$context->setContextValue($cacheable_dependency);
$context = Context::createFromContext($context, $cacheable_dependency);
$this->assertSame($cacheable_dependency, $context->getContextData());
$this->assertEquals(['node:1'], $context->getCacheTags());
$this->assertEquals(['route'], $context->getCacheContexts());

View file

@ -50,11 +50,10 @@ class ContextTypedDataTest extends UnitTestCase {
\Drupal::setContainer($container);
$definition = new ContextDefinition('any');
$context = new Context($definition);
$data_definition = DataDefinition::create('string');
$this->typedData = new StringData($data_definition);
$this->typedData->setValue('example string');
$context->setContextData($this->typedData);
$context = new Context($definition, $this->typedData);
$value = $context->getContextValue();
$this->assertSame($value, $this->typedData->getValue());
}

View file

@ -11,6 +11,8 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextHandler;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\Plugin\DataType\StringData;
use Drupal\Tests\UnitTestCase;
/**
@ -229,16 +231,20 @@ class ContextHandlerTest extends UnitTestCase {
* @covers ::applyContextMapping
*/
public function testApplyContextMapping() {
$context_hit_data = StringData::createInstance(DataDefinition::create('string'));
$context_hit_data->setValue('foo');
$context_hit = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context_hit->expects($this->atLeastOnce())
->method('getContextValue')
->will($this->returnValue(array('foo')));
->method('getContextData')
->will($this->returnValue($context_hit_data));
$context_miss_data = StringData::createInstance(DataDefinition::create('string'));
$context_miss_data->setValue('bar');
$context_hit->expects($this->atLeastOnce())
->method('hasContextValue')
->willReturn(TRUE);
$context_miss = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context_miss->expects($this->never())
->method('getContextValue');
->method('getContextData');
$contexts = array(
'hit' => $context_hit,
@ -256,7 +262,7 @@ class ContextHandlerTest extends UnitTestCase {
->will($this->returnValue(array('hit' => $context_definition)));
$plugin->expects($this->once())
->method('setContextValue')
->with('hit', array('foo'));
->with('hit', $context_hit_data);
// Make sure that the cacheability metadata is passed to the plugin context.
$plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
@ -416,10 +422,12 @@ class ContextHandlerTest extends UnitTestCase {
* @covers ::applyContextMapping
*/
public function testApplyContextMappingConfigurableAssigned() {
$context_data = StringData::createInstance(DataDefinition::create('string'));
$context_data->setValue('foo');
$context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
$context->expects($this->atLeastOnce())
->method('getContextValue')
->will($this->returnValue(array('foo')));
->method('getContextData')
->will($this->returnValue($context_data));
$context->expects($this->atLeastOnce())
->method('hasContextValue')
->willReturn(TRUE);
@ -439,7 +447,7 @@ class ContextHandlerTest extends UnitTestCase {
->will($this->returnValue(array('hit' => $context_definition)));
$plugin->expects($this->once())
->method('setContextValue')
->with('hit', array('foo'));
->with('hit', $context_data);
// Make sure that the cacheability metadata is passed to the plugin context.
$plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');

View file

@ -7,7 +7,7 @@
namespace Drupal\Tests\Core\Plugin\Discovery;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use org\bovigo\vfs\vfsStream;
@ -104,9 +104,9 @@ EOS;
$plugin_1 = $definitions['test_plugin'];
$plugin_2 = $definitions['test_plugin2'];
$this->assertInstanceOf(TranslationWrapper::class, $plugin_1['title']);
$this->assertInstanceOf(TranslatableMarkup::class, $plugin_1['title']);
$this->assertEquals([], $plugin_1['title']->getOptions());
$this->assertInstanceOf(TranslationWrapper::class, $plugin_2['title']);
$this->assertInstanceOf(TranslatableMarkup::class, $plugin_2['title']);
$this->assertEquals(['context' => 'test-context'], $plugin_2['title']->getOptions());
}

View file

@ -60,6 +60,7 @@ class BubbleableMetadataTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
$container->set('renderer', $renderer);
@ -664,6 +665,7 @@ class BubbleableMetadataTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);

View file

@ -7,7 +7,7 @@
namespace Drupal\Tests\Core\Render\Element;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Render\Markup;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Render\Element\HtmlTag;
@ -86,7 +86,7 @@ class HtmlTagTest extends UnitTestCase {
// Ensure that #value is not filtered if it is marked as safe.
$element = array(
'#tag' => 'p',
'#value' => SafeString::create('<script>value</script>'),
'#value' => Markup::create('<script>value</script>'),
);
$tags[] = array($element, "<p><script>value</script></p>\n");
@ -106,8 +106,8 @@ class HtmlTagTest extends UnitTestCase {
*/
public function testPreRenderConditionalComments($element, $expected, $set_safe = FALSE) {
if ($set_safe) {
$element['#prefix'] = SafeString::create($element['#prefix']);
$element['#suffix'] = SafeString::create($element['#suffix']);
$element['#prefix'] = Markup::create($element['#prefix']);
$element['#suffix'] = Markup::create($element['#suffix']);
}
$this->assertEquals($expected, HtmlTag::preRenderConditionalComments($element));
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Render\Element\TableSelectTest.
*/
namespace Drupal\Tests\Core\Render\Element;
use Drupal\Core\Form\FormState;
use Drupal\Core\Link;
use Drupal\Core\Render\Element\Tableselect;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\TranslationWrapper;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Render\Element\Tableselect
* @group Render
*/
class TableSelectTest extends UnitTestCase {
/**
* @covers ::processTableselect
*/
public function testProcessTableselectWithLinkTitle() {
$element = [];
$form_state = new FormState();
$complete_form = [];
$element_object = new Tableselect([], 'table_select', []);
$info = $element_object->getInfo();
$element += $info;
$element['#value'] = 0;
$element['#options'][] = [
'title' => new Link('my-text', Url::fromRoute('<front>'))
];
$element['#attributes'] = [];
Tableselect::processTableselect($element, $form_state, $complete_form);
$this->assertEquals('', $element[0]['#title']);
}
/**
* @covers ::processTableselect
*/
public function testProcessTableselectWithStringTitle() {
$element = [];
$form_state = new FormState();
$complete_form = [];
$element_object = new Tableselect([], 'table_select', []);
$info = $element_object->getInfo();
$element += $info;
$element['#value'] = 0;
$element['#options'][] = [
'title' => ['data' => ['#title' => 'Static title']],
];
$element['#attributes'] = [];
Tableselect::processTableselect($element, $form_state, $complete_form);
$this->assertEquals(new TranslatableMarkup('Update @title', ['@title' => 'Static title']), $element[0]['#title']);
}
}

View file

@ -0,0 +1,168 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Render\Placeholder\ChainedPlaceholderStrategyTest.
*/
namespace Drupal\Tests\Core\Render\Placeholder;
use Drupal\Core\Render\Placeholder\ChainedPlaceholderStrategy;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Render\Placeholder\ChainedPlaceholderStrategy
* @group Render
*/
class ChainedPlaceholderStrategyTest extends UnitTestCase {
/**
* @covers ::addPlaceholderStrategy
* @covers ::processPlaceholders
*
* @dataProvider providerProcessPlaceholders
*/
public function testProcessPlaceholders($strategies, $placeholders, $result) {
$chained_placeholder_strategy = new ChainedPlaceholderStrategy();
foreach ($strategies as $strategy) {
$chained_placeholder_strategy->addPlaceholderStrategy($strategy);
}
$this->assertEquals($result, $chained_placeholder_strategy->processPlaceholders($placeholders));
}
/**
* Provides a list of render strategies, placeholders and results.
*
* @return array
*/
public function providerProcessPlaceholders() {
$data = [];
// Empty placeholders.
$data['empty placeholders'] = [[], [], []];
// Placeholder removing strategy.
$placeholders = [
'remove-me' => ['#markup' => 'I-am-a-llama-that-will-be-removed-sad-face.'],
];
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn([]);
$dev_null_strategy = $prophecy->reveal();
$data['placeholder removing strategy'] = [[$dev_null_strategy], $placeholders, []];
// Fake Single Flush strategy.
$placeholders = [
'67890' => ['#markup' => 'special-placeholder'],
];
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn($placeholders);
$single_flush_strategy = $prophecy->reveal();
$data['fake single flush strategy'] = [[$single_flush_strategy], $placeholders, $placeholders];
// Fake ESI strategy.
$placeholders = [
'12345' => ['#markup' => 'special-placeholder-for-esi'],
];
$result = [
'12345' => ['#markup' => '<esi:include src="/fragment/12345" />'],
];
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn($result);
$esi_strategy = $prophecy->reveal();
$data['fake esi strategy'] = [[$esi_strategy], $placeholders, $result];
// ESI + SingleFlush strategy (ESI replaces all).
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn($result);
$esi_strategy = $prophecy->reveal();
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->shouldNotBeCalled();
$prophecy->processPlaceholders($result)->shouldNotBeCalled();
$prophecy->processPlaceholders([])->shouldNotBeCalled();
$single_flush_strategy = $prophecy->reveal();
$data['fake esi and single_flush strategy - esi replaces all'] = [[$esi_strategy, $single_flush_strategy], $placeholders, $result];
// ESI + SingleFlush strategy (mixed).
$placeholders = [
'12345' => ['#markup' => 'special-placeholder-for-ESI'],
'67890' => ['#markup' => 'special-placeholder'],
'foo' => ['#markup' => 'bar'],
];
$esi_result = [
'12345' => ['#markup' => '<esi:include src="/fragment/12345" />'],
];
$normal_result = [
'67890' => ['#markup' => 'special-placeholder'],
'foo' => ['#markup' => 'bar'],
];
$result = $esi_result + $normal_result;
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn($esi_result);
$esi_strategy = $prophecy->reveal();
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($normal_result)->willReturn($normal_result);
$single_flush_strategy = $prophecy->reveal();
$data['fake esi and single_flush strategy - mixed'] = [[$esi_strategy, $single_flush_strategy], $placeholders, $result];
return $data;
}
/**
* @covers ::processPlaceholders
*
* @expectedException \AssertionError
* @expectedExceptionMessage At least one placeholder strategy must be present; by default the fallback strategy \Drupal\Core\Render\Placeholder\SingleFlushStrategy is always present.
*/
public function testProcessPlaceholdersNoStrategies() {
// Placeholders but no strategies defined.
$placeholders = [
'assert-me' => ['#markup' => 'I-am-a-llama-that-will-lead-to-an-assertion-by-the-chained-placeholder-strategy.'],
];
$chained_placeholder_strategy = new ChainedPlaceholderStrategy();
$chained_placeholder_strategy->processPlaceholders($placeholders);
}
/**
* @covers ::processPlaceholders
*
* @expectedException \AssertionError
* @expectedExceptionMessage Processed placeholders must be a subset of all placeholders.
*/
public function testProcessPlaceholdersWithRoguePlaceholderStrategy() {
// Placeholders but no strategies defined.
$placeholders = [
'assert-me' => ['#markup' => 'llama'],
];
$result = [
'assert-me' => ['#markup' => 'llama'],
'new-placeholder' => ['#markup' => 'rogue llama'],
];
$prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
$prophecy->processPlaceholders($placeholders)->willReturn($result);
$rogue_strategy = $prophecy->reveal();
$chained_placeholder_strategy = new ChainedPlaceholderStrategy();
$chained_placeholder_strategy->addPlaceholderStrategy($rogue_strategy);
$chained_placeholder_strategy->processPlaceholders($placeholders);
}
}

View file

@ -10,7 +10,7 @@ namespace Drupal\Tests\Core\Render;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Element;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Render\Markup;
/**
* @coversDefaultClass \Drupal\Core\Render\Renderer
@ -50,11 +50,12 @@ class RendererPlaceholdersTest extends RendererTestBase {
* tags in its subtree, i.e. via bubbling)
* - B) manually generated placeholder
*
* So, in total 2*5 = 10 permutations.
* So, in total 2*8 = 16 permutations. (On one axis: uncacheable vs.
* uncacheable = 2; on the other axis: A17 and B = 8.)
*
* @todo Cases A5, A6 and A7 are not yet supported by core. So that makes for
* only 10 permutations currently, instead of 16. That will be done in
* https://www.drupal.org/node/2543334
* @todo Case A5 is not yet supported by core. So that makes for only 14
* permutations currently, instead of 16. That will be done in
* https://www.drupal.org/node/2559847
*
* @return array
*/
@ -69,10 +70,10 @@ class RendererPlaceholdersTest extends RendererTestBase {
$token_render_array['#cache']['keys'] = $cache_keys;
}
$token = hash('crc32b', serialize($token_render_array));
// \Drupal\Core\Render\SafeString::create() is necessary as the render
// \Drupal\Core\Render\Markup::create() is necessary as the render
// system would mangle this markup. As this is exactly what happens at
// runtime this is a valid use-case.
return SafeString::create('<drupal-render-placeholder callback="Drupal\Tests\Core\Render\PlaceholdersTest::callback" arguments="' . '0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>');
return Markup::create('<drupal-render-placeholder callback="Drupal\Tests\Core\Render\PlaceholdersTest::callback" arguments="' . '0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>');
};
$extract_placeholder_render_array = function ($placeholder_render_array) {
@ -141,6 +142,42 @@ class RendererPlaceholdersTest extends RendererTestBase {
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
],
];
// Note the absence of '#create_placeholder', presence of max-age=0 created
// by the #lazy_builder callback.
// @todo in https://www.drupal.org/node/2559847
$base_element_a5 = [];
// Note the absence of '#create_placeholder', presence of high cardinality
// cache context created by the #lazy_builder callback.
// @see \Drupal\Tests\Core\Render\PlaceholdersTest::callbackPerUser()
$base_element_a6 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
],
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callbackPerUser', $args],
],
];
// Note the absence of '#create_placeholder', presence of high-invalidation
// frequency cache tag created by the #lazy_builder callback.
// @see \Drupal\Tests\Core\Render\PlaceholdersTest::callbackTagCurrentTemperature()
$base_element_a7 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
],
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callbackTagCurrentTemperature', $args],
],
];
// Note the absence of '#create_placeholder', but the presence of
// '#attached[placeholders]'.
$base_element_b = [
@ -172,6 +209,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
// Case two: render array that has a placeholder that is:
@ -185,6 +224,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$args,
$expected_placeholder_render_array,
$keys,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
@ -211,6 +252,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
// Case four: render array that has a placeholder that is:
@ -224,7 +267,9 @@ class RendererPlaceholdersTest extends RendererTestBase {
$args,
$expected_placeholder_render_array,
FALSE,
[]
[],
[],
[],
];
// Case five: render array that has a placeholder that is:
@ -239,6 +284,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
// Case six: render array that has a placeholder that is:
@ -257,6 +304,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$args,
$expected_placeholder_render_array,
$cid_parts,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
@ -284,6 +333,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
// Case eight: render array that has a placeholder that is:
@ -298,6 +349,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$args,
$expected_placeholder_render_array,
$keys,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
@ -313,7 +366,109 @@ class RendererPlaceholdersTest extends RendererTestBase {
],
];
// Case nine: render array that has a placeholder that is:
// Case nine: render array that DOES NOT have a placeholder that is:
// - NOT created, despite max-age=0 that is bubbled
// - uncacheable
// (because the render element with #lazy_builder does not have #cache[keys]
// and hence the max-age=0 bubbles up further)
// @todo in https://www.drupal.org/node/2559847
// Case ten: render array that has a placeholder that is:
// - automatically created, and automatically triggered due to max-age=0
// that is bubbled
// - cacheable
// @todo in https://www.drupal.org/node/2559847
// Case eleven: render array that DOES NOT have a placeholder that is:
// - NOT created, despite high cardinality cache contexts that are bubbled
// - uncacheable
$element_without_cache_keys = $base_element_a6;
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a6['placeholder']);
$cases[] = [
$element_without_cache_keys,
$args,
$expected_placeholder_render_array,
FALSE,
['user'],
[],
[],
];
// Case twelve: render array that has a placeholder that is:
// - automatically created, and automatically triggered due to high
// cardinality cache contexts that are bubbled
// - cacheable
$element_with_cache_keys = $base_element_a6;
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
$expected_placeholder_render_array['#cache']['keys'] = $keys;
$cases[] = [
$element_with_cache_keys,
$args,
$expected_placeholder_render_array,
$keys,
['user'],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => ['user'],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
],
];
// Case thirteen: render array that has a placeholder that is:
// - automatically created, and automatically triggered due to high
// invalidation frequency cache tags that are bubbled
// - uncacheable
$element_without_cache_keys = $base_element_a7;
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a7['placeholder']);
$cases[] = [
$element_without_cache_keys,
$args,
$expected_placeholder_render_array,
FALSE,
[],
['current-temperature'],
[],
];
// Case fourteen: render array that has a placeholder that is:
// - automatically created, and automatically triggered due to high
// invalidation frequency cache tags that are bubbled
// - cacheable
$element_with_cache_keys = $base_element_a7;
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
$expected_placeholder_render_array['#cache']['keys'] = $keys;
$cases[] = [
$element_with_cache_keys,
$args,
$expected_placeholder_render_array,
$keys,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => ['current-temperature'],
'max-age' => Cache::PERMANENT,
],
],
];
// Case fifteen: render array that has a placeholder that is:
// - manually created
// - uncacheable
$x = $base_element_b;
@ -325,9 +480,11 @@ class RendererPlaceholdersTest extends RendererTestBase {
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
// Case ten: render array that has a placeholder that is:
// Case sixteen: render array that has a placeholder that is:
// - manually created
// - cacheable
$x = $base_element_b;
@ -345,6 +502,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
$args,
$expected_placeholder_render_array,
$keys,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
@ -388,11 +547,28 @@ class RendererPlaceholdersTest extends RendererTestBase {
* @param array $expected_data
* FALSE if no render cache item is expected, a render array with the
* expected values if a render cache item is expected.
* @param string[] $bubbled_cache_contexts
* Additional cache contexts that were bubbled when the placeholder was
* rendered.
*/
protected function assertPlaceholderRenderCache($cid_parts, array $expected_data) {
protected function assertPlaceholderRenderCache($cid_parts, array $bubbled_cache_contexts, array $expected_data) {
if ($cid_parts !== FALSE) {
if ($bubbled_cache_contexts) {
// Verify render cached placeholder.
$cached_element = $this->memoryCache->get(implode(':', $cid_parts))->data;
$expected_redirect_element = [
'#cache_redirect' => TRUE,
'#cache' => $expected_data['#cache'] + [
'keys' => $cid_parts,
'bin' => 'render',
],
];
$this->assertEquals($expected_redirect_element, $cached_element, 'The correct cache redirect exists.');
}
// Verify render cached placeholder.
$cached_element = $this->memoryCache->get(implode(':', $cid_parts))->data;
$cached = $this->memoryCache->get(implode(':', array_merge($cid_parts, $bubbled_cache_contexts)));
$cached_element = $cached->data;
$this->assertEquals($expected_data, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by the placeholder being replaced.');
}
}
@ -402,7 +578,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
*
* @dataProvider providerPlaceholders
*/
public function testUncacheableParent($element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) {
public function testUncacheableParent($element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $bubbled_cache_contexts, array $bubbled_cache_tags, array $placeholder_expected_render_cache_array) {
if ($placeholder_cid_parts) {
$this->setupMemoryCache();
}
@ -422,7 +598,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
'dynamic_animal' => $args[0],
];
$this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.');
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array);
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $bubbled_cache_contexts, $placeholder_expected_render_cache_array);
}
/**
@ -434,14 +610,15 @@ class RendererPlaceholdersTest extends RendererTestBase {
*
* @dataProvider providerPlaceholders
*/
public function testCacheableParent($test_element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) {
public function testCacheableParent($test_element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $bubbled_cache_contexts, array $bubbled_cache_tags, array $placeholder_expected_render_cache_array) {
$element = $test_element;
$this->setupMemoryCache();
$this->setUpRequest('GET');
$token = hash('crc32b', serialize($expected_placeholder_render_array));
$expected_placeholder_markup = '<drupal-render-placeholder callback="Drupal\Tests\Core\Render\PlaceholdersTest::callback" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>';
$placeholder_callback = $expected_placeholder_render_array['#lazy_builder'][0];
$expected_placeholder_markup = '<drupal-render-placeholder callback="' . $placeholder_callback . '" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>';
$this->assertSame($expected_placeholder_markup, Html::normalize($expected_placeholder_markup), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.');
// GET request: #cache enabled, cache miss.
@ -456,30 +633,105 @@ class RendererPlaceholdersTest extends RendererTestBase {
'dynamic_animal' => $args[0],
];
$this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.');
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array);
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $bubbled_cache_contexts, $placeholder_expected_render_cache_array);
// GET request: validate cached data.
$cached_element = $this->memoryCache->get('placeholder_test_GET')->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p>' . $expected_placeholder_markup,
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
$cached = $this->memoryCache->get('placeholder_test_GET');
// There are three edge cases, where the shape of the render cache item for
// the parent (with CID 'placeholder_test_GET') is vastly different. These
// are the cases where:
// - the placeholder is uncacheable (because it has no #cache[keys]), and;
// - cacheability metadata that meets auto_placeholder_conditions is bubbled
$has_uncacheable_lazy_builder = !isset($test_element['placeholder']['#cache']['keys']) && isset($test_element['placeholder']['#lazy_builder']);
// Edge cases: always where both bubbling of an auto-placeholdering
// condition happens from within a #lazy_builder that is uncacheable.
// - uncacheable + A5 (cache max-age)
// @todo in https://www.drupal.org/node/2559847
// - uncacheable + A6 (cache context)
$edge_case_a6_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\Tests\Core\Render\PlaceholdersTest::callbackPerUser';
// - uncacheable + A7 (cache tag)
$edge_case_a7_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\Tests\Core\Render\PlaceholdersTest::callbackTagCurrentTemperature';
// The redirect-cacheable edge case: a high-cardinality cache context is
// bubbled from a #lazy_builder callback for an uncacheable placeholder. The
// element containing the uncacheable placeholder has cache keys set, and
// due to the bubbled cache contexts it creates a cache redirect.
if ($edge_case_a6_uncacheable) {
$cached_element = $cached->data;
$expected_redirect = [
'#cache_redirect' => TRUE,
'#cache' => [
'keys' => ['placeholder_test_GET'],
'contexts' => ['user'],
'tags' => [],
'max-age' => Cache::PERMANENT,
'bin' => 'render',
],
'placeholders' => [
$expected_placeholder_markup => [
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
];
$this->assertEquals($expected_redirect, $cached_element);
// Follow the redirect.
$cached_element = $this->memoryCache->get('placeholder_test_GET:' . implode(':', $bubbled_cache_contexts))->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
'dynamic_animal' => $args[0],
],
],
],
'#cache' => [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
];
$expected_element['#attached']['placeholders'][$expected_placeholder_markup] = $expected_placeholder_render_array;
$this->assertEquals($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by placeholder #lazy_builder callbacks.');
'#cache' => [
'contexts' => $bubbled_cache_contexts,
'tags' => [],
'max-age' => Cache::PERMANENT,
],
];
$this->assertEquals($expected_element, $cached_element, 'The parent is render cached with a redirect in ase a cache context is bubbled from an uncacheable child (no #cache[keys]) with a #lazy_builder.');
}
// The normally cacheable edge case: a high-invalidation frequency cache tag
// is bubbled from a #lazy_builder callback for an uncacheable placeholder.
// The element containing the uncacheable placeholder has cache keys set,
// and also has the bubbled cache tags.
elseif ($edge_case_a7_uncacheable) {
$cached_element = $cached->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => $bubbled_cache_tags,
'max-age' => Cache::PERMANENT,
],
];
$this->assertEquals($expected_element, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by placeholder #lazy_builder callbacks.');
}
// The regular case.
else {
$cached_element = $cached->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p>' . $expected_placeholder_markup,
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
'placeholders' => [
$expected_placeholder_markup => [
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
],
],
],
'#cache' => [
'contexts' => [],
'tags' => $bubbled_cache_tags,
'max-age' => Cache::PERMANENT,
],
];
$expected_element['#attached']['placeholders'][$expected_placeholder_markup] = $expected_placeholder_render_array;
$this->assertEquals($expected_element, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by placeholder #lazy_builder callbacks.');
}
// GET request: #cache enabled, cache hit.
$element = $test_element;
@ -527,7 +779,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
// Even when the child element's placeholder is cacheable, it should not
// generate a render cache item.
$this->assertPlaceholderRenderCache(FALSE, []);
$this->assertPlaceholderRenderCache(FALSE, [], []);
}
/**
@ -600,7 +852,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
]];
$result = $this->renderer->renderRoot($element);
$this->assertInstanceOf('\Drupal\Core\Render\SafeString', $result);
$this->assertInstanceOf('\Drupal\Core\Render\Markup', $result);
$this->assertEquals('<p>This is a rendered placeholder!</p>', (string) $result);
}

View file

@ -13,7 +13,7 @@ use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Render\Markup;
use Drupal\Core\Template\Attribute;
/**
@ -35,18 +35,6 @@ class RendererTest extends RendererTestBase {
'#children' => '',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Reset the static list of SafeStrings to prevent bleeding between tests.
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$reflected_property->setValue([]);
}
/**
* @covers ::render
* @covers ::doRender
@ -111,7 +99,7 @@ class RendererTest extends RendererTestBase {
], '&lt;em&gt;foo&lt;/em&gt;'];
// Safe strings in #plain_text are are still escaped.
$data[] = [[
'#plain_text' => SafeString::create('<em>foo</em>'),
'#plain_text' => Markup::create('<em>foo</em>'),
], '&lt;em&gt;foo&lt;/em&gt;'];
// Renderable child element.
$data[] = [[
@ -737,10 +725,10 @@ class RendererTest extends RendererTestBase {
],
// Collect expected property names.
'#cache_properties' => array_keys(array_filter($expected_results)),
'child1' => ['#markup' => SafeString::create('1')],
'child2' => ['#markup' => SafeString::create('2')],
'child1' => ['#markup' => Markup::create('1')],
'child2' => ['#markup' => Markup::create('2')],
// Mark the value as safe.
'#custom_property' => SafeMarkup::checkPlain('custom_value'),
'#custom_property' => Markup::create('custom_value'),
'#custom_property_array' => ['custom value'],
];

View file

@ -12,7 +12,8 @@ use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\ContextCacheKeys;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RenderCache;
use Drupal\Core\Render\PlaceholderGenerator;
use Drupal\Core\Render\PlaceholderingRenderCache;
use Drupal\Core\Render\Renderer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -34,10 +35,17 @@ class RendererTestBase extends UnitTestCase {
/**
* The tested render cache.
*
* @var \Drupal\Core\Render\RenderCache
* @var \Drupal\Core\Render\PlaceholderingRenderCache
*/
protected $renderCache;
/**
* The tested placeholder generator.
*
* @var \Drupal\Core\Render\PlaceholderGenerator
*/
protected $placeholderGenerator;
/**
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
@ -136,6 +144,7 @@ class RendererTestBase extends UnitTestCase {
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
$current_user_role = &$this->currentUserRole;
$this->cacheContextsManager->expects($this->any())
->method('convertTokensToKeys')
@ -158,8 +167,9 @@ class RendererTestBase extends UnitTestCase {
}
return new ContextCacheKeys($keys, new CacheableMetadata());
});
$this->renderCache = new RenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager);
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->renderCache, $this->requestStack, $this->rendererConfig);
$this->placeholderGenerator = new PlaceholderGenerator($this->rendererConfig);
$this->renderCache = new PlaceholderingRenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager, $this->placeholderGenerator);
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->placeholderGenerator, $this->renderCache, $this->requestStack, $this->rendererConfig);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
@ -269,4 +279,34 @@ class PlaceholdersTest {
];
}
/**
* #lazy_builder callback; attaches setting, generates markup, user-specific.
*
* @param string $animal
* An animal.
*
* @return array
* A renderable array.
*/
public static function callbackPerUser($animal) {
$build = static::callback($animal);
$build['#cache']['contexts'][] = 'user';
return $build;
}
/**
* #lazy_builder callback; attaches setting, generates markup, cache tag.
*
* @param string $animal
* An animal.
*
* @return array
* A renderable array.
*/
public static function callbackTagCurrentTemperature($animal) {
$build = static::callback($animal);
$build['#cache']['tags'][] = 'current-temperature';
return $build;
}
}

View file

@ -145,7 +145,9 @@ class RoleAccessCheckTest extends UnitTestCase {
* @dataProvider roleAccessProvider
*/
public function testRoleAccess($path, $grant_accounts, $deny_accounts) {
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
$cache_contexts_manager->reveal();
$container = new Container();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);

View file

@ -63,6 +63,7 @@ class UrlGeneratorTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);
@ -347,79 +348,6 @@ class UrlGeneratorTest extends UnitTestCase {
$this->assertGenerateFromRoute('test_1', [], $options, 'https://localhost/hello/world', (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheContexts(['url.site']));
}
/**
* Tests path-based URL generation.
*/
public function testPathBasedURLGeneration() {
$base_path = '/subdir';
$base_url = 'http://www.example.com' . $base_path;
foreach (array('', 'index.php/') as $script_path) {
foreach (array(FALSE, TRUE) as $absolute) {
// Setup a fake request which looks like a Drupal installed under the
// subdir "subdir" on the domain www.example.com.
// To reproduce the values install Drupal like that and use a debugger.
$server = [
'SCRIPT_NAME' => '/subdir/index.php',
'SCRIPT_FILENAME' => $this->root . '/index.php',
'SERVER_NAME' => 'http://www.example.com',
];
$request = Request::create('/subdir/' . $script_path, 'GET', [], [], [], $server);
$request->headers->set('host', ['www.example.com']);
$this->requestStack->push($request);
// Determine the expected bubbleable metadata.
$expected_cacheability = (new BubbleableMetadata())
->setCacheContexts($absolute ? ['url.site'] : [])
->setCacheMaxAge(Cache::PERMANENT);
// Get the expected start of the path string.
$base = ($absolute ? $base_url . '/' : $base_path . '/') . $script_path;
$url = $base . 'node/123';
$result = $this->generator->generateFromPath('node/123', array('absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('node/123', array('absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
$url = $base . 'node/123#foo';
$result = $this->generator->generateFromPath('node/123', array('fragment' => 'foo', 'absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('node/123', array('fragment' => 'foo', 'absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
$url = $base . 'node/123?foo';
$result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
$url = $base . 'node/123?foo=bar&bar=baz';
$result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
$url = $base . 'node/123?foo#bar';
$result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
$url = $base;
$result = $this->generator->generateFromPath('<front>', array('absolute' => $absolute));
$this->assertEquals($url, $result, "$url == $result");
$generated_url = $this->generator->generateFromPath('<front>', array('absolute' => $absolute), TRUE);
$this->assertEquals($url, $generated_url->getGeneratedUrl(), "$url == $result");
$this->assertEquals($expected_cacheability, BubbleableMetadata::createFromObject($generated_url));
}
}
}
/**
* Tests generating a relative URL with no path.
*

View file

@ -0,0 +1,112 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\StringTranslation\TranslatableMarkupTest.
*/
namespace Drupal\Tests\Core\StringTranslation;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
/**
* Tests the TranslatableMarkup class.
*
* @coversDefaultClass \Drupal\Core\StringTranslation\TranslatableMarkup
* @group StringTranslation
*/
class TranslatableMarkupTest extends UnitTestCase {
/**
* The error message of the last error in the error handler.
*
* @var string
*/
protected $lastErrorMessage;
/**
* The error number of the last error in the error handler.
*
* @var int
*/
protected $lastErrorNumber;
/**
* Custom error handler that saves the last error.
*
* We need this custom error handler because we cannot rely on the error to
* exception conversion as __toString is never allowed to leak any kind of
* exception.
*
* @param int $error_number
* The error number.
* @param string $error_message
* The error message.
*/
public function errorHandler($error_number, $error_message) {
$this->lastErrorNumber = $error_number;
$this->lastErrorMessage = $error_message;
}
/**
* Tests that errors are correctly handled when a __toString() fails.
*
* @covers ::__toString
*/
public function testToString() {
$translation = $this->getMock(TranslationInterface::class);
$string = 'May I have an exception please?';
$text = $this->getMockBuilder(TranslatableMarkup::class)
->setConstructorArgs([$string, [], [], $translation])
->setMethods(['_die'])
->getMock();
$text
->expects($this->once())
->method('_die')
->willReturn('');
$translation
->method('translateString')
->with($text)
->willReturnCallback(function () {
throw new \Exception('Yes you may.');
});
// We set a custom error handler because of https://github.com/sebastianbergmann/phpunit/issues/487
set_error_handler([$this, 'errorHandler']);
// We want this to trigger an error.
(string) $text;
restore_error_handler();
$this->assertEquals(E_USER_ERROR, $this->lastErrorNumber);
$this->assertRegExp('/Exception thrown while calling __toString on a .*Mock_TranslatableMarkup_.* object in .*TranslatableMarkupTest.php on line [0-9]+: Yes you may./', $this->lastErrorMessage);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage $string ("foo") must be a string.
*
* @covers ::__construct
*/
public function testIsStringAssertion() {
$translation = $this->getStringTranslationStub();
new TranslatableMarkup(new TranslatableMarkup('foo', [], [], $translation));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage $string ("foo") must be a string.
*
* @covers ::__construct
*/
public function testIsStringAssertionWithFormattableMarkup() {
$translation = $this->getStringTranslationStub();
$formattable_string = new FormattableMarkup('@bar', ['@bar' => 'foo']);
new TranslatableMarkup($formattable_string);
}
}

View file

@ -5,9 +5,10 @@
* Contains \Drupal\Tests\Core\StringTranslation\TranslationManagerTest.
*/
namespace Drupal\Tests\Core\StringTranslation {
namespace Drupal\Tests\Core\StringTranslation;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\Tests\UnitTestCase;
@ -24,6 +25,9 @@ class TranslationManagerTest extends UnitTestCase {
*/
protected $translationManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->translationManager = new TestTranslationManager();
}
@ -34,19 +38,18 @@ class TranslationManagerTest extends UnitTestCase {
*/
public function providerTestFormatPlural() {
return array(
array(1, 'Singular', '@count plural', array(), array(), 'Singular', TRUE),
array(2, 'Singular', '@count plural', array(), array(), '2 plural', TRUE),
[1, 'Singular', '@count plural', array(), array(), 'Singular'],
[2, 'Singular', '@count plural', array(), array(), '2 plural'],
// @todo support locale_get_plural
array(2, 'Singular', '@count @arg', array('@arg' => '<script>'), array(), '2 &lt;script&gt;', TRUE),
array(2, 'Singular', '@count %arg', array('%arg' => '<script>'), array(), '2 <em class="placeholder">&lt;script&gt;</em>', TRUE),
array(2, 'Singular', '@count !arg', array('!arg' => '<script>'), array(), '2 <script>', FALSE),
[2, 'Singular', '@count @arg', array('@arg' => '<script>'), array(), '2 &lt;script&gt;'],
[2, 'Singular', '@count %arg', array('%arg' => '<script>'), array(), '2 <em class="placeholder">&lt;script&gt;</em>'],
);
}
/**
* @dataProvider providerTestFormatPlural
*/
public function testFormatPlural($count, $singular, $plural, array $args = array(), array $options = array(), $expected, $safe) {
public function testFormatPlural($count, $singular, $plural, array $args = array(), array $options = array(), $expected) {
$translator = $this->getMock('\Drupal\Core\StringTranslation\Translator\TranslatorInterface');
$translator->expects($this->once())
->method('getStringTranslation')
@ -56,9 +59,40 @@ class TranslationManagerTest extends UnitTestCase {
$this->translationManager->addTranslator($translator);
$result = $this->translationManager->formatPlural($count, $singular, $plural, $args, $options);
$this->assertEquals($expected, $result);
$this->assertEquals(SafeMarkup::isSafe($result), $safe);
$this->assertTrue(SafeMarkup::isSafe($result));
}
/**
* Tests translation using placeholders.
*
* @param string $string
* A string containing the English string to translate.
* @param array $args
* An associative array of replacements to make after translation.
* @param string $expected_string
* The expected translated string value.
*
* @dataProvider providerTestTranslatePlaceholder
*/
public function testTranslatePlaceholder($string, array $args = array(), $expected_string) {
$actual = $this->translationManager->translate($string, $args);
$this->assertInstanceOf(MarkupInterface::class, $actual);
$this->assertEquals($expected_string, (string) $actual);
}
/**
* Provides test data for translate().
*
* @return array
*/
public function providerTestTranslatePlaceholder() {
return [
['foo @bar', ['@bar' => 'bar'], 'foo bar'],
['bar %baz', ['%baz' => 'baz'], 'bar <em class="placeholder">baz</em>'],
['bar @bar %baz', ['@bar' => 'bar', '%baz' => 'baz'], 'bar bar <em class="placeholder">baz</em>'],
['bar %baz @bar', ['%baz' => 'baz', '@bar' => 'bar'], 'bar <em class="placeholder">baz</em> bar'],
];
}
}
class TestTranslationManager extends TranslationManager {
@ -67,11 +101,3 @@ class TestTranslationManager extends TranslationManager {
}
}
}
namespace {
if (!defined('LOCALE_PLURAL_DELIMITER')) {
define('LOCALE_PLURAL_DELIMITER', "\03");
}
}

View file

@ -7,10 +7,13 @@
namespace Drupal\Tests\Core\Template;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Markup;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\AttributeArray;
use Drupal\Core\Template\AttributeString;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Render\MarkupInterface;
/**
* @coversDefaultClass \Drupal\Core\Template\Attribute
@ -30,6 +33,18 @@ class AttributeTest extends UnitTestCase {
$attribute = new Attribute(['selected' => TRUE, 'checked' => FALSE]);
$this->assertTrue($attribute['selected']->value());
$this->assertFalse($attribute['checked']->value());
// Test that non-array values with name "class" are cast to array.
$attribute = new Attribute(array('class' => 'example-class'));
$this->assertTrue(isset($attribute['class']));
$this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
// Test that safe string objects work correctly.
$safe_string = $this->prophesize(MarkupInterface::class);
$safe_string->__toString()->willReturn('example-class');
$attribute = new Attribute(array('class' => $safe_string->reveal()));
$this->assertTrue(isset($attribute['class']));
$this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']);
}
/**
@ -340,6 +355,27 @@ class AttributeTest extends UnitTestCase {
$this->assertTrue(strpos($html, 'enabled') !== FALSE);
}
/**
* @covers ::createAttributeValue
* @dataProvider providerTestAttributeValues
*/
public function testAttributeValues(array $attributes, $expected) {
$this->assertEquals($expected, (new Attribute($attributes))->__toString());
}
public function providerTestAttributeValues() {
$data = [];
$string = '"> <script>alert(123)</script>"';
$data['safe-object-xss1'] = [['title' => Markup::create($string)], ' title="&quot;&gt; alert(123)&quot;"'];
$data['non-safe-object-xss1'] = [['title' => $string], ' title="' . Html::escape($string) . '"'];
$string = '&quot;><script>alert(123)</script>';
$data['safe-object-xss2'] = [['title' => Markup::create($string)], ' title="&quot;&gt;alert(123)"'];
$data['non-safe-object-xss2'] = [['title' => $string], ' title="' . Html::escape($string) . '"'];
return $data;
}
/**
* Checks that the given CSS class is present in the given HTML snippet.
*

View file

@ -7,8 +7,10 @@
namespace Drupal\Tests\Core\Template;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\RenderableInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Template\Loader\StringLoader;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\Core\Template\TwigExtension;
use Drupal\Tests\UnitTestCase;
@ -32,7 +34,7 @@ class TwigExtensionTest extends UnitTestCase {
$twig = new \Twig_Environment(NULL, array(
'debug' => TRUE,
'cache' => FALSE,
'autoescape' => TRUE,
'autoescape' => 'html',
'optimizations' => 0
));
$twig->addExtension((new TwigExtension($renderer))->setUrlGenerator($this->getMock('Drupal\Core\Routing\UrlGeneratorInterface')));
@ -103,7 +105,55 @@ class TwigExtensionTest extends UnitTestCase {
}
/**
* Tests the escaping of objects implementing SafeStringInterface.
* Tests the format_date filter.
*/
public function testFormatDate() {
$date_formatter = $this->getMockBuilder('\Drupal\Core\Datetime\DateFormatter')
->disableOriginalConstructor()
->getMock();
$date_formatter->expects($this->exactly(2))
->method('format')
->willReturn('1978-11-19');
$renderer = $this->getMock('\Drupal\Core\Render\RendererInterface');
$extension = new TwigExtension($renderer);
$extension->setDateFormatter($date_formatter);
$loader = new StringLoader();
$twig = new \Twig_Environment($loader);
$twig->addExtension($extension);
$result = $twig->render('{{ time|format_date("html_date") }}');
$this->assertEquals($date_formatter->format('html_date'), $result);
}
/**
* Tests the active_theme_path function.
*/
public function testActiveThemePath() {
$renderer = $this->getMock('\Drupal\Core\Render\RendererInterface');
$extension = new TwigExtension($renderer);
$theme_manager = $this->getMock('\Drupal\Core\Theme\ThemeManagerInterface');
$active_theme = $this->getMockBuilder('\Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$active_theme
->expects($this->once())
->method('getPath')
->willReturn('foo/bar');
$theme_manager
->expects($this->once())
->method('getActiveTheme')
->willReturn($active_theme);
$extension->setThemeManager($theme_manager);
$loader = new \Twig_Loader_String();
$twig = new \Twig_Environment($loader);
$twig->addExtension($extension);
$result = $twig->render('{{ active_theme_path() }}');
$this->assertEquals('foo/bar', $result);
}
/**
* Tests the escaping of objects implementing MarkupInterface.
*
* @covers ::escapeFilter
*/
@ -112,17 +162,17 @@ class TwigExtensionTest extends UnitTestCase {
$twig = new \Twig_Environment(NULL, array(
'debug' => TRUE,
'cache' => FALSE,
'autoescape' => TRUE,
'autoescape' => 'html',
'optimizations' => 0
));
$twig_extension = new TwigExtension($renderer);
// By default, TwigExtension will attempt to cast objects to strings.
// Ensure objects that implement SafeStringInterface are unchanged.
$safe_string = $this->getMock('\Drupal\Component\Utility\SafeStringInterface');
// Ensure objects that implement MarkupInterface are unchanged.
$safe_string = $this->getMock('\Drupal\Component\Render\MarkupInterface');
$this->assertSame($safe_string, $twig_extension->escapeFilter($twig, $safe_string, 'html', 'UTF-8', TRUE));
// Ensure objects that do not implement SafeStringInterface are escaped.
// Ensure objects that do not implement MarkupInterface are escaped.
$string_object = new TwigExtensionTestString("<script>alert('here');</script>");
$this->assertSame('&lt;script&gt;alert(&#039;here&#039;);&lt;/script&gt;', $twig_extension->escapeFilter($twig, $string_object, 'html', 'UTF-8', TRUE));
}
@ -138,20 +188,44 @@ class TwigExtensionTest extends UnitTestCase {
$twig_extension = new TwigExtension($renderer);
$twig_environment = $this->prophesize(TwigEnvironment::class)->reveal();
// Simulate t().
$string = '<em>will be markup</em>';
SafeMarkup::setMultiple([$string => ['html' => TRUE]]);
$markup = $this->prophesize(TranslatableMarkup::class);
$markup->__toString()->willReturn('<em>will be markup</em>');
$markup = $markup->reveal();
$items = [
'<em>will be escaped</em>',
$string,
$markup,
['#markup' => '<strong>will be rendered</strong>']
];
$result = $twig_extension->safeJoin($twig_environment, $items, '<br/>');
$this->assertEquals('&lt;em&gt;will be escaped&lt;/em&gt;<br/><em>will be markup</em><br/><strong>will be rendered</strong>', $result);
}
/**
* @dataProvider providerTestRenderVar
*/
public function testRenderVar($result, $input) {
$renderer = $this->prophesize(RendererInterface::class);
$renderer->render($result += ['#printed' => FALSE])->willReturn('Rendered output');
$renderer = $renderer->reveal();
$twig_extension = new TwigExtension($renderer);
$this->assertEquals('Rendered output', $twig_extension->renderVar($input));
}
public function providerTestRenderVar() {
$data = [];
$renderable = $this->prophesize(RenderableInterface::class);
$render_array = ['#type' => 'test', '#var' => 'giraffe'];
$renderable->toRenderable()->willReturn($render_array);
$data['renderable'] = [$render_array, $renderable->reveal()];
return $data;
}
}
class TwigExtensionTestString {

View file

@ -93,7 +93,8 @@ class RegistryTest extends UnitTestCase {
'engine' => 'twig',
'owner' => 'twig',
'stylesheets_remove' => [],
'stylesheets_override' => [],
'libraries_override' => [],
'libraries_extend' => [],
'libraries' => [],
'extension' => '.twig',
'base_themes' => [],

View file

@ -16,10 +16,8 @@ use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
use Drupal\Core\TypedData\Validation\RecursiveValidator;
use Drupal\Core\Validation\ConstraintManager;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\DefaultTranslator;
/**
* @coversDefaultClass \Drupal\Core\TypedData\Validation\RecursiveContextualValidator
@ -82,7 +80,12 @@ class RecursiveContextualValidatorTest extends UnitTestCase {
$container->set('typed_data_manager', $this->typedDataManager);
\Drupal::setContainer($container);
$translator = new IdentityTranslator();
$translator = $this->getMock('Drupal\Core\Validation\TranslatorInterface');
$translator->expects($this->any())
->method('trans')
->willReturnCallback(function($id) {
return $id;
});
$this->contextFactory = new ExecutionContextFactory($translator);
$this->validatorFactory = new ConstraintValidatorFactory();
$this->recursiveValidator = new RecursiveValidator($this->contextFactory, $this->validatorFactory, $this->typedDataManager);

View file

@ -0,0 +1,302 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Update\UpdateRegistryTest.
*/
namespace Drupal\Tests\Core\Update;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Update\UpdateRegistry;
use Drupal\Tests\UnitTestCase;
use org\bovigo\vfs\vfsStream;
/**
* @coversDefaultClass \Drupal\Core\Update\UpdateRegistry
* @group Update
*
* Note we load code, so isolate the tests.
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class UpdateRegistryTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$settings = [];
$settings['extension_discovery_scan_tests'] = TRUE;
new Settings($settings);
}
/**
* Sets up some modules with some update functions.
*/
protected function setupBasicModules() {
$info_a = <<<'EOS'
type: module
name: Module A
core: 8.x
EOS;
$info_b = <<<'EOS'
type: module
name: Module B
core: 8.x
EOS;
$module_a = <<<'EOS'
<?php
/**
* Module A update A.
*/
function module_a_post_update_a() {
}
/**
* Module A update B.
*/
function module_a_post_update_b() {
}
EOS;
$module_b = <<<'EOS'
<?php
/**
* Module B update A.
*/
function module_b_post_update_a() {
}
EOS;
vfsStream::setup('drupal');
vfsStream::create([
'sites' => [
'default' => [
'modules' => [
'module_a' => [
'module_a.post_update.php' => $module_a,
'module_a.info.yml' => $info_a
],
'module_b' => [
'module_b.post_update.php' => $module_b,
'module_b.info.yml' => $info_b
],
]
]
],
]);
}
/**
* @covers ::getPendingUpdateFunctions
*/
public function testGetPendingUpdateFunctionsNoExistingUpdates() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn([]);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$this->assertEquals([
'module_a_post_update_a',
'module_a_post_update_b',
'module_b_post_update_a'
], $update_registry->getPendingUpdateFunctions());
}
/**
* @covers ::getPendingUpdateFunctions
*/
public function testGetPendingUpdateFunctionsWithLoadedModulesButNotEnabled() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn([]);
$key_value = $key_value->reveal();
// Preload modules to ensure that ::getAvailableUpdateFunctions filters out
// not enabled modules.
include_once 'vfs://drupal/sites/default/modules/module_a/module_a.post_update.php';
include_once 'vfs://drupal/sites/default/modules/module_b/module_b.post_update.php';
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
], $key_value, FALSE);
$this->assertEquals([
'module_a_post_update_a',
'module_a_post_update_b',
], $update_registry->getPendingUpdateFunctions());
}
/**
* @covers ::getPendingUpdateFunctions
*/
public function testGetPendingUpdateFunctionsExistingUpdates() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn(['module_a_post_update_a']);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$this->assertEquals(array_values([
'module_a_post_update_b',
'module_b_post_update_a'
]), array_values($update_registry->getPendingUpdateFunctions()));
}
/**
* @covers ::getPendingUpdateInformation
*/
public function testGetPendingUpdateInformation() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn([]);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$expected = [];
$expected['module_a']['pending']['a'] = 'Module A update A.';
$expected['module_a']['pending']['b'] = 'Module A update B.';
$expected['module_a']['start'] = 'a';
$expected['module_b']['pending']['a'] = 'Module B update A.';
$expected['module_b']['start'] = 'a';
$this->assertEquals($expected, $update_registry->getPendingUpdateInformation());
}
/**
* @covers ::getPendingUpdateInformation
*/
public function testGetPendingUpdateInformationWithExistingUpdates() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn(['module_a_post_update_a']);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$expected = [];
$expected['module_a']['pending']['b'] = 'Module A update B.';
$expected['module_a']['start'] = 'b';
$expected['module_b']['pending']['a'] = 'Module B update A.';
$expected['module_b']['start'] = 'a';
$this->assertEquals($expected, $update_registry->getPendingUpdateInformation());
}
/**
* @covers ::getModuleUpdateFunctions
*/
public function testGetModuleUpdateFunctions() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class)->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$this->assertEquals(['module_a_post_update_a', 'module_a_post_update_b'], array_values($update_registry->getModuleUpdateFunctions('module_a')));
$this->assertEquals(['module_b_post_update_a'], array_values($update_registry->getModuleUpdateFunctions('module_b')));
}
/**
* @covers ::registerInvokedUpdates
*/
public function testRegisterInvokedUpdatesWithoutExistingUpdates() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn([]);
$key_value->set('existing_updates', ['module_a_post_update_a'])->willReturn(NULL);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$update_registry->registerInvokedUpdates(['module_a_post_update_a']);
}
/**
* @covers ::registerInvokedUpdates
*/
public function testRegisterInvokedUpdatesWithMultiple() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn([]);
$key_value->set('existing_updates', ['module_a_post_update_a', 'module_a_post_update_b'])->willReturn(NULL);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$update_registry->registerInvokedUpdates(['module_a_post_update_a', 'module_a_post_update_b']);
}
/**
* @covers ::registerInvokedUpdates
*/
public function testRegisterInvokedUpdatesWithExistingUpdates() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn(['module_a_post_update_b']);
$key_value->set('existing_updates', ['module_a_post_update_b', 'module_a_post_update_a'])->willReturn(NULL);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$update_registry->registerInvokedUpdates(['module_a_post_update_a']);
}
/**
* @covers ::filterOutInvokedUpdatesByModule
*/
public function testFilterOutInvokedUpdatesByModule() {
$this->setupBasicModules();
$key_value = $this->prophesize(KeyValueStoreInterface::class);
$key_value->get('existing_updates', [])->willReturn(['module_a_post_update_b', 'module_a_post_update_a', 'module_b_post_update_a']);
$key_value->set('existing_updates', ['module_b_post_update_a'])->willReturn(NULL);
$key_value = $key_value->reveal();
$update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [
'module_a',
'module_b'
], $key_value, FALSE);
$update_registry->filterOutInvokedUpdatesByModule('module_a');
}
}

View file

@ -7,10 +7,11 @@
namespace Drupal\Tests\Core\Utility {
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\GeneratedUrl;
use Drupal\Core\Language\Language;
use Drupal\Core\Link;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\Core\Utility\LinkGenerator;
use Drupal\Tests\UnitTestCase;
@ -111,8 +112,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with($route_name, $parameters, array('absolute' => $absolute) + $this->defaultOptions)
->will($this->returnValue($expected_url));
->willReturn((new GeneratedUrl())->setGeneratedUrl($expected_url));
$this->moduleHandler->expects($this->once())
->method('alter');
@ -133,7 +133,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_1', array(), array('fragment' => 'the-fragment') + $this->defaultOptions)
->will($this->returnValue('/test-route-1#the-fragment'));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1#the-fragment'));
$this->moduleHandler->expects($this->once())
->method('alter')
@ -197,7 +197,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlAssembler->expects($this->once())
->method('assemble')
->with('base:example', array('query' => array('foo' => '"bar"', 'zoo' => 'baz')) + $this->defaultOptions)
->will($this->returnValue('/example?foo=%22bar%22&zoo=baz'));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/example?foo=%22bar%22&zoo=baz'));
$path_validator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
$container_builder = new ContainerBuilder();
@ -228,9 +228,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_1', array(), $this->defaultOptions)
->will($this->returnValue(
'/test-route-1'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1'));
// Test that HTML attributes are added to the anchor.
$url = new Url('test_route_1', array(), array(
@ -255,9 +253,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_1', array(), array('query' => array('test' => 'value')) + $this->defaultOptions)
->will($this->returnValue(
'/test-route-1?test=value'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1?test=value'));
$url = new Url('test_route_1', array(), array(
'query' => array('test' => 'value'),
@ -280,9 +276,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_1', array('test' => 'value'), $this->defaultOptions)
->will($this->returnValue(
'/test-route-1?test=value'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1?test=value'));
$url = new Url('test_route_1', array('test' => 'value'), array());
$url->setUrlGenerator($this->urlGenerator);
@ -303,10 +297,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_1', array(), array('key' => 'value') + $this->defaultOptions)
->will($this->returnValue(
'/test-route-1?test=value'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-1?test=value'));
$url = new Url('test_route_1', array(), array(
'key' => 'value',
));
@ -328,9 +319,7 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->with('test_route_4', array(), $this->defaultOptions)
->will($this->returnValue(
'/test-route-4'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-4'));
// Test that HTML link text is escaped by default.
$url = new Url('test_route_4');
@ -348,15 +337,11 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->at(0))
->method('generateFromRoute')
->with('test_route_5', array(), $this->defaultOptions)
->will($this->returnValue(
'/test-route-5'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-5'));
$this->urlGenerator->expects($this->at(1))
->method('generateFromRoute')
->with('test_route_5', array(), $this->defaultOptions)
->will($this->returnValue(
'/test-route-5'
));
->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-5'));
// Test that HTML tags are stripped from the 'title' attribute.
$url = new Url('test_route_5', array(), array(
@ -372,11 +357,11 @@ class LinkGeneratorTest extends UnitTestCase {
), $result);
// Test that safe HTML is output inside the anchor tag unescaped. The
// SafeString::create() call is an intentional unit test for the interaction
// between SafeStringInterface and the LinkGenerator.
// Markup::create() call is an intentional unit test for the interaction
// between MarkupInterface and the LinkGenerator.
$url = new Url('test_route_5', array());
$url->setUrlGenerator($this->urlGenerator);
$result = $this->linkGenerator->generate(SafeString::create('<em>HTML output</em>'), $url);
$result = $this->linkGenerator->generate(Markup::create('<em>HTML output</em>'), $url);
$this->assertLink(array(
'attributes' => array('href' => '/test-route-5'),
'child' => array(
@ -394,11 +379,18 @@ class LinkGeneratorTest extends UnitTestCase {
public function testGenerateActive() {
$this->urlGenerator->expects($this->exactly(5))
->method('generateFromRoute')
->will($this->returnValueMap(array(
array('test_route_1', array(), FALSE, '/test-route-1'),
array('test_route_3', array(), FALSE, '/test-route-3'),
array('test_route_4', array('object' => '1'), FALSE, '/test-route-4/1'),
)));
->willReturnCallback(function($name, $parameters = array(), $options = array(), $collect_bubbleable_metadata = FALSE) {
switch ($name) {
case 'test_route_1':
return (new GeneratedUrl())->setGeneratedUrl('/test-route-1');
case 'test_route_3':
return (new GeneratedUrl())->setGeneratedUrl('/test-route-3');
case 'test_route_4':
if ($parameters['object'] == '1') {
return (new GeneratedUrl())->setGeneratedUrl('/test-route-4/1');
}
}
});
$this->urlGenerator->expects($this->exactly(4))
->method('getPathFromRoute')
@ -479,7 +471,6 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator->expects($this->any())
->method('generateFromRoute')
->will($this->returnValueMap([
['test_route_1', [], $options, FALSE, '/test-route-1'],
['test_route_1', [], $options, TRUE, (new GeneratedUrl())->setGeneratedUrl('/test-route-1')],
]));
@ -488,16 +479,16 @@ class LinkGeneratorTest extends UnitTestCase {
$expected_link_markup = '<a href="/test-route-1">Test</a>';
// Test ::generate().
$this->assertSame($expected_link_markup, $this->linkGenerator->generate('Test', $url));
$generated_link = $this->linkGenerator->generate('Test', $url, TRUE);
$this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
$this->assertSame($expected_link_markup, (string) $this->linkGenerator->generate('Test', $url));
$generated_link = $this->linkGenerator->generate('Test', $url);
$this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
$this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
// Test ::generateFromLink().
$link = new Link('Test', $url);
$this->assertSame($expected_link_markup, $this->linkGenerator->generateFromLink($link));
$generated_link = $this->linkGenerator->generateFromLink($link, TRUE);
$this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
$this->assertSame($expected_link_markup, (string) $this->linkGenerator->generateFromLink($link));
$generated_link = $this->linkGenerator->generateFromLink($link);
$this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
$this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
}
@ -508,12 +499,12 @@ class LinkGeneratorTest extends UnitTestCase {
* An associative array of link properties, with the following keys:
* - attributes: optional array of HTML attributes that should be present.
* - content: optional link content.
* @param string $html
* @param \Drupal\Component\Render\MarkupInterface $html
* The HTML to check.
* @param int $count
* How many times the link should be present in the HTML. Defaults to 1.
*/
public static function assertLink(array $properties, $html, $count = 1) {
public static function assertLink(array $properties, MarkupInterface $html, $count = 1) {
// Provide default values.
$properties += array('attributes' => array());

View file

@ -7,10 +7,12 @@
namespace Drupal\Tests\Core\Utility;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Markup;
use Drupal\Core\Utility\Token;
use Drupal\Tests\UnitTestCase;
@ -262,4 +264,39 @@ class TokenTest extends UnitTestCase {
$this->token->resetInfo();
}
/**
* @covers ::replace
* @dataProvider providerTestReplaceEscaping
*/
public function testReplaceEscaping($string, array $tokens, $expected) {
$this->moduleHandler->expects($this->any())
->method('invokeAll')
->willReturnCallback(function ($type, $args) {
return $args[2]['tokens'];
});
$result = $this->token->replace($string, ['tokens' => $tokens]);
$this->assertInternalType('string', $result);
$this->assertEquals($expected, $result);
}
public function providerTestReplaceEscaping() {
$data = [];
// No tokens. The first argument to Token::replace() should not be escaped.
$data['no-tokens'] = ['muh', [], 'muh'];
$data['html-in-string'] = ['<h1>Giraffe</h1>', [], '<h1>Giraffe</h1>'];
$data['html-in-string-quote'] = ['<h1>Giraffe"</h1>', [], '<h1>Giraffe"</h1>'];
$data['simple-placeholder-with-plain-text'] = ['<h1>[token:meh]</h1>', ['[token:meh]' => 'Giraffe"'], '<h1>' . Html::escape('Giraffe"') . '</h1>'];
$data['simple-placeholder-with-safe-html'] = [
'<h1>[token:meh]</h1>',
['[token:meh]' => Markup::create('<em>Emphasized</em>')],
'<h1><em>Emphasized</em></h1>',
];
return $data;
}
}

View file

@ -16,6 +16,7 @@ use Drupal\Core\TypedData\Plugin\DataType\Uri;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
/**
@ -63,6 +64,7 @@ class PrimitiveTypeConstraintValidatorTest extends UnitTestCase {
$data[] = [new IntegerData(DataDefinition::create('integer')), 1.5, FALSE];
$data[] = [new IntegerData(DataDefinition::create('integer')), 'test', FALSE];
$data[] = [new StringData(DataDefinition::create('string')), 'test', TRUE];
$data[] = [new StringData(DataDefinition::create('string')), new TranslatableMarkup('test'), TRUE];
// It is odd that 1 is a valid string.
// $data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), 1, FALSE];
$data[] = [new StringData(DataDefinition::create('string')), [], FALSE];

View file

@ -1,35 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Listeners\SafeMarkupSideEffects.
*
* Listener for PHPUnit tests, to enforce that data providers don't add to the
* SafeMarkup static safe string list.
*/
namespace Drupal\Tests\Listeners;
use Drupal\Component\Utility\SafeMarkup;
/**
* Listens for PHPUnit tests and fails those with SafeMarkup side effects.
*/
class SafeMarkupSideEffects extends \PHPUnit_Framework_BaseTestListener {
/**
* {@inheritdoc}
*/
public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) {
// Use a static so we only do this test once after all the data providers
// have run.
static $tested = FALSE;
if ($suite->getName() !== '' && !$tested) {
$tested = TRUE;
if (!empty(SafeMarkup::getAll())) {
throw new \RuntimeException('SafeMarkup string list polluted by data providers');
}
}
}
}

View file

@ -12,6 +12,9 @@ use Drupal\Component\Utility\Random;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
/**
* Provides a base class and helpers for Drupal unit tests.
@ -48,12 +51,6 @@ abstract class UnitTestCase extends \PHPUnit_Framework_TestCase {
FileCacheFactory::setConfiguration(['default' => ['class' => '\Drupal\Component\FileCache\NullFileCache']]);
$this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
// Reset the static list of SafeStrings to prevent bleeding between tests.
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$reflected_property->setValue([]);
}
/**
@ -214,11 +211,19 @@ abstract class UnitTestCase extends \PHPUnit_Framework_TestCase {
$translation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
$translation->expects($this->any())
->method('translate')
->will($this->returnCallback('Drupal\Component\Utility\SafeMarkup::format'));
->willReturnCallback(function ($string, array $args = array(), array $options = array()) use ($translation) {
return new TranslatableMarkup($string, $args, $options, $translation);
});
$translation->expects($this->any())
->method('translateString')
->willReturnCallback(function (TranslatableMarkup $wrapper) {
return $wrapper->getUntranslatedString();
});
$translation->expects($this->any())
->method('formatPlural')
->willReturnCallback(function ($count, $singular, $plural, array $args = [], array $options = []) {
return $count === 1 ? SafeMarkup::format($singular, $args) : SafeMarkup::format($plural, $args + ['@count' => $count]);
->willReturnCallback(function ($count, $singular, $plural, array $args = [], array $options = []) use ($translation) {
$wrapper = new PluralTranslatableMarkup($count, $singular, $plural, $args, $options, $translation);
return $wrapper;
});
return $translation;
}