Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
35
2017/web/core/tests/Drupal/Tests/AssertHelperTrait.php
Normal file
35
2017/web/core/tests/Drupal/Tests/AssertHelperTrait.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods for assertions.
|
||||
*/
|
||||
trait AssertHelperTrait {
|
||||
|
||||
/**
|
||||
* Casts MarkupInterface objects into strings.
|
||||
*
|
||||
* @param string|array $value
|
||||
* The value to act on.
|
||||
*
|
||||
* @return mixed
|
||||
* The input value, with MarkupInterface objects casted to string.
|
||||
*/
|
||||
protected static function castSafeStrings($value) {
|
||||
if ($value instanceof MarkupInterface) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
array_walk_recursive($value, function (&$item) {
|
||||
if ($item instanceof MarkupInterface) {
|
||||
$item = (string) $item;
|
||||
}
|
||||
});
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
43
2017/web/core/tests/Drupal/Tests/AssertHelperTraitTest.php
Normal file
43
2017/web/core/tests/Drupal/Tests/AssertHelperTraitTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Drupal\Core\Render\Markup;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Tests\AssertHelperTrait
|
||||
* @group simpletest
|
||||
* @group Tests
|
||||
*/
|
||||
class AssertHelperTraitTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::castSafeStrings
|
||||
* @dataProvider providerCastSafeStrings
|
||||
*/
|
||||
public function testCastSafeStrings($expected, $value) {
|
||||
$class = new AssertHelperTestClass();
|
||||
$this->assertSame($expected, $class->testMethod($value));
|
||||
}
|
||||
|
||||
public function providerCastSafeStrings() {
|
||||
$safe_string = Markup::create('test safe string');
|
||||
return [
|
||||
['test simple string', 'test simple string'],
|
||||
[['test simple array', 'test simple array'], ['test simple array', 'test simple array']],
|
||||
['test safe string', $safe_string],
|
||||
[['test safe string', 'test safe string'], [$safe_string, $safe_string]],
|
||||
[['test safe string', 'mixed array', 'test safe string'], [$safe_string, 'mixed array', $safe_string]],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AssertHelperTestClass {
|
||||
use AssertHelperTrait;
|
||||
|
||||
public function testMethod($value) {
|
||||
return $this->castSafeStrings($value);
|
||||
}
|
||||
|
||||
}
|
||||
222
2017/web/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php
Normal file
222
2017/web/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Provides the debug functions for browser tests.
|
||||
*/
|
||||
trait BrowserHtmlDebugTrait {
|
||||
|
||||
/**
|
||||
* Class name for HTML output logging.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htmlOutputClassName;
|
||||
|
||||
/**
|
||||
* Directory name for HTML output logging.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htmlOutputDirectory;
|
||||
|
||||
/**
|
||||
* Counter storage for HTML output logging.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htmlOutputCounterStorage;
|
||||
|
||||
/**
|
||||
* Counter for HTML output logging.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $htmlOutputCounter = 1;
|
||||
|
||||
/**
|
||||
* HTML output output enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $htmlOutputEnabled = FALSE;
|
||||
|
||||
/**
|
||||
* The file name to write the list of URLs to.
|
||||
*
|
||||
* This file is read by the PHPUnit result printer.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see \Drupal\Tests\Listeners\HtmlOutputPrinter
|
||||
*/
|
||||
protected $htmlOutputFile;
|
||||
|
||||
/**
|
||||
* HTML output test ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $htmlOutputTestId;
|
||||
|
||||
/**
|
||||
* Formats HTTP headers as string for HTML output logging.
|
||||
*
|
||||
* @param array[] $headers
|
||||
* Headers that should be formatted.
|
||||
*
|
||||
* @return string
|
||||
* The formatted HTML string.
|
||||
*/
|
||||
protected function formatHtmlOutputHeaders(array $headers) {
|
||||
$flattened_headers = array_map(function ($header) {
|
||||
if (is_array($header)) {
|
||||
return implode(';', array_map('trim', $header));
|
||||
}
|
||||
else {
|
||||
return $header;
|
||||
}
|
||||
}, $headers);
|
||||
return '<hr />Headers: <pre>' . Html::escape(var_export($flattened_headers, TRUE)) . '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers in HTML output format.
|
||||
*
|
||||
* @return string
|
||||
* HTML output headers.
|
||||
*/
|
||||
protected function getHtmlOutputHeaders() {
|
||||
return $this->formatHtmlOutputHeaders($this->getSession()->getResponseHeaders());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a HTML output message in a text file.
|
||||
*
|
||||
* The link to the HTML output message will be printed by the results printer.
|
||||
*
|
||||
* @param string|null $message
|
||||
* (optional) The HTML output message to be stored. If not supplied the
|
||||
* current page content is used.
|
||||
*
|
||||
* @see \Drupal\Tests\Listeners\VerbosePrinter::printResult()
|
||||
*/
|
||||
protected function htmlOutput($message = NULL) {
|
||||
if (!$this->htmlOutputEnabled) {
|
||||
return;
|
||||
}
|
||||
$message = $message ?: $this->getSession()->getPage()->getContent();
|
||||
$message = '<hr />ID #' . $this->htmlOutputCounter . ' (<a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter - 1) . '-' . $this->htmlOutputTestId . '.html">Previous</a> | <a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter + 1) . '-' . $this->htmlOutputTestId . '.html">Next</a>)<hr />' . $message;
|
||||
$html_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.html';
|
||||
file_put_contents($this->htmlOutputDirectory . '/' . $html_output_filename, $message);
|
||||
file_put_contents($this->htmlOutputCounterStorage, $this->htmlOutputCounter++);
|
||||
// Do not use file_create_url() as the module_handler service might not be
|
||||
// available.
|
||||
$uri = $GLOBALS['base_url'] . '/sites/simpletest/browser_output/' . $html_output_filename;
|
||||
file_put_contents($this->htmlOutputFile, $uri . "\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the directory to store browser output.
|
||||
*
|
||||
* Creates the directory to store browser output in if a file to write
|
||||
* URLs to has been created by \Drupal\Tests\Listeners\HtmlOutputPrinter.
|
||||
*/
|
||||
protected function initBrowserOutputFile() {
|
||||
$browser_output_file = getenv('BROWSERTEST_OUTPUT_FILE');
|
||||
$this->htmlOutputEnabled = is_file($browser_output_file);
|
||||
if ($this->htmlOutputEnabled) {
|
||||
$this->htmlOutputFile = $browser_output_file;
|
||||
$this->htmlOutputClassName = str_replace("\\", "_", get_called_class());
|
||||
$this->htmlOutputDirectory = DRUPAL_ROOT . '/sites/simpletest/browser_output';
|
||||
// Do not use the file_system service so this method can be called before
|
||||
// it is available.
|
||||
if (!is_dir($this->htmlOutputDirectory)) {
|
||||
mkdir($this->htmlOutputDirectory, 0775, TRUE);
|
||||
}
|
||||
if (!file_exists($this->htmlOutputDirectory . '/.htaccess')) {
|
||||
file_put_contents($this->htmlOutputDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
|
||||
}
|
||||
$this->htmlOutputCounterStorage = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '.counter';
|
||||
$this->htmlOutputTestId = str_replace('sites/simpletest/', '', $this->siteDirectory);
|
||||
if (is_file($this->htmlOutputCounterStorage)) {
|
||||
$this->htmlOutputCounter = max(1, (int) file_get_contents($this->htmlOutputCounterStorage)) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Guzzle middleware handler to log every response received.
|
||||
*
|
||||
* @return callable
|
||||
* The callable handler that will do the logging.
|
||||
*/
|
||||
protected function getResponseLogHandler() {
|
||||
return function (callable $handler) {
|
||||
return function (RequestInterface $request, array $options) use ($handler) {
|
||||
return $handler($request, $options)
|
||||
->then(function (ResponseInterface $response) use ($request) {
|
||||
if ($this->htmlOutputEnabled) {
|
||||
|
||||
$caller = $this->getTestMethodCaller();
|
||||
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
|
||||
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
|
||||
|
||||
// On redirect responses (status code starting with '3') we need
|
||||
// to remove the meta tag that would do a browser refresh. We
|
||||
// don't want to redirect developers away when they look at the
|
||||
// debug output file in their browser.
|
||||
$body = $response->getBody();
|
||||
$status_code = (string) $response->getStatusCode();
|
||||
if ($status_code[0] === '3') {
|
||||
$body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
|
||||
}
|
||||
$html_output .= '<hr />' . $body;
|
||||
$html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
|
||||
|
||||
$this->htmlOutput($html_output);
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current calling line in the class under test.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with keys 'file', 'line' and 'function'.
|
||||
*/
|
||||
protected function getTestMethodCaller() {
|
||||
$backtrace = debug_backtrace();
|
||||
// Find the test class that has the test method.
|
||||
while ($caller = Error::getLastCaller($backtrace)) {
|
||||
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
|
||||
break;
|
||||
}
|
||||
// If the test method is implemented by a test class's parent then the
|
||||
// class name of $this will not be part of the backtrace.
|
||||
// In that case we process the backtrace until the caller is not a
|
||||
// subclass of $this and return the previous caller.
|
||||
if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
|
||||
// Return the last caller since that has to be the test class.
|
||||
$caller = $last_caller;
|
||||
break;
|
||||
}
|
||||
// Otherwise we have not reached our test class yet: save the last caller
|
||||
// and remove an element from to backtrace to process the next call.
|
||||
$last_caller = $caller;
|
||||
array_shift($backtrace);
|
||||
}
|
||||
|
||||
return $caller;
|
||||
}
|
||||
|
||||
}
|
||||
755
2017/web/core/tests/Drupal/Tests/BrowserTestBase.php
Normal file
755
2017/web/core/tests/Drupal/Tests/BrowserTestBase.php
Normal file
|
|
@ -0,0 +1,755 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Behat\Mink\Driver\GoutteDriver;
|
||||
use Behat\Mink\Element\Element;
|
||||
use Behat\Mink\Mink;
|
||||
use Behat\Mink\Selector\SelectorsHandler;
|
||||
use Behat\Mink\Session;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Test\FunctionalTestSetupTrait;
|
||||
use Drupal\Core\Test\TestSetupTrait;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\FunctionalTests\AssertLegacyTrait;
|
||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\CssSelector\CssSelectorConverter;
|
||||
|
||||
/**
|
||||
* Provides a test case for functional Drupal tests.
|
||||
*
|
||||
* Tests extending BrowserTestBase must exist in the
|
||||
* Drupal\Tests\yourmodule\Functional namespace and live in the
|
||||
* modules/yourmodule/tests/src/Functional directory.
|
||||
*
|
||||
* Tests extending this base class should only translate text when testing
|
||||
* translation functionality. For example, avoid wrapping test text with t()
|
||||
* or TranslatableMarkup().
|
||||
*
|
||||
* @ingroup testing
|
||||
*/
|
||||
abstract class BrowserTestBase extends TestCase {
|
||||
|
||||
use FunctionalTestSetupTrait;
|
||||
use UiHelperTrait {
|
||||
FunctionalTestSetupTrait::refreshVariables insteadof UiHelperTrait;
|
||||
}
|
||||
use TestSetupTrait;
|
||||
use BlockCreationTrait {
|
||||
placeBlock as drupalPlaceBlock;
|
||||
}
|
||||
use AssertLegacyTrait;
|
||||
use RandomGeneratorTrait;
|
||||
use NodeCreationTrait {
|
||||
getNodeByTitle as drupalGetNodeByTitle;
|
||||
createNode as drupalCreateNode;
|
||||
}
|
||||
use ContentTypeCreationTrait {
|
||||
createContentType as drupalCreateContentType;
|
||||
}
|
||||
use ConfigTestTrait;
|
||||
use TestRequirementsTrait;
|
||||
use UserCreationTrait {
|
||||
createRole as drupalCreateRole;
|
||||
createUser as drupalCreateUser;
|
||||
}
|
||||
use XdebugRequestTrait;
|
||||
use PhpunitCompatibilityTrait;
|
||||
|
||||
/**
|
||||
* The database prefix of this test run.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $databasePrefix;
|
||||
|
||||
/**
|
||||
* Time limit in seconds for the test.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeLimit = 500;
|
||||
|
||||
/**
|
||||
* The translation file directory for the test environment.
|
||||
*
|
||||
* This is set in BrowserTestBase::prepareEnvironment().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translationFilesDirectory;
|
||||
|
||||
/**
|
||||
* The config importer that can be used in a test.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* The test runner will merge the $modules lists from this class, the class
|
||||
* it extends, and so on up the class hierarchy. It is not necessary to
|
||||
* include modules in your list that a parent class has already declared.
|
||||
*
|
||||
* @var string[]
|
||||
*
|
||||
* @see \Drupal\Tests\BrowserTestBase::installDrupal()
|
||||
*/
|
||||
protected static $modules = [];
|
||||
|
||||
/**
|
||||
* The profile to install as a basis for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* An array of custom translations suitable for drupal_rewrite_settings().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customTranslations;
|
||||
|
||||
/*
|
||||
* Mink class for the default driver to use.
|
||||
*
|
||||
* Should be a fully-qualified class name that implements
|
||||
* Behat\Mink\Driver\DriverInterface.
|
||||
*
|
||||
* Value can be overridden using the environment variable MINK_DRIVER_CLASS.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minkDefaultDriverClass = GoutteDriver::class;
|
||||
|
||||
/*
|
||||
* Mink default driver params.
|
||||
*
|
||||
* If it's an array its contents are used as constructor params when default
|
||||
* Mink driver class is instantiated.
|
||||
*
|
||||
* Can be overridden using the environment variable MINK_DRIVER_ARGS. In this
|
||||
* case that variable should be a JSON array, for example:
|
||||
* '["firefox", null, "http://localhost:4444/wd/hub"]'.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $minkDefaultDriverArgs;
|
||||
|
||||
/**
|
||||
* Mink session manager.
|
||||
*
|
||||
* This will not be initialized if there was an error during the test setup.
|
||||
*
|
||||
* @var \Behat\Mink\Mink|null
|
||||
*/
|
||||
protected $mink;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Browser tests are run in separate processes to prevent collisions between
|
||||
* code that may be loaded by tests.
|
||||
*/
|
||||
protected $runTestInSeparateProcess = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $preserveGlobalState = FALSE;
|
||||
|
||||
/**
|
||||
* The base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl;
|
||||
|
||||
/**
|
||||
* The original array of shutdown function callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $originalShutdownCallbacks = [];
|
||||
|
||||
/**
|
||||
* The app root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* The original container.
|
||||
*
|
||||
* Move this to \Drupal\Core\Test\FunctionalTestSetupTrait once TestBase no
|
||||
* longer provides the same value.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $originalContainer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($name = NULL, array $data = [], $dataName = '') {
|
||||
parent::__construct($name, $data, $dataName);
|
||||
|
||||
$this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Mink sessions.
|
||||
*/
|
||||
protected function initMink() {
|
||||
$driver = $this->getDefaultDriverInstance();
|
||||
|
||||
if ($driver instanceof GoutteDriver) {
|
||||
// Turn off curl timeout. Having a timeout is not a problem in a normal
|
||||
// test running, but it is a problem when debugging. Also, disable SSL
|
||||
// peer verification so that testing under HTTPS always works.
|
||||
/** @var \GuzzleHttp\Client $client */
|
||||
$client = $this->container->get('http_client_factory')->fromOptions([
|
||||
'timeout' => NULL,
|
||||
'verify' => FALSE,
|
||||
]);
|
||||
|
||||
// Inject a Guzzle middleware to generate debug output for every request
|
||||
// performed in the test.
|
||||
$handler_stack = $client->getConfig('handler');
|
||||
$handler_stack->push($this->getResponseLogHandler());
|
||||
|
||||
$driver->getClient()->setClient($client);
|
||||
}
|
||||
|
||||
$selectors_handler = new SelectorsHandler([
|
||||
'hidden_field_selector' => new HiddenFieldSelector(),
|
||||
]);
|
||||
$session = new Session($driver, $selectors_handler);
|
||||
$this->mink = new Mink();
|
||||
$this->mink->registerSession('default', $session);
|
||||
$this->mink->setDefaultSessionName('default');
|
||||
$this->registerSessions();
|
||||
|
||||
$this->initFrontPage();
|
||||
|
||||
// Copies cookies from the current environment, for example, XDEBUG_SESSION
|
||||
// in order to support Xdebug.
|
||||
// @see BrowserTestBase::initFrontPage()
|
||||
$cookies = $this->extractCookiesFromRequest(\Drupal::request());
|
||||
foreach ($cookies as $cookie_name => $values) {
|
||||
foreach ($values as $value) {
|
||||
$session->setCookie($cookie_name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the front page when initializing Mink.
|
||||
*
|
||||
* According to the W3C WebDriver specification a cookie can only be set if
|
||||
* the cookie domain is equal to the domain of the active document. When the
|
||||
* browser starts up the active document is not our domain but 'about:blank'
|
||||
* or similar. To be able to set our User-Agent and Xdebug cookies at the
|
||||
* start of the test we now do a request to the front page so the active
|
||||
* document matches the domain.
|
||||
*
|
||||
* @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
|
||||
* @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975
|
||||
*/
|
||||
protected function initFrontPage() {
|
||||
$session = $this->getSession();
|
||||
$session->visit($this->baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of the default Mink driver.
|
||||
*
|
||||
* @return Behat\Mink\Driver\DriverInterface
|
||||
* Instance of default Mink driver.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* When provided default Mink driver class can't be instantiated.
|
||||
*/
|
||||
protected function getDefaultDriverInstance() {
|
||||
// Get default driver params from environment if available.
|
||||
if ($arg_json = $this->getMinkDriverArgs()) {
|
||||
$this->minkDefaultDriverArgs = json_decode($arg_json, TRUE);
|
||||
}
|
||||
|
||||
// Get and check default driver class from environment if available.
|
||||
if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) {
|
||||
if (class_exists($minkDriverClass)) {
|
||||
$this->minkDefaultDriverClass = $minkDriverClass;
|
||||
}
|
||||
else {
|
||||
throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class.");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->minkDefaultDriverArgs)) {
|
||||
// Use ReflectionClass to instantiate class with received params.
|
||||
$reflector = new \ReflectionClass($this->minkDefaultDriverClass);
|
||||
$driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
|
||||
}
|
||||
else {
|
||||
$driver = new $this->minkDefaultDriverClass();
|
||||
}
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Mink driver args from an environment variable, if it is set. Can
|
||||
* be overridden in a derived class so it is possible to use a different
|
||||
* value for a subset of tests, e.g. the JavaScript tests.
|
||||
*
|
||||
* @return string|false
|
||||
* The JSON-encoded argument string. False if it is not set.
|
||||
*/
|
||||
protected function getMinkDriverArgs() {
|
||||
return getenv('MINK_DRIVER_ARGS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Guzzle middleware handler to log every response received.
|
||||
*
|
||||
* @return callable
|
||||
* The callable handler that will do the logging.
|
||||
*/
|
||||
protected function getResponseLogHandler() {
|
||||
return function (callable $handler) {
|
||||
return function (RequestInterface $request, array $options) use ($handler) {
|
||||
return $handler($request, $options)
|
||||
->then(function (ResponseInterface $response) use ($request) {
|
||||
if ($this->htmlOutputEnabled) {
|
||||
|
||||
$caller = $this->getTestMethodCaller();
|
||||
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
|
||||
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
|
||||
|
||||
// On redirect responses (status code starting with '3') we need
|
||||
// to remove the meta tag that would do a browser refresh. We
|
||||
// don't want to redirect developers away when they look at the
|
||||
// debug output file in their browser.
|
||||
$body = $response->getBody();
|
||||
$status_code = (string) $response->getStatusCode();
|
||||
if ($status_code[0] === '3') {
|
||||
$body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
|
||||
}
|
||||
$html_output .= '<hr />' . $body;
|
||||
$html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
|
||||
|
||||
$this->htmlOutput($html_output);
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional Mink sessions.
|
||||
*
|
||||
* Tests wishing to use a different driver or change the default driver should
|
||||
* override this method.
|
||||
*
|
||||
* @code
|
||||
* // Register a new session that uses the MinkPonyDriver.
|
||||
* $pony = new MinkPonyDriver();
|
||||
* $session = new Session($pony);
|
||||
* $this->mink->registerSession('pony', $session);
|
||||
* @endcode
|
||||
*/
|
||||
protected function registerSessions() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
// Installing Drupal creates 1000s of objects. Garbage collection of these
|
||||
// objects is expensive. This appears to be causing random segmentation
|
||||
// faults in PHP 5.x due to https://bugs.php.net/bug.php?id=72286. Once
|
||||
// Drupal is installed is rebuilt, garbage collection is re-enabled.
|
||||
$disable_gc = version_compare(PHP_VERSION, '7', '<') && gc_enabled();
|
||||
if ($disable_gc) {
|
||||
gc_collect_cycles();
|
||||
gc_disable();
|
||||
}
|
||||
parent::setUp();
|
||||
|
||||
$this->setupBaseUrl();
|
||||
|
||||
// Install Drupal test site.
|
||||
$this->prepareEnvironment();
|
||||
$this->installDrupal();
|
||||
|
||||
// Setup Mink.
|
||||
$this->initMink();
|
||||
|
||||
// Set up the browser test output file.
|
||||
$this->initBrowserOutputFile();
|
||||
// If garbage collection was disabled prior to rebuilding container,
|
||||
// re-enable it.
|
||||
if ($disable_gc) {
|
||||
gc_enable();
|
||||
}
|
||||
|
||||
// Ensure that the test is not marked as risky because of no assertions. In
|
||||
// PHPUnit 6 tests that only make assertions using $this->assertSession()
|
||||
// can be marked as risky.
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures test files are deletable within file_unmanaged_delete_recursive().
|
||||
*
|
||||
* Some tests chmod generated files to be read only. During
|
||||
* BrowserTestBase::cleanupEnvironment() and other cleanup operations,
|
||||
* these files need to get deleted too.
|
||||
*
|
||||
* @param string $path
|
||||
* The file path.
|
||||
*/
|
||||
public static function filePreDeleteCallback($path) {
|
||||
// When the webserver runs with the same system user as phpunit, we can
|
||||
// make read-only files writable again. If not, chmod will fail while the
|
||||
// file deletion still works if file permissions have been configured
|
||||
// correctly. Thus, we ignore any problems while running chmod.
|
||||
@chmod($path, 0700);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the Simpletest environment.
|
||||
*/
|
||||
protected function cleanupEnvironment() {
|
||||
// Remove all prefixed tables.
|
||||
$original_connection_info = Database::getConnectionInfo('simpletest_original_default');
|
||||
$original_prefix = $original_connection_info['default']['prefix']['default'];
|
||||
$test_connection_info = Database::getConnectionInfo('default');
|
||||
$test_prefix = $test_connection_info['default']['prefix']['default'];
|
||||
if ($original_prefix != $test_prefix) {
|
||||
$tables = Database::getConnection()->schema()->findTables('%');
|
||||
foreach ($tables as $table) {
|
||||
if (Database::getConnection()->schema()->dropTable($table)) {
|
||||
unset($tables[$table]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete test site directory.
|
||||
file_unmanaged_delete_recursive($this->siteDirectory, [$this, 'filePreDeleteCallback']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown() {
|
||||
parent::tearDown();
|
||||
|
||||
// Destroy the testing kernel.
|
||||
if (isset($this->kernel)) {
|
||||
$this->cleanupEnvironment();
|
||||
$this->kernel->shutdown();
|
||||
}
|
||||
|
||||
// Ensure that internal logged in variable is reset.
|
||||
$this->loggedInUser = FALSE;
|
||||
|
||||
if ($this->mink) {
|
||||
$this->mink->stopSessions();
|
||||
}
|
||||
|
||||
// Restore original shutdown callbacks.
|
||||
if (function_exists('drupal_register_shutdown_function')) {
|
||||
$callbacks = &drupal_register_shutdown_function();
|
||||
$callbacks = $this->originalShutdownCallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Mink session.
|
||||
*
|
||||
* @param string $name
|
||||
* (optional) Name of the session. Defaults to the active session.
|
||||
*
|
||||
* @return \Behat\Mink\Session
|
||||
* The active Mink session object.
|
||||
*/
|
||||
public function getSession($name = NULL) {
|
||||
return $this->mink->getSession($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session cookies from current session.
|
||||
*
|
||||
* @return \GuzzleHttp\Cookie\CookieJar
|
||||
* A cookie jar with the current session.
|
||||
*/
|
||||
protected function getSessionCookies() {
|
||||
$domain = parse_url($this->getUrl(), PHP_URL_HOST);
|
||||
$session_id = $this->getSession()->getCookie($this->getSessionName());
|
||||
$cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the HTTP client for the system under test.
|
||||
*
|
||||
* Use this method for arbitrary HTTP requests to the site under test. For
|
||||
* most tests, you should not get the HTTP client and instead use navigation
|
||||
* methods such as drupalGet() and clickLink() in order to benefit from
|
||||
* assertions.
|
||||
*
|
||||
* Subclasses which substitute a different Mink driver should override this
|
||||
* method and provide a Guzzle client if the Mink driver provides one.
|
||||
*
|
||||
* @return \GuzzleHttp\ClientInterface
|
||||
* The client with BrowserTestBase configuration.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* If the Mink driver does not support a Guzzle HTTP client, throw an
|
||||
* exception.
|
||||
*/
|
||||
protected function getHttpClient() {
|
||||
/* @var $mink_driver \Behat\Mink\Driver\DriverInterface */
|
||||
$mink_driver = $this->getSession()->getDriver();
|
||||
if ($mink_driver instanceof GoutteDriver) {
|
||||
return $mink_driver->getClient()->getClient();
|
||||
}
|
||||
throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the options of select field.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement|string $select
|
||||
* Name, ID, or Label of select field to assert.
|
||||
* @param \Behat\Mink\Element\Element $container
|
||||
* (optional) Container element to check against. Defaults to current page.
|
||||
*
|
||||
* @return array
|
||||
* Associative array of option keys and values.
|
||||
*/
|
||||
protected function getOptions($select, Element $container = NULL) {
|
||||
if (is_string($select)) {
|
||||
$select = $this->assertSession()->selectExists($select, $container);
|
||||
}
|
||||
$options = [];
|
||||
/* @var \Behat\Mink\Element\NodeElement $option */
|
||||
foreach ($select->findAll('xpath', '//option') as $option) {
|
||||
$label = $option->getText();
|
||||
$value = $option->getAttribute('value') ?: $label;
|
||||
$options[$value] = $label;
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs Drupal into the Simpletest site.
|
||||
*/
|
||||
public function installDrupal() {
|
||||
$this->initUserSession();
|
||||
$this->prepareSettings();
|
||||
$this->doInstall();
|
||||
$this->initSettings();
|
||||
$container = $this->initKernel(\Drupal::request());
|
||||
$this->initConfig($container);
|
||||
$this->installModulesFromClassProperty($container);
|
||||
$this->rebuildAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents serializing any properties.
|
||||
*
|
||||
* Browser tests are run in a separate process. To do this PHPUnit creates a
|
||||
* script to run the test. If it fails, the test result object will contain a
|
||||
* stack trace which includes the test object. It will attempt to serialize
|
||||
* it. Returning an empty array prevents it from serializing anything it
|
||||
* should not.
|
||||
*
|
||||
* @return array
|
||||
* An empty array.
|
||||
*
|
||||
* @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
|
||||
*/
|
||||
public function __sleep() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a CSS expression to its XPath equivalent.
|
||||
*
|
||||
* The search is relative to the root element (HTML tag normally) of the page.
|
||||
*
|
||||
* @param string $selector
|
||||
* CSS selector to use in the search.
|
||||
* @param bool $html
|
||||
* (optional) Enables HTML support. Disable it for XML documents.
|
||||
* @param string $prefix
|
||||
* (optional) The prefix for the XPath expression.
|
||||
*
|
||||
* @return string
|
||||
* The equivalent XPath of a CSS expression.
|
||||
*/
|
||||
protected function cssSelectToXpath($selector, $html = TRUE, $prefix = 'descendant-or-self::') {
|
||||
return (new CssSelectorConverter($html))->toXPath($selector, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an xpath search on the contents of the internal browser.
|
||||
*
|
||||
* The search is relative to the root element (HTML tag normally) of the page.
|
||||
*
|
||||
* @param string $xpath
|
||||
* The xpath string to use in the search.
|
||||
* @param array $arguments
|
||||
* An array of arguments with keys in the form ':name' matching the
|
||||
* placeholders in the query. The values may be either strings or numeric
|
||||
* values.
|
||||
*
|
||||
* @return \Behat\Mink\Element\NodeElement[]
|
||||
* The list of elements matching the xpath expression.
|
||||
*/
|
||||
protected function xpath($xpath, array $arguments = []) {
|
||||
$xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments);
|
||||
return $this->getSession()->getPage()->findAll('xpath', $xpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration accessor for tests. Returns non-overridden configuration.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration name.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Config
|
||||
* The configuration object with original configuration data.
|
||||
*/
|
||||
protected function config($name) {
|
||||
return $this->container->get('config.factory')->getEditable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all response headers.
|
||||
*
|
||||
* @return array
|
||||
* The HTTP headers values.
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0.
|
||||
* Use $this->getSession()->getResponseHeaders() instead.
|
||||
*/
|
||||
protected function drupalGetHeaders() {
|
||||
return $this->getSession()->getResponseHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of an HTTP response header.
|
||||
*
|
||||
* If multiple requests were required to retrieve the page, only the headers
|
||||
* from the last request will be checked by default.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the header to retrieve. Names are case-insensitive (see RFC
|
||||
* 2616 section 4.2).
|
||||
*
|
||||
* @return string|null
|
||||
* The HTTP header value or NULL if not found.
|
||||
*/
|
||||
protected function drupalGetHeader($name) {
|
||||
return $this->getSession()->getResponseHeader($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the JavaScript drupalSettings variable for the currently-loaded page.
|
||||
*
|
||||
* @return array
|
||||
* The JSON decoded drupalSettings value from the current page.
|
||||
*/
|
||||
protected function getDrupalSettings() {
|
||||
$html = $this->getSession()->getPage()->getContent();
|
||||
if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $html, $matches)) {
|
||||
return Json::decode($matches[1]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
|
||||
// Cast objects implementing MarkupInterface to string instead of
|
||||
// relying on PHP casting them to string depending on what they are being
|
||||
// comparing with.
|
||||
$expected = static::castSafeStrings($expected);
|
||||
$actual = static::castSafeStrings($actual);
|
||||
parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current calling line in the class under test.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with keys 'file', 'line' and 'function'.
|
||||
*/
|
||||
protected function getTestMethodCaller() {
|
||||
$backtrace = debug_backtrace();
|
||||
// Find the test class that has the test method.
|
||||
while ($caller = Error::getLastCaller($backtrace)) {
|
||||
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
|
||||
break;
|
||||
}
|
||||
// If the test method is implemented by a test class's parent then the
|
||||
// class name of $this will not be part of the backtrace.
|
||||
// In that case we process the backtrace until the caller is not a
|
||||
// subclass of $this and return the previous caller.
|
||||
if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
|
||||
// Return the last caller since that has to be the test class.
|
||||
$caller = $last_caller;
|
||||
break;
|
||||
}
|
||||
// Otherwise we have not reached our test class yet: save the last caller
|
||||
// and remove an element from to backtrace to process the next call.
|
||||
$last_caller = $caller;
|
||||
array_shift($backtrace);
|
||||
}
|
||||
|
||||
return $caller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a nested array into a flat array suitable for drupalPostForm().
|
||||
*
|
||||
* @param array $values
|
||||
* A multi-dimensional form values array to convert.
|
||||
*
|
||||
* @return array
|
||||
* The flattened $edit array suitable for BrowserTestBase::drupalPostForm().
|
||||
*/
|
||||
protected function translatePostValues(array $values) {
|
||||
$edit = [];
|
||||
// The easiest and most straightforward way to translate values suitable for
|
||||
// BrowserTestBase::drupalPostForm() is to actually build the POST data
|
||||
// string and convert the resulting key/value pairs back into a flat array.
|
||||
$query = http_build_query($values);
|
||||
foreach (explode('&', $query) as $item) {
|
||||
list($key, $value) = explode('=', $item);
|
||||
$edit[urldecode($key)] = urldecode($value);
|
||||
}
|
||||
return $edit;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
|
||||
* @group Annotation
|
||||
*/
|
||||
class AnnotatedClassDiscoveryCachedTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Ensure FileCacheFactory::DISABLE_CACHE is *not* set, since we're testing
|
||||
// integration with the file cache.
|
||||
FileCacheFactory::setConfiguration([]);
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that getDefinitions() retrieves the file cache correctly.
|
||||
*
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions() {
|
||||
// Path to the classes which we'll discover and parse annotation.
|
||||
$discovery_path = __DIR__ . '/Fixtures';
|
||||
// File path that should be discovered within that directory.
|
||||
$file_path = $discovery_path . '/PluginNamespace/DiscoveryTest1.php';
|
||||
|
||||
$discovery = new AnnotatedClassDiscovery(['com\example' => [$discovery_path]]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
// Gain access to the file cache so we can change it.
|
||||
$ref_file_cache = new \ReflectionProperty($discovery, 'fileCache');
|
||||
$ref_file_cache->setAccessible(TRUE);
|
||||
/* @var $file_cache \Drupal\Component\FileCache\FileCacheInterface */
|
||||
$file_cache = $ref_file_cache->getValue($discovery);
|
||||
// The file cache is keyed by the file path, and we'll add some known
|
||||
// content to test against.
|
||||
$file_cache->set($file_path, [
|
||||
'id' => 'wrong_id',
|
||||
'content' => serialize(['an' => 'array']),
|
||||
]);
|
||||
|
||||
// Now perform the same query and check for the cached results.
|
||||
$this->assertEquals([
|
||||
'wrong_id' => [
|
||||
'an' => 'array',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
|
||||
* @group Annotation
|
||||
*/
|
||||
class AnnotatedClassDiscoveryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Ensure the file cache is disabled.
|
||||
FileCacheFactory::setConfiguration([FileCacheFactory::DISABLE_CACHE => TRUE]);
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::getPluginNamespaces
|
||||
*/
|
||||
public function testGetPluginNamespaces() {
|
||||
$discovery = new AnnotatedClassDiscovery(['com/example' => [__DIR__]]);
|
||||
|
||||
$reflection = new \ReflectionMethod($discovery, 'getPluginNamespaces');
|
||||
$reflection->setAccessible(TRUE);
|
||||
|
||||
$result = $reflection->invoke($discovery);
|
||||
$this->assertEquals(['com/example' => [__DIR__]], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
* @covers ::prepareAnnotationDefinition
|
||||
* @covers ::getAnnotationReader
|
||||
*/
|
||||
public function testGetDefinitions() {
|
||||
$discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
$custom_annotation_discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']], CustomPlugin::class, ['Drupal\Tests\Component\Annotation']);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
|
||||
'title' => 'Discovery test plugin',
|
||||
],
|
||||
], $custom_annotation_discovery->getDefinitions());
|
||||
|
||||
$empty_discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']], CustomPlugin2::class, ['Drupal\Tests\Component\Annotation']);
|
||||
$this->assertEquals([], $empty_discovery->getDefinitions());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom plugin annotation.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class CustomPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The plugin title.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom plugin annotation.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class CustomPlugin2 extends Plugin {}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationBase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\AnnotationBase
|
||||
* @group Annotation
|
||||
*/
|
||||
class AnnotationBaseTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getProvider
|
||||
* @covers ::setProvider
|
||||
*/
|
||||
public function testSetProvider() {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId() {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
// Doctrine sets the public prop directly.
|
||||
$plugin->id = 'example';
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getClass
|
||||
* @covers ::setClass
|
||||
*/
|
||||
public function testSetClass() {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class AnnotationBaseStub extends AnnotationBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace com\example\PluginNamespace;
|
||||
|
||||
/**
|
||||
* Provides a custom test plugin.
|
||||
*
|
||||
* @Plugin(
|
||||
* id = "discovery_test_1"
|
||||
* )
|
||||
* @CustomPlugin(
|
||||
* id = "discovery_test_1",
|
||||
* title = "Discovery test plugin"
|
||||
* )
|
||||
*/
|
||||
class DiscoveryTest1 {}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# This should not be loaded by our annotated class discovery.
|
||||
id:discovery_test_2
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Reflection\MockFileFinder
|
||||
* @group Annotation
|
||||
*/
|
||||
class MockFileFinderTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::create
|
||||
* @covers ::findFile
|
||||
*/
|
||||
public function testFindFile() {
|
||||
$tmp = MockFileFinder::create('testfilename.txt');
|
||||
$this->assertEquals('testfilename.txt', $tmp->findFile('n/a'));
|
||||
$this->assertEquals('testfilename.txt', $tmp->findFile('someclass'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator;
|
||||
use Drupal\Component\Plugin\Definition\PluginDefinition;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator
|
||||
* @group Plugin
|
||||
*/
|
||||
class AnnotationBridgeDecoratorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions() {
|
||||
$definitions = [];
|
||||
$definitions['object'] = new ObjectDefinition(['id' => 'foo']);
|
||||
$definitions['array'] = ['id' => 'bar'];
|
||||
$discovery = $this->prophesize(DiscoveryInterface::class);
|
||||
$discovery->getDefinitions()->willReturn($definitions);
|
||||
|
||||
$decorator = new AnnotationBridgeDecorator($discovery->reveal(), TestAnnotation::class);
|
||||
|
||||
$expected = [
|
||||
'object' => new ObjectDefinition(['id' => 'foo']),
|
||||
'array' => new ObjectDefinition(['id' => 'bar']),
|
||||
];
|
||||
$this->assertEquals($expected, $decorator->getDefinitions());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class TestAnnotation extends Plugin {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
return new ObjectDefinition($this->definition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class ObjectDefinition extends PluginDefinition {
|
||||
|
||||
/**
|
||||
* ObjectDefinition constructor.
|
||||
*
|
||||
* @param array $definition
|
||||
* An array of definition values.
|
||||
*/
|
||||
public function __construct(array $definition) {
|
||||
foreach ($definition as $property => $value) {
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\PluginID;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\PluginId
|
||||
* @group Annotation
|
||||
*/
|
||||
class PluginIdTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet() {
|
||||
// Assert plugin starts empty regardless of constructor.
|
||||
$plugin = new PluginID([
|
||||
'foo' => 'bar',
|
||||
'biz' => [
|
||||
'baz' => 'boom',
|
||||
],
|
||||
'nestedAnnotation' => new PluginID([
|
||||
'foo' => 'bar',
|
||||
]),
|
||||
'value' => 'biz',
|
||||
]);
|
||||
$this->assertEquals([
|
||||
'id' => NULL,
|
||||
'class' => NULL,
|
||||
'provider' => NULL,
|
||||
], $plugin->get());
|
||||
|
||||
// Set values and ensure we can retrieve them.
|
||||
$plugin->value = 'foo';
|
||||
$plugin->setClass('bar');
|
||||
$plugin->setProvider('baz');
|
||||
$this->assertEquals([
|
||||
'id' => 'foo',
|
||||
'class' => 'bar',
|
||||
'provider' => 'baz',
|
||||
], $plugin->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId() {
|
||||
$plugin = new PluginID([]);
|
||||
$plugin->value = 'example';
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin
|
||||
* @group Annotation
|
||||
*/
|
||||
class PluginTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::parse
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet() {
|
||||
// Assert all values are accepted through constructor and default value is
|
||||
// used for non existent but defined property.
|
||||
$plugin = new PluginStub([
|
||||
'foo' => 'bar',
|
||||
'biz' => [
|
||||
'baz' => 'boom',
|
||||
],
|
||||
'nestedAnnotation' => new Plugin([
|
||||
'foo' => 'bar',
|
||||
]),
|
||||
]);
|
||||
$this->assertEquals([
|
||||
// This property wasn't in our definition but is defined as a property on
|
||||
// our plugin class.
|
||||
'defaultProperty' => 'testvalue',
|
||||
'foo' => 'bar',
|
||||
'biz' => [
|
||||
'baz' => 'boom',
|
||||
],
|
||||
'nestedAnnotation' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
], $plugin->get());
|
||||
|
||||
// Without default properties, we get a completely empty plugin definition.
|
||||
$plugin = new Plugin([]);
|
||||
$this->assertEquals([], $plugin->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getProvider
|
||||
*/
|
||||
public function testGetProvider() {
|
||||
$plugin = new Plugin(['provider' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setProvider
|
||||
*/
|
||||
public function testSetProvider() {
|
||||
$plugin = new Plugin([]);
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId() {
|
||||
$plugin = new Plugin(['id' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getClass
|
||||
*/
|
||||
public function testGetClass() {
|
||||
$plugin = new Plugin(['class' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setClass
|
||||
*/
|
||||
public function testSetClass() {
|
||||
$plugin = new Plugin([]);
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class PluginStub extends Plugin {
|
||||
protected $defaultProperty = 'testvalue';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\Assertion\InspectorTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\Assertion;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Assertion\Inspector
|
||||
* @group Assertion
|
||||
*/
|
||||
class InspectorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests asserting argument is an array or traversable object.
|
||||
*
|
||||
* @covers ::assertTraversable
|
||||
*/
|
||||
public function testAssertTraversable() {
|
||||
$this->assertTrue(Inspector::assertTraversable([]));
|
||||
$this->assertTrue(Inspector::assertTraversable(new \ArrayObject()));
|
||||
$this->assertFalse(Inspector::assertTraversable(new \stdClass()));
|
||||
$this->assertFalse(Inspector::assertTraversable('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are strings.
|
||||
*
|
||||
* @covers ::assertAllStrings
|
||||
* @dataProvider providerTestAssertAllStrings
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are strings or objects with __toString().
|
||||
*
|
||||
* @covers ::assertAllStringable
|
||||
*/
|
||||
public function testAssertAllStringable() {
|
||||
$this->assertTrue(Inspector::assertAllStringable([]));
|
||||
$this->assertTrue(Inspector::assertAllStringable(['foo', 'bar']));
|
||||
$this->assertFalse(Inspector::assertAllStringable('foo'));
|
||||
$this->assertTrue(Inspector::assertAllStringable(['foo', new StringObject()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are arrays.
|
||||
*
|
||||
* @covers ::assertAllArrays
|
||||
*/
|
||||
public function testAssertAllArrays() {
|
||||
$this->assertTrue(Inspector::assertAllArrays([]));
|
||||
$this->assertTrue(Inspector::assertAllArrays([[], []]));
|
||||
$this->assertFalse(Inspector::assertAllArrays([[], 'foo']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting array is 0-indexed - the strict definition of array.
|
||||
*
|
||||
* @covers ::assertStrictArray
|
||||
*/
|
||||
public function testAssertStrictArray() {
|
||||
$this->assertTrue(Inspector::assertStrictArray([]));
|
||||
$this->assertTrue(Inspector::assertStrictArray(['bar', 'foo']));
|
||||
$this->assertFalse(Inspector::assertStrictArray(['foo' => 'bar', 'bar' => 'foo']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are strict arrays.
|
||||
*
|
||||
* @covers ::assertAllStrictArrays
|
||||
*/
|
||||
public function testAssertAllStrictArrays() {
|
||||
$this->assertTrue(Inspector::assertAllStrictArrays([]));
|
||||
$this->assertTrue(Inspector::assertAllStrictArrays([[], []]));
|
||||
$this->assertFalse(Inspector::assertAllStrictArrays([['foo' => 'bar', 'bar' => 'foo']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members have specified keys.
|
||||
*
|
||||
* @covers ::assertAllHaveKey
|
||||
*/
|
||||
public function testAssertAllHaveKey() {
|
||||
$this->assertTrue(Inspector::assertAllHaveKey([]));
|
||||
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']]));
|
||||
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'foo'));
|
||||
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'bar', 'foo'));
|
||||
$this->assertFalse(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'bar', 'foo', 'moo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are integers.
|
||||
*
|
||||
* @covers ::assertAllIntegers
|
||||
*/
|
||||
public function testAssertAllIntegers() {
|
||||
$this->assertTrue(Inspector::assertAllIntegers([]));
|
||||
$this->assertTrue(Inspector::assertAllIntegers([1, 2, 3]));
|
||||
$this->assertFalse(Inspector::assertAllIntegers([1, 2, 3.14]));
|
||||
$this->assertFalse(Inspector::assertAllIntegers([1, '2', 3]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are floating point variables.
|
||||
*
|
||||
* @covers ::assertAllFloat
|
||||
*/
|
||||
public function testAssertAllFloat() {
|
||||
$this->assertTrue(Inspector::assertAllFloat([]));
|
||||
$this->assertTrue(Inspector::assertAllFloat([1.0, 2.1, 3.14]));
|
||||
$this->assertFalse(Inspector::assertAllFloat([1, 2.1, 3.14]));
|
||||
$this->assertFalse(Inspector::assertAllFloat([1.0, '2', 3]));
|
||||
$this->assertFalse(Inspector::assertAllFloat(['Titanic']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are callable.
|
||||
*
|
||||
* @covers ::assertAllCallable
|
||||
*/
|
||||
public function testAllCallable() {
|
||||
$this->assertTrue(Inspector::assertAllCallable([
|
||||
'strchr',
|
||||
[$this, 'callMe'],
|
||||
[__CLASS__, 'callMeStatic'],
|
||||
function () {
|
||||
return TRUE;
|
||||
},
|
||||
]));
|
||||
|
||||
$this->assertFalse(Inspector::assertAllCallable([
|
||||
'strchr',
|
||||
[$this, 'callMe'],
|
||||
[__CLASS__, 'callMeStatic'],
|
||||
function () {
|
||||
return TRUE;
|
||||
},
|
||||
"I'm not callable",
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are !empty().
|
||||
*
|
||||
* @covers ::assertAllNotEmpty
|
||||
*/
|
||||
public function testAllNotEmpty() {
|
||||
$this->assertTrue(Inspector::assertAllNotEmpty([1, 'two']));
|
||||
$this->assertFalse(Inspector::assertAllNotEmpty(['']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all arguments are numbers or strings castable to numbers.
|
||||
*
|
||||
* @covers ::assertAllNumeric
|
||||
*/
|
||||
public function testAssertAllNumeric() {
|
||||
$this->assertTrue(Inspector::assertAllNumeric([1, '2', 3.14]));
|
||||
$this->assertFalse(Inspector::assertAllNumeric([1, 'two', 3.14]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting strstr() or stristr() match.
|
||||
*
|
||||
* @covers ::assertAllMatch
|
||||
*/
|
||||
public function testAssertAllMatch() {
|
||||
$this->assertTrue(Inspector::assertAllMatch('f', ['fee', 'fi', 'fo']));
|
||||
$this->assertTrue(Inspector::assertAllMatch('F', ['fee', 'fi', 'fo']));
|
||||
$this->assertTrue(Inspector::assertAllMatch('f', ['fee', 'fi', 'fo'], TRUE));
|
||||
$this->assertFalse(Inspector::assertAllMatch('F', ['fee', 'fi', 'fo'], TRUE));
|
||||
$this->assertFalse(Inspector::assertAllMatch('e', ['fee', 'fi', 'fo']));
|
||||
$this->assertFalse(Inspector::assertAllMatch('1', [12]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting regular expression match.
|
||||
*
|
||||
* @covers ::assertAllRegularExpressionMatch
|
||||
*/
|
||||
public function testAssertAllRegularExpressionMatch() {
|
||||
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/f/i', ['fee', 'fi', 'fo']));
|
||||
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/F/i', ['fee', 'fi', 'fo']));
|
||||
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/f/', ['fee', 'fi', 'fo']));
|
||||
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/F/', ['fee', 'fi', 'fo']));
|
||||
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/e/', ['fee', 'fi', 'fo']));
|
||||
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/1/', [12]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asserting all members are objects.
|
||||
*
|
||||
* @covers ::assertAllObjects
|
||||
*/
|
||||
public function testAssertAllObjects() {
|
||||
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject()]));
|
||||
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject(), 'foo']));
|
||||
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject()], '\\Traversable'));
|
||||
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject(), 'foo'], '\\Traversable'));
|
||||
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new StringObject()], '\\Traversable'));
|
||||
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new StringObject()], '\\Traversable', '\\Drupal\\Tests\\Component\\Assertion\\StringObject'));
|
||||
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new StringObject(), new \stdClass()], '\\ArrayObject', '\\Drupal\\Tests\\Component\\Assertion\\StringObject'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method referenced by ::testAllCallable().
|
||||
*/
|
||||
public function callMe() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method referenced by ::testAllCallable().
|
||||
*/
|
||||
public static function callMeStatic() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick class for testing for objects with __toString.
|
||||
*/
|
||||
class StringObject {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() {
|
||||
return 'foo';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Bridge;
|
||||
|
||||
use Drupal\Component\Bridge\ZfExtensionManagerSfContainer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Zend\Feed\Reader\Extension\Atom\Entry;
|
||||
use Zend\Feed\Reader\StandaloneExtensionManager;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Bridge\ZfExtensionManagerSfContainer
|
||||
* @group Bridge
|
||||
*/
|
||||
class ZfExtensionManagerSfContainerTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::setContainer
|
||||
* @covers ::setStandalone
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet() {
|
||||
$service = new \stdClass();
|
||||
$service->value = 'myvalue';
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('foo', $service);
|
||||
$bridge = new ZfExtensionManagerSfContainer();
|
||||
$bridge->setContainer($container);
|
||||
$this->assertEquals($service, $bridge->get('foo'));
|
||||
$bridge->setStandalone(StandaloneExtensionManager::class);
|
||||
$this->assertInstanceOf(Entry::class, $bridge->get('Atom\Entry'));
|
||||
// Ensure that the container is checked first.
|
||||
$container->set('atomentry', $service);
|
||||
$this->assertEquals($service, $bridge->get('Atom\Entry'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setContainer
|
||||
* @covers ::setStandalone
|
||||
* @covers ::has
|
||||
*/
|
||||
public function testHas() {
|
||||
$service = new \stdClass();
|
||||
$service->value = 'myvalue';
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('foo', $service);
|
||||
$bridge = new ZfExtensionManagerSfContainer();
|
||||
$bridge->setContainer($container);
|
||||
$this->assertTrue($bridge->has('foo'));
|
||||
$this->assertFalse($bridge->has('bar'));
|
||||
$this->assertFalse($bridge->has('Atom\Entry'));
|
||||
$bridge->setStandalone(StandaloneExtensionManager::class);
|
||||
$this->assertTrue($bridge->has('Atom\Entry'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setStandalone
|
||||
*/
|
||||
public function testSetStandaloneException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Drupal\Tests\Component\Bridge\ZfExtensionManagerSfContainerTest must implement Zend\Feed\Reader\ExtensionManagerInterface or Zend\Feed\Writer\ExtensionManagerInterface');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class, 'Drupal\Tests\Component\Bridge\ZfExtensionManagerSfContainerTest must implement Zend\Feed\Reader\ExtensionManagerInterface or Zend\Feed\Writer\ExtensionManagerInterface');
|
||||
}
|
||||
$bridge = new ZfExtensionManagerSfContainer();
|
||||
$bridge->setStandalone(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGetContainerException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(ServiceNotFoundException::class);
|
||||
$this->expectExceptionMessage('You have requested a non-existent service "test.foo".');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(ServiceNotFoundException::class, 'You have requested a non-existent service "test.foo".');
|
||||
}
|
||||
$container = new ContainerBuilder();
|
||||
$bridge = new ZfExtensionManagerSfContainer('test.');
|
||||
$bridge->setContainer($container);
|
||||
$bridge->setStandalone(StandaloneExtensionManager::class);
|
||||
$bridge->get('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::has
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testPrefix() {
|
||||
$service = new \stdClass();
|
||||
$service->value = 'myvalue';
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('foo.bar', $service);
|
||||
$bridge = new ZfExtensionManagerSfContainer('foo.');
|
||||
$bridge->setContainer($container);
|
||||
$this->assertTrue($bridge->has('bar'));
|
||||
$this->assertFalse($bridge->has('baz'));
|
||||
$this->assertEquals($service, $bridge->get('bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::canonicalizeName
|
||||
* @dataProvider canonicalizeNameProvider
|
||||
*/
|
||||
public function testCanonicalizeName($name, $canonical_name) {
|
||||
$service = new \stdClass();
|
||||
$service->value = 'myvalue';
|
||||
$container = new ContainerBuilder();
|
||||
$container->set($canonical_name, $service);
|
||||
$bridge = new ZfExtensionManagerSfContainer();
|
||||
$bridge->setContainer($container);
|
||||
$this->assertTrue($bridge->has($name));
|
||||
$this->assertEquals($service, $bridge->get($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testReverseProxyEnabled.
|
||||
*
|
||||
* Replacements:
|
||||
* array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '')
|
||||
*/
|
||||
public function canonicalizeNameProvider() {
|
||||
return [
|
||||
[
|
||||
'foobar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo-bar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo_bar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo bar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo\\bar',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo/bar',
|
||||
'foobar',
|
||||
],
|
||||
// There is also a strtolower in canonicalizeName.
|
||||
[
|
||||
'Foo/bAr',
|
||||
'foobar',
|
||||
],
|
||||
[
|
||||
'foo/-_\\ bar',
|
||||
'foobar',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\ClassFinder;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Drupal\Component\ClassFinder\ClassFinder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\ClassFinder\ClassFinder
|
||||
* @group ClassFinder
|
||||
*/
|
||||
class ClassFinderTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::findFile
|
||||
*/
|
||||
public function testFindFile() {
|
||||
$finder = new ClassFinder();
|
||||
|
||||
// The full path is returned therefore only tests with
|
||||
// assertStringEndsWith() so the test is portable.
|
||||
$this->assertStringEndsWith('core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php', $finder->findFile(ClassFinderTest::class));
|
||||
$class = 'Not\\A\\Class';
|
||||
$this->assertNull($finder->findFile($class));
|
||||
|
||||
// Register an autoloader that can find this class.
|
||||
$loader = new ClassLoader();
|
||||
$loader->addClassMap([$class => __FILE__]);
|
||||
$loader->register();
|
||||
$this->assertEquals(__FILE__, $finder->findFile($class));
|
||||
// This shouldn't prevent us from finding the original file.
|
||||
$this->assertStringEndsWith('core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php', $finder->findFile(ClassFinderTest::class));
|
||||
|
||||
// Clean up the additional autoloader after the test.
|
||||
$loader->unregister();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,923 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Datetime;
|
||||
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus
|
||||
* @group Datetime
|
||||
*/
|
||||
class DateTimePlusTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Test creating dates from string and array input.
|
||||
*
|
||||
* @param mixed $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $expected
|
||||
* Expected output from DateTimePlus::format().
|
||||
*
|
||||
* @dataProvider providerTestDates
|
||||
*/
|
||||
public function testDates($input, $timezone, $expected) {
|
||||
$date = new DateTimePlus($input, $timezone);
|
||||
$value = $date->format('c');
|
||||
|
||||
if (is_array($input)) {
|
||||
$input = var_export($input, TRUE);
|
||||
}
|
||||
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating dates from string and array input.
|
||||
*
|
||||
* @param mixed $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $expected
|
||||
* Expected output from DateTimePlus::format().
|
||||
*
|
||||
* @dataProvider providerTestDateArrays
|
||||
*/
|
||||
public function testDateArrays($input, $timezone, $expected) {
|
||||
$date = DateTimePlus::createFromArray($input, $timezone);
|
||||
$value = $date->format('c');
|
||||
|
||||
if (is_array($input)) {
|
||||
$input = var_export($input, TRUE);
|
||||
}
|
||||
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test date diffs.
|
||||
*
|
||||
* @param mixed $input1
|
||||
* A DateTimePlus object.
|
||||
* @param mixed $input2
|
||||
* Date argument for DateTimePlus::diff method.
|
||||
* @param bool $absolute
|
||||
* Absolute flag for DateTimePlus::diff method.
|
||||
* @param \DateInterval $expected
|
||||
* The expected result of the DateTimePlus::diff operation.
|
||||
*
|
||||
* @dataProvider providerTestDateDiff
|
||||
*/
|
||||
public function testDateDiff($input1, $input2, $absolute, \DateInterval $expected) {
|
||||
$interval = $input1->diff($input2, $absolute);
|
||||
$this->assertEquals($interval, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test date diff exception caused by invalid input.
|
||||
*
|
||||
* @param mixed $input1
|
||||
* A DateTimePlus object.
|
||||
* @param mixed $input2
|
||||
* Date argument for DateTimePlus::diff method.
|
||||
* @param bool $absolute
|
||||
* Absolute flag for DateTimePlus::diff method.
|
||||
*
|
||||
* @dataProvider providerTestInvalidDateDiff
|
||||
*/
|
||||
public function testInvalidDateDiff($input1, $input2, $absolute) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$this->expectExceptionMessage('Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\BadMethodCallException::class, 'Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object');
|
||||
}
|
||||
$interval = $input1->diff($input2, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating dates from invalid array input.
|
||||
*
|
||||
* @param mixed $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $class
|
||||
* The Exception subclass to expect to be thrown.
|
||||
*
|
||||
* @dataProvider providerTestInvalidDateArrays
|
||||
*/
|
||||
public function testInvalidDateArrays($input, $timezone, $class) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException($class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException($class);
|
||||
}
|
||||
$this->assertInstanceOf(
|
||||
'\Drupal\Component\DateTimePlus',
|
||||
DateTimePlus::createFromArray($input, $timezone)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating dates from timestamps, and manipulating timezones.
|
||||
*
|
||||
* @param int $input
|
||||
* Input argument for DateTimePlus::createFromTimestamp().
|
||||
* @param array $initial
|
||||
* An array containing:
|
||||
* - 'timezone_initial' - Timezone argument for DateTimePlus.
|
||||
* - 'format_initial' - Format argument for DateTimePlus.
|
||||
* - 'expected_initial_date' - Expected output from DateTimePlus::format().
|
||||
* - 'expected_initial_timezone' - Expected output from
|
||||
* DateTimePlus::getTimeZone()::getName().
|
||||
* - 'expected_initial_offset' - Expected output from DateTimePlus::getOffset().
|
||||
* @param array $transform
|
||||
* An array containing:
|
||||
* - 'timezone_transform' - Argument to transform date to another timezone via
|
||||
* DateTimePlus::setTimezone().
|
||||
* - 'format_transform' - Format argument to use when transforming date to
|
||||
* another timezone.
|
||||
* - 'expected_transform_date' - Expected output from DateTimePlus::format(),
|
||||
* after timezone transform.
|
||||
* - 'expected_transform_timezone' - Expected output from
|
||||
* DateTimePlus::getTimeZone()::getName(), after timezone transform.
|
||||
* - 'expected_transform_offset' - Expected output from
|
||||
* DateTimePlus::getOffset(), after timezone transform.
|
||||
*
|
||||
* @dataProvider providerTestTimestamp
|
||||
*/
|
||||
public function testTimestamp($input, array $initial, array $transform) {
|
||||
// Initialize a new date object.
|
||||
$date = DateTimePlus::createFromTimestamp($input, $initial['timezone']);
|
||||
$this->assertDateTimestamp($date, $input, $initial, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating dates from datetime strings.
|
||||
*
|
||||
* @param string $input
|
||||
* Input argument for DateTimePlus().
|
||||
* @param array $initial
|
||||
* @see testTimestamp()
|
||||
* @param array $transform
|
||||
* @see testTimestamp()
|
||||
*
|
||||
* @dataProvider providerTestDateTimestamp
|
||||
*/
|
||||
public function testDateTimestamp($input, array $initial, array $transform) {
|
||||
// Initialize a new date object.
|
||||
$date = new DateTimePlus($input, $initial['timezone']);
|
||||
$this->assertDateTimestamp($date, $input, $initial, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertion helper for testTimestamp and testDateTimestamp since they need
|
||||
* different dataProviders.
|
||||
*
|
||||
* @param \Drupal\Component\Datetime\DateTimePlus $date
|
||||
* DateTimePlus to test.
|
||||
* @input mixed $input
|
||||
* The original input passed to the test method.
|
||||
* @param array $initial
|
||||
* @see testTimestamp()
|
||||
* @param array $transform
|
||||
* @see testTimestamp()
|
||||
*/
|
||||
public function assertDateTimestamp($date, $input, $initial, $transform) {
|
||||
// Check format.
|
||||
$value = $date->format($initial['format']);
|
||||
$this->assertEquals($initial['expected_date'], $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $initial['timezone'], $initial['expected_date'], $value));
|
||||
|
||||
// Check timezone name.
|
||||
$value = $date->getTimeZone()->getName();
|
||||
$this->assertEquals($initial['expected_timezone'], $value, sprintf("The current timezone is %s: should be %s.", $value, $initial['expected_timezone']));
|
||||
|
||||
// Check offset.
|
||||
$value = $date->getOffset();
|
||||
$this->assertEquals($initial['expected_offset'], $value, sprintf("The current offset is %s: should be %s.", $value, $initial['expected_offset']));
|
||||
|
||||
// Transform the date to another timezone.
|
||||
$date->setTimezone(new \DateTimeZone($transform['timezone']));
|
||||
|
||||
// Check transformed format.
|
||||
$value = $date->format($transform['format']);
|
||||
$this->assertEquals($transform['expected_date'], $value, sprintf("Test \$date->setTimezone(new \\DateTimeZone(%s)): should be %s, found %s.", $transform['timezone'], $transform['expected_date'], $value));
|
||||
|
||||
// Check transformed timezone.
|
||||
$value = $date->getTimeZone()->getName();
|
||||
$this->assertEquals($transform['expected_timezone'], $value, sprintf("The current timezone should be %s, found %s.", $transform['expected_timezone'], $value));
|
||||
|
||||
// Check transformed offset.
|
||||
$value = $date->getOffset();
|
||||
$this->assertEquals($transform['expected_offset'], $value, sprintf("The current offset should be %s, found %s.", $transform['expected_offset'], $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating dates from format strings.
|
||||
*
|
||||
* @param string $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $format_date
|
||||
* Format argument for DateTimePlus::format().
|
||||
* @param string $expected
|
||||
* Expected output from DateTimePlus::format().
|
||||
*
|
||||
* @dataProvider providerTestDateFormat
|
||||
*/
|
||||
public function testDateFormat($input, $timezone, $format, $format_date, $expected) {
|
||||
$date = DateTimePlus::createFromFormat($format, $input, $timezone);
|
||||
$value = $date->format($format_date);
|
||||
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s, %s): should be %s, found %s.", $input, $timezone, $format, $expected, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid date handling.
|
||||
*
|
||||
* @param mixed $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $format
|
||||
* Format argument for DateTimePlus.
|
||||
* @param string $message
|
||||
* Message to print if no errors are thrown by the invalid dates.
|
||||
* @param string $class
|
||||
* The Exception subclass to expect to be thrown.
|
||||
*
|
||||
* @dataProvider providerTestInvalidDates
|
||||
*/
|
||||
public function testInvalidDates($input, $timezone, $format, $message, $class) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException($class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException($class);
|
||||
}
|
||||
DateTimePlus::createFromFormat($format, $input, $timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that DrupalDateTime can detect the right timezone to use.
|
||||
* When specified or not.
|
||||
*
|
||||
* @param mixed $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param mixed $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $expected_timezone
|
||||
* Expected timezone returned from DateTimePlus::getTimezone::getName().
|
||||
* @param string $message
|
||||
* Message to print on test failure.
|
||||
*
|
||||
* @dataProvider providerTestDateTimezone
|
||||
*/
|
||||
public function testDateTimezone($input, $timezone, $expected_timezone, $message) {
|
||||
$date = new DateTimePlus($input, $timezone);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertEquals($timezone, $expected_timezone, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DrupalDateTime can detect the right timezone to use when
|
||||
* constructed from a datetime object.
|
||||
*/
|
||||
public function testDateTimezoneWithDateTimeObject() {
|
||||
// Create a date object with another date object.
|
||||
$input = new \DateTime('now', new \DateTimeZone('Pacific/Midway'));
|
||||
$timezone = NULL;
|
||||
$expected_timezone = 'Pacific/Midway';
|
||||
$message = 'DateTimePlus uses the specified timezone if provided.';
|
||||
|
||||
$date = DateTimePlus::createFromDateTime($input, $timezone);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertEquals($timezone, $expected_timezone, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for date tests.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the input parameters for
|
||||
* DateTimePlusTest::testDates().
|
||||
*
|
||||
* @see DateTimePlusTest::testDates()
|
||||
*/
|
||||
public function providerTestDates() {
|
||||
$dates = [
|
||||
// String input.
|
||||
// Create date object from datetime string.
|
||||
['2009-03-07 10:30', 'America/Chicago', '2009-03-07T10:30:00-06:00'],
|
||||
// Same during daylight savings time.
|
||||
['2009-06-07 10:30', 'America/Chicago', '2009-06-07T10:30:00-05:00'],
|
||||
// Create date object from date string.
|
||||
['2009-03-07', 'America/Chicago', '2009-03-07T00:00:00-06:00'],
|
||||
// Same during daylight savings time.
|
||||
['2009-06-07', 'America/Chicago', '2009-06-07T00:00:00-05:00'],
|
||||
// Create date object from date string.
|
||||
['2009-03-07 10:30', 'Australia/Canberra', '2009-03-07T10:30:00+11:00'],
|
||||
// Same during daylight savings time.
|
||||
['2009-06-07 10:30', 'Australia/Canberra', '2009-06-07T10:30:00+10:00'],
|
||||
];
|
||||
|
||||
// On 32-bit systems, timestamps are limited to 1901-2038.
|
||||
if (PHP_INT_SIZE > 4) {
|
||||
// Create a date object in the distant past.
|
||||
// @see https://www.drupal.org/node/2795489#comment-12127088
|
||||
if (version_compare(PHP_VERSION, '5.6.15', '>=')) {
|
||||
// Note that this date is after the United States standardized its
|
||||
// timezones.
|
||||
$dates[] = ['1883-11-19 10:30', 'America/Chicago', '1883-11-19T10:30:00-06:00'];
|
||||
}
|
||||
// Create a date object in the far future.
|
||||
$dates[] = ['2345-01-02 02:04', 'UTC', '2345-01-02T02:04:00+00:00'];
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for date tests.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the input parameters for
|
||||
* DateTimePlusTest::testDates().
|
||||
*
|
||||
* @see DateTimePlusTest::testDates()
|
||||
*/
|
||||
public function providerTestDateArrays() {
|
||||
$dates = [
|
||||
// Array input.
|
||||
// Create date object from date array, date only.
|
||||
[['year' => 2010, 'month' => 2, 'day' => 28], 'America/Chicago', '2010-02-28T00:00:00-06:00'],
|
||||
// Create date object from date array with hour.
|
||||
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'America/Chicago', '2010-02-28T10:00:00-06:00'],
|
||||
// Create date object from date array, date only.
|
||||
[['year' => 2010, 'month' => 2, 'day' => 28], 'Europe/Berlin', '2010-02-28T00:00:00+01:00'],
|
||||
// Create date object from date array with hour.
|
||||
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'Europe/Berlin', '2010-02-28T10:00:00+01:00'],
|
||||
];
|
||||
|
||||
// On 32-bit systems, timestamps are limited to 1901-2038.
|
||||
if (PHP_INT_SIZE > 4) {
|
||||
// Create a date object in the distant past.
|
||||
// @see https://www.drupal.org/node/2795489#comment-12127088
|
||||
if (version_compare(PHP_VERSION, '5.6.15', '>=')) {
|
||||
// Note that this date is after the United States standardized its
|
||||
// timezones.
|
||||
$dates[] = [['year' => 1883, 'month' => 11, 'day' => 19], 'America/Chicago', '1883-11-19T00:00:00-06:00'];
|
||||
}
|
||||
// Create a date object in the far future.
|
||||
$dates[] = [['year' => 2345, 'month' => 1, 'day' => 2], 'UTC', '2345-01-02T00:00:00+00:00'];
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testDateFormats.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing:
|
||||
* - 'input' - Input to DateTimePlus.
|
||||
* - 'timezone' - Timezone for DateTimePlus.
|
||||
* - 'format' - Date format for DateTimePlus.
|
||||
* - 'format_date' - Date format for use in $date->format() method.
|
||||
* - 'expected' - The expected return from DateTimePlus.
|
||||
*
|
||||
* @see testDateFormats()
|
||||
*/
|
||||
public function providerTestDateFormat() {
|
||||
return [
|
||||
// Create a year-only date.
|
||||
['2009', NULL, 'Y', 'Y', '2009'],
|
||||
// Create a month and year-only date.
|
||||
['2009-10', NULL, 'Y-m', 'Y-m', '2009-10'],
|
||||
// Create a time-only date.
|
||||
['T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'],
|
||||
// Create a time-only date.
|
||||
['10:30:00', NULL, 'H:i:s', 'H:i:s', '10:30:00'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testInvalidDates.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing:
|
||||
* - 'input' - Input for DateTimePlus.
|
||||
* - 'timezone' - Timezone for DateTimePlus.
|
||||
* - 'format' - Format for DateTimePlus.
|
||||
* - 'message' - Message to display on failure.
|
||||
*
|
||||
* @see testInvalidDates
|
||||
*/
|
||||
public function providerTestInvalidDates() {
|
||||
return [
|
||||
// Test for invalid month names when we are using a short version
|
||||
// of the month.
|
||||
['23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors.", \InvalidArgumentException::class],
|
||||
// Test for invalid hour.
|
||||
['0000-00-00T45:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-00T45:30:00 contains an invalid hour and did not produce errors.", \UnexpectedValueException::class],
|
||||
// Test for invalid day.
|
||||
['0000-00-99T05:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-99T05:30:00 contains an invalid day and did not produce errors.", \UnexpectedValueException::class],
|
||||
// Test for invalid month.
|
||||
['0000-75-00T15:30:00', NULL, 'Y-m-d\TH:i:s', "0000-75-00T15:30:00 contains an invalid month and did not produce errors.", \UnexpectedValueException::class],
|
||||
// Test for invalid year.
|
||||
['11-08-01T15:30:00', NULL, 'Y-m-d\TH:i:s', "11-08-01T15:30:00 contains an invalid year and did not produce errors.", \UnexpectedValueException::class],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testInvalidDateArrays.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing:
|
||||
* - 'input' - Input for DateTimePlus.
|
||||
* - 'timezone' - Timezone for DateTimePlus.
|
||||
*
|
||||
* @see testInvalidDateArrays
|
||||
*/
|
||||
public function providerTestInvalidDateArrays() {
|
||||
return [
|
||||
// One year larger than the documented upper limit of checkdate().
|
||||
[['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
|
||||
// One year smaller than the documented lower limit of checkdate().
|
||||
[['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
|
||||
// Test for invalid month from date array.
|
||||
[['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
|
||||
// Test for invalid hour from date array.
|
||||
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
|
||||
// Test for invalid minute from date array.
|
||||
[['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
|
||||
// Regression test for https://www.drupal.org/node/2084455.
|
||||
[['hour' => 59, 'minute' => 1, 'second' => 1], 'America/Chicago', \InvalidArgumentException::class],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testDateTimezone.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing:
|
||||
* - 'date' - Date string or object for DateTimePlus.
|
||||
* - 'timezone' - Timezone string for DateTimePlus.
|
||||
* - 'expected' - Expected return from DateTimePlus::getTimezone()::getName().
|
||||
* - 'message' - Message to display on test failure.
|
||||
*
|
||||
* @see testDateTimezone
|
||||
*/
|
||||
public function providerTestDateTimezone() {
|
||||
// Use a common date for most of the tests.
|
||||
$date_string = '2007-01-31 21:00:00';
|
||||
|
||||
// Detect the system timezone.
|
||||
$system_timezone = date_default_timezone_get();
|
||||
|
||||
return [
|
||||
// Create a date object with an unspecified timezone, which should
|
||||
// end up using the system timezone.
|
||||
[$date_string, NULL, $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'],
|
||||
// Create a date object with a specified timezone name.
|
||||
[$date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'],
|
||||
// Create a date object with a timezone object.
|
||||
[$date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'],
|
||||
// Create a date object with another date object.
|
||||
[new DateTimePlus('now', 'Pacific/Midway'), NULL, 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testTimestamp.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the arguments required for
|
||||
* self::testTimestamp().
|
||||
*
|
||||
* @see testTimestamp()
|
||||
*/
|
||||
public function providerTestTimestamp() {
|
||||
return [
|
||||
// Create date object from a unix timestamp and display it in
|
||||
// local time.
|
||||
[
|
||||
'input' => 0,
|
||||
'initial' => [
|
||||
'timezone' => 'UTC',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1970-01-01T00:00:00+00:00',
|
||||
'expected_timezone' => 'UTC',
|
||||
'expected_offset' => 0,
|
||||
],
|
||||
'transform' => [
|
||||
'timezone' => 'America/Los_Angeles',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T16:00:00-08:00',
|
||||
'expected_timezone' => 'America/Los_Angeles',
|
||||
'expected_offset' => '-28800',
|
||||
],
|
||||
],
|
||||
// Create a date using the timestamp of zero, then display its
|
||||
// value both in UTC and the local timezone.
|
||||
[
|
||||
'input' => 0,
|
||||
'initial' => [
|
||||
'timezone' => 'America/Los_Angeles',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T16:00:00-08:00',
|
||||
'expected_timezone' => 'America/Los_Angeles',
|
||||
'expected_offset' => '-28800',
|
||||
],
|
||||
'transform' => [
|
||||
'timezone' => 'UTC',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1970-01-01T00:00:00+00:00',
|
||||
'expected_timezone' => 'UTC',
|
||||
'expected_offset' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testDateTimestamp.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the arguments required for
|
||||
* self::testDateTimestamp().
|
||||
*
|
||||
* @see testDateTimestamp()
|
||||
*/
|
||||
public function providerTestDateTimestamp() {
|
||||
return [
|
||||
// Create date object from datetime string in UTC, and convert
|
||||
// it to a local date.
|
||||
[
|
||||
'input' => '1970-01-01 00:00:00',
|
||||
'initial' => [
|
||||
'timezone' => 'UTC',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1970-01-01T00:00:00+00:00',
|
||||
'expected_timezone' => 'UTC',
|
||||
'expected_offset' => 0,
|
||||
],
|
||||
'transform' => [
|
||||
'timezone' => 'America/Los_Angeles',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T16:00:00-08:00',
|
||||
'expected_timezone' => 'America/Los_Angeles',
|
||||
'expected_offset' => '-28800',
|
||||
],
|
||||
],
|
||||
// Convert the local time to UTC using string input.
|
||||
[
|
||||
'input' => '1969-12-31 16:00:00',
|
||||
'initial' => [
|
||||
'timezone' => 'America/Los_Angeles',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T16:00:00-08:00',
|
||||
'expected_timezone' => 'America/Los_Angeles',
|
||||
'expected_offset' => '-28800',
|
||||
],
|
||||
'transform' => [
|
||||
'timezone' => 'UTC',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1970-01-01T00:00:00+00:00',
|
||||
'expected_timezone' => 'UTC',
|
||||
'expected_offset' => 0,
|
||||
],
|
||||
],
|
||||
// Convert the local time to UTC using string input.
|
||||
[
|
||||
'input' => '1969-12-31 16:00:00',
|
||||
'initial' => [
|
||||
'timezone' => 'Europe/Warsaw',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T16:00:00+01:00',
|
||||
'expected_timezone' => 'Europe/Warsaw',
|
||||
'expected_offset' => '+3600',
|
||||
],
|
||||
'transform' => [
|
||||
'timezone' => 'UTC',
|
||||
'format' => 'c',
|
||||
'expected_date' => '1969-12-31T15:00:00+00:00',
|
||||
'expected_timezone' => 'UTC',
|
||||
'expected_offset' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for date tests.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the input parameters for
|
||||
* DateTimePlusTest::testDateDiff().
|
||||
*
|
||||
* @see DateTimePlusTest::testDateDiff()
|
||||
*/
|
||||
public function providerTestDateDiff() {
|
||||
|
||||
$empty_interval = new \DateInterval('PT0S');
|
||||
|
||||
$positive_19_hours = new \DateInterval('PT19H');
|
||||
|
||||
$positive_18_hours = new \DateInterval('PT18H');
|
||||
|
||||
$positive_1_hour = new \DateInterval('PT1H');
|
||||
|
||||
$negative_1_hour = new \DateInterval('PT1H');
|
||||
$negative_1_hour->invert = 1;
|
||||
|
||||
return [
|
||||
// There should be a 19 hour time interval between
|
||||
// new years in Sydney and new years in LA in year 2000.
|
||||
[
|
||||
'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
|
||||
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $positive_19_hours,
|
||||
],
|
||||
// In 1970 Sydney did not observe daylight savings time
|
||||
// So there is only a 18 hour time interval.
|
||||
[
|
||||
'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
|
||||
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $positive_18_hours,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600, new \DateTimeZone('America/Los_Angeles')),
|
||||
'input2' => DateTimePlus::createFromFormat('U', 0, new \DateTimeZone('UTC')),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => DateTimePlus::createFromFormat('U', 0),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => \DateTime::createFromFormat('U', 0),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => DateTimePlus::createFromFormat('U', 0),
|
||||
'absolute' => TRUE,
|
||||
'expected' => $positive_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => \DateTime::createFromFormat('U', 0),
|
||||
'absolute' => TRUE,
|
||||
'expected' => $positive_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 0),
|
||||
'input2' => DateTimePlus::createFromFormat('U', 0),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $empty_interval,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for date tests.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the input parameters for
|
||||
* DateTimePlusTest::testInvalidDateDiff().
|
||||
*
|
||||
* @see DateTimePlusTest::testInvalidDateDiff()
|
||||
*/
|
||||
public function providerTestInvalidDateDiff() {
|
||||
return [
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => '1970-01-01 00:00:00',
|
||||
'absolute' => FALSE,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('U', 3600),
|
||||
'input2' => NULL,
|
||||
'absolute' => FALSE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid values passed to constructor.
|
||||
*
|
||||
* @param string $time
|
||||
* A date/time string.
|
||||
* @param string[] $errors
|
||||
* An array of error messages.
|
||||
*
|
||||
* @covers ::__construct
|
||||
*
|
||||
* @dataProvider providerTestInvalidConstructor
|
||||
*/
|
||||
public function testInvalidConstructor($time, array $errors) {
|
||||
$date = new DateTimePlus($time);
|
||||
|
||||
$this->assertEquals(TRUE, $date->hasErrors());
|
||||
$this->assertEquals($errors, $date->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for testInvalidConstructor().
|
||||
*
|
||||
* @return array
|
||||
* An array of invalid date/time strings, and corresponding error messages.
|
||||
*/
|
||||
public function providerTestInvalidConstructor() {
|
||||
return [
|
||||
[
|
||||
'YYYY-MM-DD',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Unexpected character',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
[
|
||||
'2017-MM-DD',
|
||||
[
|
||||
'Unexpected character',
|
||||
'The timezone could not be found in the database',
|
||||
],
|
||||
],
|
||||
[
|
||||
'YYYY-03-DD',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Unexpected character',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
[
|
||||
'YYYY-MM-07',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Unexpected character',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
[
|
||||
'2017-13-55',
|
||||
[
|
||||
'Unexpected character',
|
||||
],
|
||||
],
|
||||
[
|
||||
'YYYY-MM-DD hh:mm:ss',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Unexpected character',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
[
|
||||
'2017-03-07 25:70:80',
|
||||
[
|
||||
'Unexpected character',
|
||||
'Double time specification',
|
||||
],
|
||||
],
|
||||
[
|
||||
'lorem ipsum dolor sit amet',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the $settings['validate_format'] parameter in ::createFromFormat().
|
||||
*/
|
||||
public function testValidateFormat() {
|
||||
// Check that an input that does not strictly follow the input format will
|
||||
// produce the desired date. In this case the year string '11' doesn't
|
||||
// precisely match the 'Y' formatter parameter, but PHP will parse it
|
||||
// regardless. However, when formatted with the same string, the year will
|
||||
// be output with four digits. With the ['validate_format' => FALSE]
|
||||
// $settings, this will not thrown an exception.
|
||||
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => FALSE]);
|
||||
$this->assertEquals('0011-03-31 17:44:00', $date->format('Y-m-d H:i:s'));
|
||||
|
||||
// Parse the same date with ['validate_format' => TRUE] and make sure we
|
||||
// get the expected exception.
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\UnexpectedValueException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\UnexpectedValueException::class);
|
||||
}
|
||||
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => TRUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting the default time for date-only objects.
|
||||
*/
|
||||
public function testDefaultDateTime() {
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
|
||||
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '2017-05-23 22:58:00', $utc);
|
||||
$this->assertEquals('22:58:00', $date->format('H:i:s'));
|
||||
$date->setDefaultDateTime();
|
||||
$this->assertEquals('12:00:00', $date->format('H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that object methods are chainable.
|
||||
*
|
||||
* @covers ::__call
|
||||
*/
|
||||
public function testChainable() {
|
||||
$date = new DateTimePlus('now', 'Australia/Sydney');
|
||||
|
||||
$date->setTimestamp(12345678);
|
||||
$rendered = $date->render();
|
||||
$this->assertEquals('1970-05-24 07:21:18 Australia/Sydney', $rendered);
|
||||
|
||||
$date->setTimestamp(23456789);
|
||||
$rendered = $date->setTimezone(new \DateTimeZone('America/New_York'))->render();
|
||||
$this->assertEquals('1970-09-29 07:46:29 America/New_York', $rendered);
|
||||
|
||||
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-05-24 07:21:18', new \DateTimeZone('Australia/Sydney'))
|
||||
->setTimezone(new \DateTimeZone('America/New_York'));
|
||||
$rendered = $date->render();
|
||||
$this->assertInstanceOf(DateTimePlus::class, $date);
|
||||
$this->assertEquals(12345678, $date->getTimestamp());
|
||||
$this->assertEquals('1970-05-23 17:21:18 America/New_York', $rendered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that non-chainable methods work.
|
||||
*
|
||||
* @covers ::__call
|
||||
*/
|
||||
public function testChainableNonChainable() {
|
||||
$datetime1 = new DateTimePlus('2009-10-11 12:00:00');
|
||||
$datetime2 = new DateTimePlus('2009-10-13 12:00:00');
|
||||
$interval = $datetime1->diff($datetime2);
|
||||
$this->assertInstanceOf(\DateInterval::class, $interval);
|
||||
$this->assertEquals('+2 days', $interval->format('%R%a days'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that chained calls to non-existent functions throw an exception.
|
||||
*
|
||||
* @covers ::__call
|
||||
*/
|
||||
public function testChainableNonCallable() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$this->expectExceptionMessage('Call to undefined method Drupal\Component\Datetime\DateTimePlus::nonexistent()');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\BadMethodCallException::class, 'Call to undefined method Drupal\Component\Datetime\DateTimePlus::nonexistent()');
|
||||
}
|
||||
$date = new DateTimePlus('now', 'Australia/Sydney');
|
||||
$date->setTimezone(new \DateTimeZone('America/New_York'))->nonexistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getPhpDateTime
|
||||
*/
|
||||
public function testGetPhpDateTime() {
|
||||
$new_york = new \DateTimeZone('America/New_York');
|
||||
$berlin = new \DateTimeZone('Europe/Berlin');
|
||||
|
||||
// Test retrieving a cloned copy of the wrapped \DateTime object, and that
|
||||
// altering it does not change the DateTimePlus object.
|
||||
$datetimeplus = DateTimePlus::createFromFormat('Y-m-d H:i:s', '2017-07-13 22:40:00', $new_york, ['langcode' => 'en']);
|
||||
$this->assertEquals(1500000000, $datetimeplus->getTimestamp());
|
||||
$this->assertEquals('America/New_York', $datetimeplus->getTimezone()->getName());
|
||||
|
||||
$datetime = $datetimeplus->getPhpDateTime();
|
||||
$this->assertInstanceOf('DateTime', $datetime);
|
||||
$this->assertEquals(1500000000, $datetime->getTimestamp());
|
||||
$this->assertEquals('America/New_York', $datetime->getTimezone()->getName());
|
||||
|
||||
$datetime->setTimestamp(1400000000)->setTimezone($berlin);
|
||||
$this->assertEquals(1400000000, $datetime->getTimestamp());
|
||||
$this->assertEquals('Europe/Berlin', $datetime->getTimezone()->getName());
|
||||
$this->assertEquals(1500000000, $datetimeplus->getTimestamp());
|
||||
$this->assertEquals('America/New_York', $datetimeplus->getTimezone()->getName());
|
||||
}
|
||||
|
||||
}
|
||||
122
2017/web/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
Normal file
122
2017/web/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Datetime;
|
||||
|
||||
use Drupal\Component\Datetime\Time;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Datetime\Time
|
||||
* @group Datetime
|
||||
*
|
||||
* Isolate the tests to prevent side effects from altering system time.
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TimeTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The mocked request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The mocked time class.
|
||||
*
|
||||
* @var \Drupal\Component\Datetime\Time
|
||||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
|
||||
$this->time = new Time($this->requestStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRequestTime method.
|
||||
*
|
||||
* @covers ::getRequestTime
|
||||
*/
|
||||
public function testGetRequestTime() {
|
||||
$expected = 12345678;
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$request->server->set('REQUEST_TIME', $expected);
|
||||
|
||||
// Mocks a the request stack getting the current request.
|
||||
$this->requestStack->expects($this->any())
|
||||
->method('getCurrentRequest')
|
||||
->willReturn($request);
|
||||
|
||||
$this->assertEquals($expected, $this->time->getRequestTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRequestMicroTime method.
|
||||
*
|
||||
* @covers ::getRequestMicroTime
|
||||
*/
|
||||
public function testGetRequestMicroTime() {
|
||||
$expected = 1234567.89;
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$request->server->set('REQUEST_TIME_FLOAT', $expected);
|
||||
|
||||
// Mocks a the request stack getting the current request.
|
||||
$this->requestStack->expects($this->any())
|
||||
->method('getCurrentRequest')
|
||||
->willReturn($request);
|
||||
|
||||
$this->assertEquals($expected, $this->time->getRequestMicroTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getCurrentTime method.
|
||||
*
|
||||
* @covers ::getCurrentTime
|
||||
*/
|
||||
public function testGetCurrentTime() {
|
||||
$expected = 12345678;
|
||||
$this->assertEquals($expected, $this->time->getCurrentTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getCurrentMicroTime method.
|
||||
*
|
||||
* @covers ::getCurrentMicroTime
|
||||
*/
|
||||
public function testGetCurrentMicroTime() {
|
||||
$expected = 1234567.89;
|
||||
$this->assertEquals($expected, $this->time->getCurrentMicroTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\Component\Datetime;
|
||||
|
||||
/**
|
||||
* Shadow time() system call.
|
||||
*
|
||||
* @returns int
|
||||
*/
|
||||
function time() {
|
||||
return 12345678;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shadow microtime system call.
|
||||
*
|
||||
* @returns float
|
||||
*/
|
||||
function microtime() {
|
||||
return 1234567.89;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,685 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\DependencyInjection\Dumper {
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\ExpressionLanguage\Expression;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
|
||||
* @group DependencyInjection
|
||||
*/
|
||||
class OptimizedPhpArrayDumperTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The container builder instance.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
|
||||
*/
|
||||
protected $containerBuilder;
|
||||
|
||||
/**
|
||||
* The definition for the container to build in tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $containerDefinition;
|
||||
|
||||
/**
|
||||
* Whether the dumper uses the machine-optimized format or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $machineFormat = TRUE;
|
||||
|
||||
/**
|
||||
* Stores the dumper class to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
|
||||
|
||||
/**
|
||||
* The dumper instance.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
|
||||
*/
|
||||
protected $dumper;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
// Setup a mock container builder.
|
||||
$this->containerBuilder = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerBuilder');
|
||||
$this->containerBuilder->getAliases()->willReturn([]);
|
||||
$this->containerBuilder->getParameterBag()->willReturn(new ParameterBag());
|
||||
$this->containerBuilder->getDefinitions()->willReturn(NULL);
|
||||
$this->containerBuilder->isCompiled()->willReturn(TRUE);
|
||||
|
||||
$definition = [];
|
||||
$definition['aliases'] = [];
|
||||
$definition['parameters'] = [];
|
||||
$definition['services'] = [];
|
||||
$definition['frozen'] = TRUE;
|
||||
$definition['machine_format'] = $this->machineFormat;
|
||||
|
||||
$this->containerDefinition = $definition;
|
||||
|
||||
// Create the dumper.
|
||||
$this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an empty container works properly.
|
||||
*
|
||||
* @covers ::dump
|
||||
* @covers ::getArray
|
||||
* @covers ::supportsMachineFormat
|
||||
*/
|
||||
public function testDumpForEmptyContainer() {
|
||||
$serialized_definition = $this->dumper->dump();
|
||||
$this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that alias processing works properly.
|
||||
*
|
||||
* @covers ::getAliases
|
||||
*
|
||||
* @dataProvider getAliasesDataProvider
|
||||
*/
|
||||
public function testGetAliases($aliases, $definition_aliases) {
|
||||
$this->containerDefinition['aliases'] = $definition_aliases;
|
||||
$this->containerBuilder->getAliases()->willReturn($aliases);
|
||||
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetAliases().
|
||||
*
|
||||
* @return array[]
|
||||
* Returns data-set elements with:
|
||||
* - aliases as returned by ContainerBuilder.
|
||||
* - aliases as expected in the container definition.
|
||||
*/
|
||||
public function getAliasesDataProvider() {
|
||||
return [
|
||||
[[], []],
|
||||
[
|
||||
['foo' => 'foo.alias'],
|
||||
['foo' => 'foo.alias'],
|
||||
],
|
||||
[
|
||||
['foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'],
|
||||
['foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that parameter processing works properly.
|
||||
*
|
||||
* @covers ::getParameters
|
||||
* @covers ::prepareParameters
|
||||
* @covers ::escape
|
||||
* @covers ::dumpValue
|
||||
* @covers ::getReferenceCall
|
||||
*
|
||||
* @dataProvider getParametersDataProvider
|
||||
*/
|
||||
public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
|
||||
$this->containerDefinition['parameters'] = $definition_parameters;
|
||||
$this->containerDefinition['frozen'] = $is_frozen;
|
||||
|
||||
$parameter_bag = new ParameterBag($parameters);
|
||||
$this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
|
||||
$this->containerBuilder->isCompiled()->willReturn($is_frozen);
|
||||
|
||||
if (isset($parameters['reference'])) {
|
||||
$definition = new Definition('\stdClass');
|
||||
$this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
|
||||
}
|
||||
|
||||
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetParameters().
|
||||
*
|
||||
* @return array[]
|
||||
* Returns data-set elements with:
|
||||
* - parameters as returned by ContainerBuilder.
|
||||
* - parameters as expected in the container definition.
|
||||
* - frozen value
|
||||
*/
|
||||
public function getParametersDataProvider() {
|
||||
return [
|
||||
[[], [], TRUE],
|
||||
[
|
||||
['foo' => 'value_foo'],
|
||||
['foo' => 'value_foo'],
|
||||
TRUE,
|
||||
],
|
||||
[
|
||||
['foo' => ['llama' => 'yes']],
|
||||
['foo' => ['llama' => 'yes']],
|
||||
TRUE,
|
||||
],
|
||||
[
|
||||
['foo' => '%llama%', 'llama' => 'yes'],
|
||||
['foo' => '%%llama%%', 'llama' => 'yes'],
|
||||
TRUE,
|
||||
],
|
||||
[
|
||||
['foo' => '%llama%', 'llama' => 'yes'],
|
||||
['foo' => '%llama%', 'llama' => 'yes'],
|
||||
FALSE,
|
||||
],
|
||||
[
|
||||
['reference' => new Reference('referenced_service')],
|
||||
['reference' => $this->getServiceCall('referenced_service')],
|
||||
TRUE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that service processing works properly.
|
||||
*
|
||||
* @covers ::getServiceDefinitions
|
||||
* @covers ::getServiceDefinition
|
||||
* @covers ::dumpMethodCalls
|
||||
* @covers ::dumpCollection
|
||||
* @covers ::dumpCallable
|
||||
* @covers ::dumpValue
|
||||
* @covers ::getPrivateServiceCall
|
||||
* @covers ::getReferenceCall
|
||||
* @covers ::getServiceCall
|
||||
* @covers ::getParameterCall
|
||||
*
|
||||
* @dataProvider getDefinitionsDataProvider
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testGetServiceDefinitions($services, $definition_services) {
|
||||
$this->containerDefinition['services'] = $definition_services;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
|
||||
|
||||
$private_definition = new Definition('\stdClass');
|
||||
$private_definition->setPublic(FALSE);
|
||||
|
||||
$this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
|
||||
|
||||
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetServiceDefinitions().
|
||||
*
|
||||
* @return array[]
|
||||
* Returns data-set elements with:
|
||||
* - parameters as returned by ContainerBuilder.
|
||||
* - parameters as expected in the container definition.
|
||||
* - frozen value
|
||||
*/
|
||||
public function getDefinitionsDataProvider() {
|
||||
$base_service_definition = [
|
||||
'class' => '\stdClass',
|
||||
'public' => TRUE,
|
||||
'file' => FALSE,
|
||||
'synthetic' => FALSE,
|
||||
'lazy' => FALSE,
|
||||
'arguments' => [],
|
||||
'arguments_count' => 0,
|
||||
'properties' => [],
|
||||
'calls' => [],
|
||||
'shared' => TRUE,
|
||||
'factory' => FALSE,
|
||||
'configurator' => FALSE,
|
||||
];
|
||||
|
||||
// Test basic flags.
|
||||
$service_definitions[] = [] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'public' => FALSE,
|
||||
] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'file' => 'test_include.php',
|
||||
] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'synthetic' => TRUE,
|
||||
] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'shared' => FALSE,
|
||||
] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'lazy' => TRUE,
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a basic public Reference.
|
||||
$service_definitions[] = [
|
||||
'arguments' => ['foo', new Reference('bar')],
|
||||
'arguments_count' => 2,
|
||||
'arguments_expected' => $this->getCollection(['foo', $this->getServiceCall('bar')]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a public reference that should not throw an Exception.
|
||||
$reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
|
||||
$service_definitions[] = [
|
||||
'arguments' => [$reference],
|
||||
'arguments_count' => 1,
|
||||
'arguments_expected' => $this->getCollection([$this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a private shared service, denoted by having a Reference.
|
||||
$private_definition = [
|
||||
'class' => '\stdClass',
|
||||
'public' => FALSE,
|
||||
'arguments_count' => 0,
|
||||
];
|
||||
|
||||
$service_definitions[] = [
|
||||
'arguments' => ['foo', new Reference('private_definition')],
|
||||
'arguments_count' => 2,
|
||||
'arguments_expected' => $this->getCollection([
|
||||
'foo',
|
||||
$this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
|
||||
]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a private non-shared service, denoted by having a Definition.
|
||||
$private_definition_object = new Definition('\stdClass');
|
||||
$private_definition_object->setPublic(FALSE);
|
||||
|
||||
$service_definitions[] = [
|
||||
'arguments' => ['foo', $private_definition_object],
|
||||
'arguments_count' => 2,
|
||||
'arguments_expected' => $this->getCollection([
|
||||
'foo',
|
||||
$this->getPrivateServiceCall(NULL, $private_definition),
|
||||
]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a deep collection without a reference.
|
||||
$service_definitions[] = [
|
||||
'arguments' => [[['foo']]],
|
||||
'arguments_count' => 1,
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a deep collection with a reference to resolve.
|
||||
$service_definitions[] = [
|
||||
'arguments' => [[new Reference('bar')]],
|
||||
'arguments_count' => 1,
|
||||
'arguments_expected' => $this->getCollection([$this->getCollection([$this->getServiceCall('bar')])]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a collection with a variable to resolve.
|
||||
$service_definitions[] = [
|
||||
'arguments' => [new Parameter('llama_parameter')],
|
||||
'arguments_count' => 1,
|
||||
'arguments_expected' => $this->getCollection([$this->getParameterCall('llama_parameter')]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test objects that have _serviceId property.
|
||||
$drupal_service = new \stdClass();
|
||||
$drupal_service->_serviceId = 'bar';
|
||||
|
||||
$service_definitions[] = [
|
||||
'arguments' => [$drupal_service],
|
||||
'arguments_count' => 1,
|
||||
'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test getMethodCalls.
|
||||
$calls = [
|
||||
['method', $this->getCollection([])],
|
||||
['method2', $this->getCollection([])],
|
||||
];
|
||||
$service_definitions[] = [
|
||||
'calls' => $calls,
|
||||
] + $base_service_definition;
|
||||
|
||||
$service_definitions[] = [
|
||||
'shared' => FALSE,
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test factory.
|
||||
$service_definitions[] = [
|
||||
'factory' => [new Reference('bar'), 'factoryMethod'],
|
||||
'factory_expected' => [$this->getServiceCall('bar'), 'factoryMethod'],
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test invalid factory - needed to test deep dumpValue().
|
||||
$service_definitions[] = [
|
||||
'factory' => [['foo', 'llama'], 'factoryMethod'],
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test properties.
|
||||
$service_definitions[] = [
|
||||
'properties' => ['_value' => 'llama'],
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test configurator.
|
||||
$service_definitions[] = [
|
||||
'configurator' => [new Reference('bar'), 'configureService'],
|
||||
'configurator_expected' => [$this->getServiceCall('bar'), 'configureService'],
|
||||
] + $base_service_definition;
|
||||
|
||||
$services_provided = [];
|
||||
$services_provided[] = [
|
||||
[],
|
||||
[],
|
||||
];
|
||||
|
||||
foreach ($service_definitions as $service_definition) {
|
||||
$definition = $this->prophesize('\Symfony\Component\DependencyInjection\Definition');
|
||||
$definition->getClass()->willReturn($service_definition['class']);
|
||||
$definition->isPublic()->willReturn($service_definition['public']);
|
||||
$definition->getFile()->willReturn($service_definition['file']);
|
||||
$definition->isSynthetic()->willReturn($service_definition['synthetic']);
|
||||
$definition->isLazy()->willReturn($service_definition['lazy']);
|
||||
$definition->getArguments()->willReturn($service_definition['arguments']);
|
||||
$definition->getProperties()->willReturn($service_definition['properties']);
|
||||
$definition->getMethodCalls()->willReturn($service_definition['calls']);
|
||||
$definition->isShared()->willReturn($service_definition['shared']);
|
||||
$definition->getDecoratedService()->willReturn(NULL);
|
||||
$definition->getFactory()->willReturn($service_definition['factory']);
|
||||
$definition->getConfigurator()->willReturn($service_definition['configurator']);
|
||||
|
||||
// Preserve order.
|
||||
$filtered_service_definition = [];
|
||||
foreach ($base_service_definition as $key => $value) {
|
||||
$filtered_service_definition[$key] = $service_definition[$key];
|
||||
unset($service_definition[$key]);
|
||||
|
||||
if ($key == 'class' || $key == 'arguments_count') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
|
||||
unset($filtered_service_definition[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining properties.
|
||||
$filtered_service_definition += $service_definition;
|
||||
|
||||
// Allow to set _expected values.
|
||||
foreach (['arguments', 'factory', 'configurator'] as $key) {
|
||||
$expected = $key . '_expected';
|
||||
if (isset($filtered_service_definition[$expected])) {
|
||||
$filtered_service_definition[$key] = $filtered_service_definition[$expected];
|
||||
unset($filtered_service_definition[$expected]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
|
||||
$services_provided[] = [
|
||||
['foo_service' => $definition->reveal()],
|
||||
[],
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$services_provided[] = [
|
||||
['foo_service' => $definition->reveal()],
|
||||
['foo_service' => $this->serializeDefinition($filtered_service_definition)],
|
||||
];
|
||||
}
|
||||
|
||||
return $services_provided;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to serialize a definition.
|
||||
*
|
||||
* Used to override serialization.
|
||||
*/
|
||||
protected function serializeDefinition(array $service_definition) {
|
||||
return serialize($service_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a service definition.
|
||||
*/
|
||||
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return (object) [
|
||||
'type' => 'service',
|
||||
'id' => $id,
|
||||
'invalidBehavior' => $invalid_behavior,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that references to aliases work correctly.
|
||||
*
|
||||
* @covers ::getReferenceCall
|
||||
*
|
||||
* @dataProvider publicPrivateDataProvider
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testGetServiceDefinitionWithReferenceToAlias($public) {
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition_php_array = [
|
||||
'class' => '\stdClass',
|
||||
];
|
||||
if (!$public) {
|
||||
$bar_definition->setPublic(FALSE);
|
||||
$bar_definition_php_array['public'] = FALSE;
|
||||
}
|
||||
$bar_definition_php_array['arguments_count'] = 0;
|
||||
|
||||
$services['bar'] = $bar_definition;
|
||||
|
||||
$aliases['bar.alias'] = 'bar';
|
||||
|
||||
$foo = new Definition('\stdClass');
|
||||
$foo->addArgument(new Reference('bar.alias'));
|
||||
|
||||
$services['foo'] = $foo;
|
||||
|
||||
$this->containerBuilder->getAliases()->willReturn($aliases);
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
$this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
|
||||
$dump = $this->dumper->getArray();
|
||||
if ($public) {
|
||||
$service_definition = $this->getServiceCall('bar');
|
||||
}
|
||||
else {
|
||||
$service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
|
||||
}
|
||||
$data = [
|
||||
'class' => '\stdClass',
|
||||
'arguments' => $this->getCollection([
|
||||
$service_definition,
|
||||
]),
|
||||
'arguments_count' => 1,
|
||||
];
|
||||
$this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
|
||||
}
|
||||
|
||||
public function publicPrivateDataProvider() {
|
||||
return [
|
||||
[TRUE],
|
||||
[FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getDecoratedService() is unsupported.
|
||||
*
|
||||
* Tests that the correct InvalidArgumentException is thrown for
|
||||
* getDecoratedService().
|
||||
*
|
||||
* @covers ::getServiceDefinition
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testGetServiceDefinitionForDecoratedService() {
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setDecoratedService(new Reference('foo'));
|
||||
$services['bar'] = $bar_definition;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
}
|
||||
$this->dumper->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the correct RuntimeException is thrown for expressions.
|
||||
*
|
||||
* @covers ::dumpValue
|
||||
*/
|
||||
public function testGetServiceDefinitionForExpression() {
|
||||
$expression = new Expression();
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->addArgument($expression);
|
||||
$services['bar'] = $bar_definition;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(RuntimeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(RuntimeException::class);
|
||||
}
|
||||
$this->dumper->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the correct RuntimeException is thrown for dumping an object.
|
||||
*
|
||||
* @covers ::dumpValue
|
||||
*/
|
||||
public function testGetServiceDefinitionForObject() {
|
||||
$service = new \stdClass();
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->addArgument($service);
|
||||
$services['bar'] = $bar_definition;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(RuntimeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(RuntimeException::class);
|
||||
}
|
||||
$this->dumper->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the correct RuntimeException is thrown for dumping a resource.
|
||||
*
|
||||
* @covers ::dumpValue
|
||||
*/
|
||||
public function testGetServiceDefinitionForResource() {
|
||||
$resource = fopen('php://memory', 'r');
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->addArgument($resource);
|
||||
$services['bar'] = $bar_definition;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(RuntimeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(RuntimeException::class);
|
||||
}
|
||||
$this->dumper->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a private service definition.
|
||||
*/
|
||||
protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
|
||||
if (!$id) {
|
||||
$hash = Crypt::hashBase64(serialize($service_definition));
|
||||
$id = 'private__' . $hash;
|
||||
}
|
||||
return (object) [
|
||||
'type' => 'private_service',
|
||||
'id' => $id,
|
||||
'value' => $service_definition,
|
||||
'shared' => $shared,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a machine-optimized collection.
|
||||
*/
|
||||
protected function getCollection($collection, $resolve = TRUE) {
|
||||
return (object) [
|
||||
'type' => 'collection',
|
||||
'value' => $collection,
|
||||
'resolve' => $resolve,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a parameter definition.
|
||||
*/
|
||||
protected function getParameterCall($name) {
|
||||
return (object) [
|
||||
'type' => 'parameter',
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* As Drupal Core does not ship with ExpressionLanguage component we need to
|
||||
* define a dummy, else it cannot be tested.
|
||||
*/
|
||||
namespace Symfony\Component\ExpressionLanguage {
|
||||
|
||||
if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
|
||||
/**
|
||||
* Dummy class to ensure non-existent Symfony component can be tested.
|
||||
*/
|
||||
class Expression {
|
||||
|
||||
/**
|
||||
* Gets the string representation of the expression.
|
||||
*/
|
||||
public function __toString() {
|
||||
return 'dummy_expression';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\DependencyInjection\Dumper;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
|
||||
* @group DependencyInjection
|
||||
*/
|
||||
class PhpArrayDumperTest extends OptimizedPhpArrayDumperTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->machineFormat = FALSE;
|
||||
$this->dumperClass = '\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper';
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function serializeDefinition(array $service_definition) {
|
||||
return $service_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return sprintf('@?%s', $id);
|
||||
}
|
||||
|
||||
return sprintf('@%s', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getParameterCall($name) {
|
||||
return '%' . $name . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCollection($collection, $resolve = TRUE) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains a test function for container 'file' include testing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test function for container testing.
|
||||
*
|
||||
* @return string
|
||||
* A string just for testing.
|
||||
*/
|
||||
function container_test_file_service_test_service_function() {
|
||||
return 'Hello Container';
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\DependencyInjection\PhpArrayContainer
|
||||
* @group DependencyInjection
|
||||
*/
|
||||
class PhpArrayContainerTest extends ContainerTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->machineFormat = FALSE;
|
||||
$this->containerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer';
|
||||
$this->containerDefinition = $this->getMockContainerDefinition();
|
||||
$this->container = new $this->containerClass($this->containerDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a service definition.
|
||||
*/
|
||||
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return sprintf('@?%s', $id);
|
||||
}
|
||||
|
||||
return sprintf('@%s', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a service definition.
|
||||
*/
|
||||
protected function getParameterCall($name) {
|
||||
return '%' . $name . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a machine-optimized '@notation'.
|
||||
*/
|
||||
protected function getCollection($collection, $resolve = TRUE) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Diff;
|
||||
|
||||
use Drupal\Component\Diff\Diff;
|
||||
use Drupal\Component\Diff\DiffFormatter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test DiffFormatter classes.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Diff\DiffFormatter
|
||||
*
|
||||
* @group Diff
|
||||
*/
|
||||
class DiffFormatterTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* - Expected formatted diff output.
|
||||
* - First array of text to diff.
|
||||
* - Second array of text to diff.
|
||||
*/
|
||||
public function provideTestDiff() {
|
||||
return [
|
||||
'empty' => ['', [], []],
|
||||
'add' => [
|
||||
"3a3\n> line2a\n",
|
||||
['line1', 'line2', 'line3'],
|
||||
['line1', 'line2', 'line2a', 'line3'],
|
||||
],
|
||||
'delete' => [
|
||||
"3d3\n< line2a\n",
|
||||
['line1', 'line2', 'line2a', 'line3'],
|
||||
['line1', 'line2', 'line3'],
|
||||
],
|
||||
'change' => [
|
||||
"3c3\n< line2a\n---\n> line2b\n",
|
||||
['line1', 'line2', 'line2a', 'line3'],
|
||||
['line1', 'line2', 'line2b', 'line3'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether op classes returned by DiffEngine::diff() match expectations.
|
||||
*
|
||||
* @covers ::format
|
||||
* @dataProvider provideTestDiff
|
||||
*/
|
||||
public function testDiff($expected, $from, $to) {
|
||||
$diff = new Diff($from, $to);
|
||||
$formatter = new DiffFormatter();
|
||||
$output = $formatter->format($diff);
|
||||
$this->assertEquals($expected, $output);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Diff\Engine;
|
||||
|
||||
use Drupal\Component\Diff\Engine\DiffEngine;
|
||||
use Drupal\Component\Diff\Engine\DiffOpAdd;
|
||||
use Drupal\Component\Diff\Engine\DiffOpCopy;
|
||||
use Drupal\Component\Diff\Engine\DiffOpChange;
|
||||
use Drupal\Component\Diff\Engine\DiffOpDelete;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test DiffEngine class.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Diff\Engine\DiffEngine
|
||||
*
|
||||
* @group Diff
|
||||
*/
|
||||
class DiffEngineTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* - Expected output in terms of return class. A list of class names
|
||||
* expected to be returned by DiffEngine::diff().
|
||||
* - An array of strings to change from.
|
||||
* - An array of strings to change to.
|
||||
*/
|
||||
public function provideTestDiff() {
|
||||
return [
|
||||
'empty' => [[], [], []],
|
||||
'add' => [[DiffOpAdd::class], [], ['a']],
|
||||
'copy' => [[DiffOpCopy::class], ['a'], ['a']],
|
||||
'change' => [[DiffOpChange::class], ['a'], ['b']],
|
||||
'copy-and-change' => [
|
||||
[
|
||||
DiffOpCopy::class,
|
||||
DiffOpChange::class,
|
||||
],
|
||||
['a', 'b'],
|
||||
['a', 'c'],
|
||||
],
|
||||
'copy-change-copy' => [
|
||||
[
|
||||
DiffOpCopy::class,
|
||||
DiffOpChange::class,
|
||||
DiffOpCopy::class,
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a', 'c', 'd'],
|
||||
],
|
||||
'copy-change-copy-add' => [
|
||||
[
|
||||
DiffOpCopy::class,
|
||||
DiffOpChange::class,
|
||||
DiffOpCopy::class,
|
||||
DiffOpAdd::class,
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a', 'c', 'd', 'e'],
|
||||
],
|
||||
'copy-delete' => [
|
||||
[
|
||||
DiffOpCopy::class,
|
||||
DiffOpDelete::class,
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether op classes returned by DiffEngine::diff() match expectations.
|
||||
*
|
||||
* @covers ::diff
|
||||
* @dataProvider provideTestDiff
|
||||
*/
|
||||
public function testDiff($expected, $from, $to) {
|
||||
$diff_engine = new DiffEngine();
|
||||
$diff = $diff_engine->diff($from, $to);
|
||||
// Make sure we have the same number of results as expected.
|
||||
$this->assertCount(count($expected), $diff);
|
||||
// Make sure the diff objects match our expectations.
|
||||
foreach ($expected as $index => $op_class) {
|
||||
$this->assertEquals($op_class, get_class($diff[$index]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that two files can be successfully diffed.
|
||||
*
|
||||
* @covers ::diff
|
||||
*/
|
||||
public function testDiffInfiniteLoop() {
|
||||
$from = explode("\n", file_get_contents(__DIR__ . '/fixtures/file1.txt'));
|
||||
$to = explode("\n", file_get_contents(__DIR__ . '/fixtures/file2.txt'));
|
||||
$diff_engine = new DiffEngine();
|
||||
$diff = $diff_engine->diff($from, $to);
|
||||
$this->assertCount(4, $diff);
|
||||
$this->assertEquals($diff[0], new DiffOpDelete([' - image.style.max_650x650']));
|
||||
$this->assertEquals($diff[1], new DiffOpCopy([' - image.style.max_325x325']));
|
||||
$this->assertEquals($diff[2], new DiffOpAdd([' - image.style.max_650x650', '_core:', ' default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM']));
|
||||
$this->assertEquals($diff[3], new DiffOpCopy(['fallback_image_style: max_325x325', '']));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Diff\Engine;
|
||||
|
||||
use Drupal\Component\Diff\Engine\DiffOp;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Error\Error;
|
||||
|
||||
/**
|
||||
* Test DiffOp base class.
|
||||
*
|
||||
* The only significant behavior here is that ::reverse() should throw an error
|
||||
* if not overridden. In versions of this code in other projects, reverse() is
|
||||
* marked as abstract, which enforces some of this behavior.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Diff\Engine\DiffOp
|
||||
*
|
||||
* @group Diff
|
||||
*/
|
||||
class DiffOpTest extends TestCase {
|
||||
|
||||
/**
|
||||
* DiffOp::reverse() always throws an error.
|
||||
*
|
||||
* @covers ::reverse
|
||||
*/
|
||||
public function testReverse() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(Error::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\PHPUnit_Framework_Error::class);
|
||||
}
|
||||
$op = new DiffOp();
|
||||
$result = $op->reverse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Diff\Engine;
|
||||
|
||||
use Drupal\Component\Diff\Engine\HWLDFWordAccumulator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test HWLDFWordAccumulator.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Diff\Engine\HWLDFWordAccumulator
|
||||
*
|
||||
* @group Diff
|
||||
*/
|
||||
class HWLDFWordAccumulatorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Verify that we only get back a NBSP from an empty accumulator.
|
||||
*
|
||||
* @covers ::getLines
|
||||
*
|
||||
* @see Drupal\Component\Diff\Engine\HWLDFWordAccumulator::NBSP
|
||||
*/
|
||||
public function testGetLinesEmpty() {
|
||||
$acc = new HWLDFWordAccumulator();
|
||||
$this->assertEquals([' '], $acc->getLines());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* - Expected array of lines from getLines().
|
||||
* - Array of strings for the $words parameter to addWords().
|
||||
* - String tag for the $tag parameter to addWords().
|
||||
*/
|
||||
public function provideAddWords() {
|
||||
return [
|
||||
[['wordword2'], ['word', 'word2'], 'tag'],
|
||||
[['word', 'word2'], ['word', "\nword2"], 'tag'],
|
||||
[[' ', 'word2'], ['', "\nword2"], 'tag'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addWords
|
||||
* @dataProvider provideAddWords
|
||||
*/
|
||||
public function testAddWords($expected, $words, $tag) {
|
||||
$acc = new HWLDFWordAccumulator();
|
||||
$acc->addWords($words, $tag);
|
||||
$this->assertEquals($expected, $acc->getLines());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
- image.style.max_650x650
|
||||
- image.style.max_325x325
|
||||
fallback_image_style: max_325x325
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
- image.style.max_325x325
|
||||
- image.style.max_650x650
|
||||
_core:
|
||||
default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM
|
||||
fallback_image_style: max_325x325
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Discovery;
|
||||
|
||||
use Drupal\Component\Discovery\DiscoveryException;
|
||||
use Drupal\Component\Discovery\YamlDirectoryDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* YamlDirectoryDiscoveryTest component unit tests.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Discovery\YamlDirectoryDiscovery
|
||||
*
|
||||
* @group Discovery
|
||||
*/
|
||||
class YamlDirectoryDiscoveryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML directory discovery.
|
||||
*
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscovery() {
|
||||
vfsStream::setup('modules', NULL, [
|
||||
'test_1' => [
|
||||
'subdir1' => [
|
||||
'item_1.test.yml' => "id: item1\nname: 'test1 item 1'",
|
||||
],
|
||||
'subdir2' => [
|
||||
'item_2.test.yml' => "id: item2\nname: 'test1 item 2'",
|
||||
],
|
||||
],
|
||||
'test_2' => [
|
||||
'subdir1' => [
|
||||
'item_3.test.yml' => "id: item3\nname: 'test2 item 3'",
|
||||
],
|
||||
'subdir2' => [],
|
||||
],
|
||||
'test_3' => [],
|
||||
'test_4' => [
|
||||
'subdir1' => [
|
||||
'item_4.test.yml' => "id: item4\nname: 'test4 item 4'",
|
||||
'item_5.test.yml' => "id: item5\nname: 'test4 item 5'",
|
||||
'item_6.test.yml' => "id: item6\nname: 'test4 item 6'",
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Set up the directories to search.
|
||||
$directories = [
|
||||
// Multiple directories both with valid items.
|
||||
'test_1' => [
|
||||
vfsStream::url('modules/test_1/subdir1'),
|
||||
vfsStream::url('modules/test_1/subdir2'),
|
||||
],
|
||||
// The subdir2 directory is empty.
|
||||
'test_2' => [
|
||||
vfsStream::url('modules/test_2/subdir1'),
|
||||
vfsStream::url('modules/test_2/subdir2'),
|
||||
],
|
||||
// Directories that do not exist.
|
||||
'test_3' => [
|
||||
vfsStream::url('modules/test_3/subdir1'),
|
||||
vfsStream::url('modules/test_3/subdir2'),
|
||||
],
|
||||
// A single directory.
|
||||
'test_4' => vfsStream::url('modules/test_4/subdir1'),
|
||||
];
|
||||
|
||||
$discovery = new YamlDirectoryDiscovery($directories, 'test');
|
||||
$data = $discovery->findAll();
|
||||
|
||||
$this->assertSame(['id' => 'item1', 'name' => 'test1 item 1', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir1/item_1.test.yml'], $data['test_1']['item1']);
|
||||
$this->assertSame(['id' => 'item2', 'name' => 'test1 item 2', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir2/item_2.test.yml'], $data['test_1']['item2']);
|
||||
$this->assertCount(2, $data['test_1']);
|
||||
|
||||
$this->assertSame(['id' => 'item3', 'name' => 'test2 item 3', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_2/subdir1/item_3.test.yml'], $data['test_2']['item3']);
|
||||
$this->assertCount(1, $data['test_2']);
|
||||
|
||||
$this->assertTrue(empty($data['test_3']), 'test_3 provides 0 items');
|
||||
|
||||
$this->assertSame(['id' => 'item4', 'name' => 'test4 item 4', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_4.test.yml'], $data['test_4']['item4']);
|
||||
$this->assertSame(['id' => 'item5', 'name' => 'test4 item 5', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_5.test.yml'], $data['test_4']['item5']);
|
||||
$this->assertSame(['id' => 'item6', 'name' => 'test4 item 6', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_6.test.yml'], $data['test_4']['item6']);
|
||||
$this->assertCount(3, $data['test_4']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML directory discovery with an alternate ID key.
|
||||
*
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryAlternateId() {
|
||||
vfsStream::setup('modules', NULL, [
|
||||
'test_1' => [
|
||||
'item_1.test.yml' => "alt_id: item1\nid: ignored",
|
||||
],
|
||||
]);
|
||||
|
||||
// Set up the directories to search.
|
||||
$directories = ['test_1' => vfsStream::url('modules/test_1')];
|
||||
|
||||
$discovery = new YamlDirectoryDiscovery($directories, 'test', 'alt_id');
|
||||
$data = $discovery->findAll();
|
||||
|
||||
$this->assertSame(['alt_id' => 'item1', 'id' => 'ignored', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/item_1.test.yml'], $data['test_1']['item1']);
|
||||
$this->assertCount(1, $data['test_1']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML directory discovery with a missing ID key.
|
||||
*
|
||||
* @covers ::findAll
|
||||
* @covers ::getIdentifier
|
||||
*/
|
||||
public function testDiscoveryNoIdException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('The vfs://modules/test_1/item_1.test.yml contains no data in the identifier key \'id\'');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(DiscoveryException::class, 'The vfs://modules/test_1/item_1.test.yml contains no data in the identifier key \'id\'');
|
||||
}
|
||||
vfsStream::setup('modules', NULL, [
|
||||
'test_1' => [
|
||||
'item_1.test.yml' => "",
|
||||
],
|
||||
]);
|
||||
|
||||
// Set up the directories to search.
|
||||
$directories = ['test_1' => vfsStream::url('modules/test_1')];
|
||||
|
||||
$discovery = new YamlDirectoryDiscovery($directories, 'test');
|
||||
$discovery->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML directory discovery with invalid YAML.
|
||||
*
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryInvalidYamlException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('The vfs://modules/test_1/item_1.test.yml contains invalid YAML');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(DiscoveryException::class, 'The vfs://modules/test_1/item_1.test.yml contains invalid YAML');
|
||||
}
|
||||
vfsStream::setup('modules', NULL, [
|
||||
'test_1' => [
|
||||
'item_1.test.yml' => "id: invalid\nfoo : [bar}",
|
||||
],
|
||||
]);
|
||||
|
||||
// Set up the directories to search.
|
||||
$directories = ['test_1' => vfsStream::url('modules/test_1')];
|
||||
|
||||
$discovery = new YamlDirectoryDiscovery($directories, 'test');
|
||||
$discovery->findAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Discovery;
|
||||
|
||||
use Drupal\Component\Discovery\YamlDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use org\bovigo\vfs\vfsStreamWrapper;
|
||||
use org\bovigo\vfs\vfsStreamDirectory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* YamlDiscovery component unit tests.
|
||||
*
|
||||
* @group Discovery
|
||||
*/
|
||||
class YamlDiscoveryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the YAML file discovery.
|
||||
*/
|
||||
public function testDiscovery() {
|
||||
vfsStreamWrapper::register();
|
||||
$root = new vfsStreamDirectory('modules');
|
||||
vfsStreamWrapper::setRoot($root);
|
||||
$url = vfsStream::url('modules');
|
||||
|
||||
mkdir($url . '/test_1');
|
||||
file_put_contents($url . '/test_1/test_1.test.yml', 'name: test');
|
||||
file_put_contents($url . '/test_1/test_2.test.yml', 'name: test');
|
||||
|
||||
mkdir($url . '/test_2');
|
||||
file_put_contents($url . '/test_2/test_3.test.yml', 'name: test');
|
||||
// Write an empty YAML file.
|
||||
file_put_contents($url . '/test_2/test_4.test.yml', '');
|
||||
|
||||
// Set up the directories to search.
|
||||
$directories = [
|
||||
'test_1' => $url . '/test_1',
|
||||
'test_2' => $url . '/test_1',
|
||||
'test_3' => $url . '/test_2',
|
||||
'test_4' => $url . '/test_2',
|
||||
];
|
||||
|
||||
$discovery = new YamlDiscovery('test', $directories);
|
||||
$data = $discovery->findAll();
|
||||
|
||||
$this->assertEquals(count($data), count($directories));
|
||||
$this->assertArrayHasKey('test_1', $data);
|
||||
$this->assertArrayHasKey('test_2', $data);
|
||||
$this->assertArrayHasKey('test_3', $data);
|
||||
$this->assertArrayHasKey('test_4', $data);
|
||||
|
||||
foreach (['test_1', 'test_2', 'test_3'] as $key) {
|
||||
$this->assertArrayHasKey('name', $data[$key]);
|
||||
$this->assertEquals($data[$key]['name'], 'test');
|
||||
}
|
||||
|
||||
$this->assertSame([], $data['test_4']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component;
|
||||
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* General tests for \Drupal\Component that can't go anywhere else.
|
||||
*
|
||||
* @group Component
|
||||
*/
|
||||
class DrupalComponentTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests that classes in Component do not use any Core class.
|
||||
*/
|
||||
public function testNoCoreInComponent() {
|
||||
$component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/lib/Drupal/Component';
|
||||
foreach ($this->findPhpClasses($component_path) as $class) {
|
||||
$this->assertNoCoreUsage($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that classes in Component Tests do not use any Core class.
|
||||
*/
|
||||
public function testNoCoreInComponentTests() {
|
||||
$component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/tests/Drupal/Tests/Component';
|
||||
foreach ($this->findPhpClasses($component_path) as $class) {
|
||||
$this->assertNoCoreUsage($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests LICENSE.txt is present and has the correct content.
|
||||
*
|
||||
* @param $component_path
|
||||
* The path to the component.
|
||||
* @dataProvider \Drupal\Tests\Component\DrupalComponentTest::getComponents
|
||||
*/
|
||||
public function testComponentLicence($component_path) {
|
||||
$this->assertFileExists($component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt');
|
||||
$this->assertSame('e84dac1d9fbb5a4a69e38654ce644cea769aa76b', hash_file('sha1', $component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getComponents() {
|
||||
$root_component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/lib/Drupal/Component';
|
||||
$component_paths = [];
|
||||
foreach (new \DirectoryIterator($root_component_path) as $file) {
|
||||
if ($file->isDir() && !$file->isDot()) {
|
||||
$component_paths[$file->getBasename()] = [$file->getPathname()];
|
||||
}
|
||||
}
|
||||
return $component_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a directory recursively for PHP classes.
|
||||
*
|
||||
* @param string $dir
|
||||
* The full path to the directory that should be checked.
|
||||
*
|
||||
* @return array
|
||||
* An array of class paths.
|
||||
*/
|
||||
protected function findPhpClasses($dir) {
|
||||
$classes = [];
|
||||
foreach (new \DirectoryIterator($dir) as $file) {
|
||||
if ($file->isDir() && !$file->isDot()) {
|
||||
$classes = array_merge($classes, $this->findPhpClasses($file->getPathname()));
|
||||
}
|
||||
elseif ($file->getExtension() == 'php') {
|
||||
$classes[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given class is not using any class from Core namespace.
|
||||
*
|
||||
* @param string $class_path
|
||||
* The full path to the class that should be checked.
|
||||
*/
|
||||
protected function assertNoCoreUsage($class_path) {
|
||||
$contents = file_get_contents($class_path);
|
||||
preg_match_all('/^.*Drupal\\\Core.*$/m', $contents, $matches);
|
||||
$matches = array_filter($matches[0], function ($line) {
|
||||
// Filter references to @see as they don't really matter.
|
||||
return strpos($line, '@see') === FALSE;
|
||||
});
|
||||
$this->assertEmpty($matches, "Checking for illegal reference to 'Drupal\\Core' namespace in $class_path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testAssertNoCoreUseage().
|
||||
*
|
||||
* @return array
|
||||
* Data for testAssertNoCoreUseage() in the form:
|
||||
* - TRUE if the test passes, FALSE otherwise.
|
||||
* - File data as a string. This will be used as a virtual file.
|
||||
*/
|
||||
public function providerAssertNoCoreUseage() {
|
||||
return [
|
||||
[
|
||||
TRUE,
|
||||
'@see \\Drupal\\Core\\Something',
|
||||
],
|
||||
[
|
||||
FALSE,
|
||||
'\\Drupal\\Core\\Something',
|
||||
],
|
||||
[
|
||||
FALSE,
|
||||
"@see \\Drupal\\Core\\Something\n" .
|
||||
'\\Drupal\\Core\\Something',
|
||||
],
|
||||
[
|
||||
FALSE,
|
||||
"\\Drupal\\Core\\Something\n" .
|
||||
'@see \\Drupal\\Core\\Something',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Tests\Component\DrupalComponentTest::assertNoCoreUsage
|
||||
* @dataProvider providerAssertNoCoreUseage
|
||||
*/
|
||||
public function testAssertNoCoreUseage($expected_pass, $file_data) {
|
||||
// Set up a virtual file to read.
|
||||
$vfs_root = vfsStream::setup('root');
|
||||
vfsStream::newFile('Test.php')->at($vfs_root)->setContent($file_data);
|
||||
$file_uri = vfsStream::url('root/Test.php');
|
||||
|
||||
try {
|
||||
$pass = TRUE;
|
||||
$this->assertNoCoreUsage($file_uri);
|
||||
}
|
||||
catch (\PHPUnit_Framework_AssertionFailedError $e) {
|
||||
$pass = FALSE;
|
||||
}
|
||||
$this->assertEquals($expected_pass, $pass, $expected_pass ?
|
||||
'Test caused a false positive' :
|
||||
'Test failed to detect Core usage');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\EventDispatcher;
|
||||
|
||||
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\Tests\CallableClass;
|
||||
use Symfony\Component\EventDispatcher\Tests\ContainerAwareEventDispatcherTest as SymfonyContainerAwareEventDispatcherTest;
|
||||
use Symfony\Component\EventDispatcher\Tests\TestEventListener;
|
||||
|
||||
/**
|
||||
* Unit tests for the ContainerAwareEventDispatcher.
|
||||
*
|
||||
* NOTE: 98% of this code is a literal copy of Symfony's emerging
|
||||
* CompiledEventDispatcherTest.
|
||||
*
|
||||
* This file does NOT follow Drupal coding standards, so as to simplify future
|
||||
* synchronizations.
|
||||
*
|
||||
* @see https://github.com/symfony/symfony/pull/12521
|
||||
*
|
||||
* @group EventDispatcher
|
||||
*/
|
||||
class ContainerAwareEventDispatcherTest extends SymfonyContainerAwareEventDispatcherTest
|
||||
{
|
||||
protected function createEventDispatcher()
|
||||
{
|
||||
$container = new Container();
|
||||
|
||||
return new ContainerAwareEventDispatcher($container);
|
||||
}
|
||||
|
||||
public function testGetListenersWithCallables()
|
||||
{
|
||||
// When passing in callables exclusively as listeners into the event
|
||||
// dispatcher constructor, the event dispatcher must not attempt to
|
||||
// resolve any services.
|
||||
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
|
||||
$container->expects($this->never())->method($this->anything());
|
||||
|
||||
$firstListener = new CallableClass();
|
||||
$secondListener = function () {};
|
||||
$thirdListener = array(new TestEventListener(), 'preFoo');
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
0 => array(
|
||||
array('callable' => $firstListener),
|
||||
array('callable' => $secondListener),
|
||||
array('callable' => $thirdListener),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$actualListeners = $dispatcher->getListeners();
|
||||
|
||||
$expectedListeners = array(
|
||||
'test_event' => array(
|
||||
$firstListener,
|
||||
$secondListener,
|
||||
$thirdListener,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame($expectedListeners, $actualListeners);
|
||||
}
|
||||
|
||||
public function testDispatchWithCallables()
|
||||
{
|
||||
// When passing in callables exclusively as listeners into the event
|
||||
// dispatcher constructor, the event dispatcher must not attempt to
|
||||
// resolve any services.
|
||||
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
|
||||
$container->expects($this->never())->method($this->anything());
|
||||
|
||||
$firstListener = new CallableClass();
|
||||
$secondListener = function () {};
|
||||
$thirdListener = array(new TestEventListener(), 'preFoo');
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
0 => array(
|
||||
array('callable' => $firstListener),
|
||||
array('callable' => $secondListener),
|
||||
array('callable' => $thirdListener),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$dispatcher->dispatch('test_event');
|
||||
|
||||
$this->assertTrue($thirdListener[0]->preFooInvoked);
|
||||
}
|
||||
|
||||
public function testGetListenersWithServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
|
||||
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
0 => array(
|
||||
array('service' => array('listener_service', 'preFoo')),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$actualListeners = $dispatcher->getListeners();
|
||||
|
||||
$listenerService = $container->get('listener_service');
|
||||
$expectedListeners = array(
|
||||
'test_event' => array(
|
||||
array($listenerService, 'preFoo'),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame($expectedListeners, $actualListeners);
|
||||
}
|
||||
|
||||
public function testDispatchWithServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
|
||||
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
0 => array(
|
||||
array('service' => array('listener_service', 'preFoo')),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
|
||||
$dispatcher->dispatch('test_event');
|
||||
|
||||
$listenerService = $container->get('listener_service');
|
||||
$this->assertTrue($listenerService->preFooInvoked);
|
||||
}
|
||||
|
||||
public function testRemoveService()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
|
||||
$container->register('other_listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
|
||||
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
0 => array(
|
||||
array('service' => array('listener_service', 'preFoo')),
|
||||
array('service' => array('other_listener_service', 'preFoo')),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
|
||||
$listenerService = $container->get('listener_service');
|
||||
$dispatcher->removeListener('test_event', array($listenerService, 'preFoo'));
|
||||
|
||||
// Ensure that other service was not initialized during removal of the
|
||||
// listener service.
|
||||
$this->assertFalse($container->initialized('other_listener_service'));
|
||||
|
||||
$dispatcher->dispatch('test_event');
|
||||
|
||||
$this->assertFalse($listenerService->preFooInvoked);
|
||||
$otherService = $container->get('other_listener_service');
|
||||
$this->assertTrue($otherService->preFooInvoked);
|
||||
}
|
||||
|
||||
public function testGetListenerPriorityWithServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('listener_service', TestEventListener::class);
|
||||
|
||||
$listeners = array(
|
||||
'test_event' => array(
|
||||
5 => array(
|
||||
array('service' => array('listener_service', 'preFoo')),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$listenerService = $container->get('listener_service');
|
||||
$actualPriority = $dispatcher->getListenerPriority('test_event', [$listenerService, 'preFoo']);
|
||||
|
||||
$this->assertSame(5, $actualPriority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testAddAListenerService() {
|
||||
parent::testAddAListenerService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testPreventDuplicateListenerService() {
|
||||
parent::testPreventDuplicateListenerService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testAddASubscriberService() {
|
||||
parent::testAddASubscriberService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testHasListenersOnLazyLoad() {
|
||||
parent::testHasListenersOnLazyLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testGetListenersOnLazyLoad() {
|
||||
parent::testGetListenersOnLazyLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testRemoveAfterDispatch() {
|
||||
parent::testRemoveAfterDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedDeprecation The Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.
|
||||
* @group legacy
|
||||
*/
|
||||
public function testRemoveBeforeDispatch() {
|
||||
parent::testRemoveBeforeDispatch();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\FileCache;
|
||||
|
||||
use Drupal\Component\FileCache\FileCache;
|
||||
use Drupal\Component\FileCache\NullFileCache;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\FileCache\FileCacheFactory
|
||||
* @group FileCache
|
||||
*/
|
||||
class FileCacheFactoryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$configuration = [
|
||||
'test_foo_settings' => [
|
||||
'collection' => 'test-23',
|
||||
'cache_backend_class' => '\Drupal\Tests\Component\FileCache\StaticFileCacheBackend',
|
||||
'cache_backend_configuration' => [
|
||||
'bin' => 'dog',
|
||||
],
|
||||
],
|
||||
];
|
||||
FileCacheFactory::setConfiguration($configuration);
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet() {
|
||||
$file_cache = FileCacheFactory::get('test_foo_settings', []);
|
||||
|
||||
// Ensure the right backend and configuration is used.
|
||||
$filename = __DIR__ . '/Fixtures/llama-23.txt';
|
||||
$realpath = realpath($filename);
|
||||
$cid = 'prefix:test-23:' . $realpath;
|
||||
|
||||
$file_cache->set($filename, 23);
|
||||
|
||||
$static_cache = new StaticFileCacheBackend(['bin' => 'dog']);
|
||||
$result = $static_cache->fetch([$cid]);
|
||||
$this->assertNotEmpty($result);
|
||||
|
||||
// Cleanup static caches.
|
||||
$file_cache->delete($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGetNoPrefix() {
|
||||
FileCacheFactory::setPrefix(NULL);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Required prefix configuration is missing');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\InvalidArgumentException::class, 'Required prefix configuration is missing');
|
||||
}
|
||||
FileCacheFactory::get('test_foo_settings', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGetDisabledFileCache() {
|
||||
// Ensure the returned FileCache is an instance of FileCache::class.
|
||||
$file_cache = FileCacheFactory::get('test_foo_settings', []);
|
||||
$this->assertInstanceOf(FileCache::class, $file_cache);
|
||||
|
||||
$configuration = FileCacheFactory::getConfiguration();
|
||||
$configuration[FileCacheFactory::DISABLE_CACHE] = TRUE;
|
||||
FileCacheFactory::setConfiguration($configuration);
|
||||
|
||||
// Ensure the returned FileCache is now an instance of NullFileCache::class.
|
||||
$file_cache = FileCacheFactory::get('test_foo_settings', []);
|
||||
$this->assertInstanceOf(NullFileCache::class, $file_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*
|
||||
* @dataProvider configurationDataProvider
|
||||
*/
|
||||
public function testGetConfigurationOverrides($configuration, $arguments, $class) {
|
||||
FileCacheFactory::setConfiguration($configuration);
|
||||
|
||||
$file_cache = FileCacheFactory::get('test_foo_settings', $arguments);
|
||||
$this->assertInstanceOf($class, $file_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetConfigurationOverrides().
|
||||
*/
|
||||
public function configurationDataProvider() {
|
||||
$data = [];
|
||||
|
||||
// Get a unique FileCache class.
|
||||
$file_cache = $this->getMockBuilder(FileCache::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$class = get_class($file_cache);
|
||||
|
||||
// Test fallback configuration.
|
||||
$data['fallback-configuration'] = [
|
||||
[],
|
||||
[],
|
||||
FileCache::class,
|
||||
];
|
||||
|
||||
// Test default configuration.
|
||||
$data['default-configuration'] = [
|
||||
['default' => ['class' => $class]],
|
||||
[],
|
||||
$class,
|
||||
];
|
||||
|
||||
// Test specific per collection setting.
|
||||
$data['collection-setting'] = [
|
||||
['test_foo_settings' => ['class' => $class]],
|
||||
[],
|
||||
$class,
|
||||
];
|
||||
|
||||
// Test default configuration plus specific per collection setting.
|
||||
$data['default-plus-collection-setting'] = [
|
||||
[
|
||||
'default' => ['class' => '\stdClass'],
|
||||
'test_foo_settings' => ['class' => $class],
|
||||
],
|
||||
[],
|
||||
$class,
|
||||
];
|
||||
|
||||
// Test default configuration plus class specific override.
|
||||
$data['default-plus-class-override'] = [
|
||||
['default' => ['class' => '\stdClass']],
|
||||
['class' => $class],
|
||||
$class,
|
||||
];
|
||||
|
||||
// Test default configuration plus class specific override plus specific
|
||||
// per collection setting.
|
||||
$data['default-plus-class-plus-collection-setting'] = [
|
||||
[
|
||||
'default' => ['class' => '\stdClass'],
|
||||
'test_foo_settings' => ['class' => $class],
|
||||
],
|
||||
['class' => '\stdClass'],
|
||||
$class,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getConfiguration
|
||||
* @covers ::setConfiguration
|
||||
*/
|
||||
public function testGetSetConfiguration() {
|
||||
$configuration = FileCacheFactory::getConfiguration();
|
||||
$configuration['test_foo_bar'] = ['bar' => 'llama'];
|
||||
FileCacheFactory::setConfiguration($configuration);
|
||||
$configuration = FileCacheFactory::getConfiguration();
|
||||
$this->assertEquals(['bar' => 'llama'], $configuration['test_foo_bar']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getPrefix
|
||||
* @covers ::setPrefix
|
||||
*/
|
||||
public function testGetSetPrefix() {
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
$prefix = $random->name(8, TRUE);
|
||||
FileCacheFactory::setPrefix($prefix);
|
||||
$this->assertEquals($prefix, FileCacheFactory::getPrefix());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\FileCache;
|
||||
|
||||
use Drupal\Component\FileCache\FileCache;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\FileCache\FileCache
|
||||
* @group FileCache
|
||||
*/
|
||||
class FileCacheTest extends TestCase {
|
||||
|
||||
/**
|
||||
* FileCache object used for the tests.
|
||||
*
|
||||
* @var \Drupal\Component\FileCache\FileCacheInterface
|
||||
*/
|
||||
protected $fileCache;
|
||||
|
||||
/**
|
||||
* Static FileCache object used for verification of tests.
|
||||
*
|
||||
* @var \Drupal\Component\FileCache\FileCacheBackendInterface
|
||||
*/
|
||||
protected $staticFileCache;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->fileCache = new FileCache('prefix', 'test', '\Drupal\Tests\Component\FileCache\StaticFileCacheBackend', ['bin' => 'llama']);
|
||||
$this->staticFileCache = new StaticFileCacheBackend(['bin' => 'llama']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
* @covers ::__construct
|
||||
*/
|
||||
public function testGet() {
|
||||
// Test a cache miss.
|
||||
$result = $this->fileCache->get(__DIR__ . '/Fixtures/no-llama-42.yml');
|
||||
$this->assertNull($result);
|
||||
|
||||
// Test a cache hit.
|
||||
$filename = __DIR__ . '/Fixtures/llama-42.txt';
|
||||
$realpath = realpath($filename);
|
||||
$cid = 'prefix:test:' . $realpath;
|
||||
$data = [
|
||||
'mtime' => filemtime($realpath),
|
||||
'filepath' => $realpath,
|
||||
'data' => 42,
|
||||
];
|
||||
|
||||
$this->staticFileCache->store($cid, $data);
|
||||
|
||||
$result = $this->fileCache->get($filename);
|
||||
$this->assertEquals(42, $result);
|
||||
|
||||
// Cleanup static caches.
|
||||
$this->fileCache->delete($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getMultiple
|
||||
*/
|
||||
public function testGetMultiple() {
|
||||
// Test a cache miss.
|
||||
$result = $this->fileCache->getMultiple([__DIR__ . '/Fixtures/no-llama-42.yml']);
|
||||
$this->assertEmpty($result);
|
||||
|
||||
// Test a cache hit.
|
||||
$filename = __DIR__ . '/Fixtures/llama-42.txt';
|
||||
$realpath = realpath($filename);
|
||||
$cid = 'prefix:test:' . $realpath;
|
||||
$data = [
|
||||
'mtime' => filemtime($realpath),
|
||||
'filepath' => $realpath,
|
||||
'data' => 42,
|
||||
];
|
||||
|
||||
$this->staticFileCache->store($cid, $data);
|
||||
|
||||
$result = $this->fileCache->getMultiple([$filename]);
|
||||
$this->assertEquals([$filename => 42], $result);
|
||||
|
||||
// Test a static cache hit.
|
||||
$file2 = __DIR__ . '/Fixtures/llama-23.txt';
|
||||
$this->fileCache->set($file2, 23);
|
||||
|
||||
$result = $this->fileCache->getMultiple([$filename, $file2]);
|
||||
$this->assertEquals([$filename => 42, $file2 => 23], $result);
|
||||
|
||||
// Cleanup static caches.
|
||||
$this->fileCache->delete($filename);
|
||||
$this->fileCache->delete($file2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::set
|
||||
*/
|
||||
public function testSet() {
|
||||
$filename = __DIR__ . '/Fixtures/llama-23.txt';
|
||||
$realpath = realpath($filename);
|
||||
$cid = 'prefix:test:' . $realpath;
|
||||
$data = [
|
||||
'mtime' => filemtime($realpath),
|
||||
'filepath' => $realpath,
|
||||
'data' => 23,
|
||||
];
|
||||
|
||||
$this->fileCache->set($filename, 23);
|
||||
$result = $this->staticFileCache->fetch([$cid]);
|
||||
$this->assertEquals([$cid => $data], $result);
|
||||
|
||||
// Cleanup static caches.
|
||||
$this->fileCache->delete($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::delete
|
||||
*/
|
||||
public function testDelete() {
|
||||
$filename = __DIR__ . '/Fixtures/llama-23.txt';
|
||||
$realpath = realpath($filename);
|
||||
$cid = 'prefix:test:' . $realpath;
|
||||
|
||||
$this->fileCache->set($filename, 23);
|
||||
|
||||
// Ensure data is removed after deletion.
|
||||
$this->fileCache->delete($filename);
|
||||
|
||||
$result = $this->staticFileCache->fetch([$cid]);
|
||||
$this->assertEquals([], $result);
|
||||
|
||||
$result = $this->fileCache->get($filename);
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
23
|
||||
|
|
@ -0,0 +1 @@
|
|||
42
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\FileCache;
|
||||
|
||||
use Drupal\Component\FileCache\FileCacheBackendInterface;
|
||||
|
||||
/**
|
||||
* Allows to cache data based on file modification dates in a static cache.
|
||||
*/
|
||||
class StaticFileCacheBackend implements FileCacheBackendInterface {
|
||||
|
||||
/**
|
||||
* Internal static cache.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $cache = [];
|
||||
|
||||
/**
|
||||
* Bin used for storing the data in the static cache.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bin;
|
||||
|
||||
/**
|
||||
* Constructs a PHP Storage FileCache backend.
|
||||
*
|
||||
* @param array $configuration
|
||||
* (optional) Configuration used to configure this object.
|
||||
*/
|
||||
public function __construct($configuration) {
|
||||
$this->bin = isset($configuration['bin']) ? $configuration['bin'] : 'file_cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fetch(array $cids) {
|
||||
$result = [];
|
||||
foreach ($cids as $cid) {
|
||||
if (isset(static::$cache[$this->bin][$cid])) {
|
||||
$result[$cid] = static::$cache[$this->bin][$cid];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function store($cid, $data) {
|
||||
static::$cache[$this->bin][$cid] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($cid) {
|
||||
unset(static::$cache[$this->bin][$cid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows tests to reset the static cache to avoid side effects.
|
||||
*/
|
||||
public static function reset() {
|
||||
static::$cache = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\FileSystem;
|
||||
|
||||
use Drupal\Component\FileSystem\RegexDirectoryIterator;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\FileSystem\RegexDirectoryIterator
|
||||
* @group FileSystem
|
||||
*/
|
||||
class RegexDirectoryIteratorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::accept
|
||||
* @dataProvider providerTestRegexDirectoryIterator
|
||||
*/
|
||||
public function testRegexDirectoryIterator(array $directory, $regex, array $expected) {
|
||||
vfsStream::setup('root', NULL, $directory);
|
||||
$iterator = new RegexDirectoryIterator(vfsStream::url('root'), $regex);
|
||||
|
||||
// Create an array of filenames to assert against.
|
||||
$file_list = array_map(function (\SplFileInfo $file) {
|
||||
return $file->getFilename();
|
||||
}, array_values(iterator_to_array($iterator)));
|
||||
|
||||
$this->assertSame($expected, $file_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for self::testRegexDirectoryIterator().
|
||||
*/
|
||||
public function providerTestRegexDirectoryIterator() {
|
||||
return [
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
],
|
||||
'/\.yml$/',
|
||||
[
|
||||
'1.yml',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
'2.yml' => '',
|
||||
'3.txt' => '',
|
||||
],
|
||||
'/\.yml$/',
|
||||
[
|
||||
'1.yml',
|
||||
'2.yml',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
'2.yml' => '',
|
||||
'3.txt' => '',
|
||||
],
|
||||
'/\.txt/',
|
||||
[
|
||||
'3.txt',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
// Ensure we don't recurse in directories even if that match the
|
||||
// regex.
|
||||
'2.yml' => [
|
||||
'3.yml' => '',
|
||||
'4.yml' => '',
|
||||
],
|
||||
'3.txt' => '',
|
||||
],
|
||||
'/\.yml$/',
|
||||
[
|
||||
'1.yml',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
'2.yml' => '',
|
||||
'3.txt' => '',
|
||||
],
|
||||
'/^\d/',
|
||||
[
|
||||
'1.yml',
|
||||
'2.yml',
|
||||
'3.txt',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'1.yml' => '',
|
||||
'2.yml' => '',
|
||||
'3.txt' => '',
|
||||
],
|
||||
'/^\D/',
|
||||
[],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Gettext;
|
||||
|
||||
use Drupal\Component\Gettext\PoHeader;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the Gettext PO file header handling features.
|
||||
*
|
||||
* @see Drupal\Component\Gettext\PoHeader.
|
||||
*
|
||||
* @group Gettext
|
||||
*/
|
||||
class PoHeaderTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests that plural expressions are evaluated correctly.
|
||||
*
|
||||
* Validate that the given plural expressions is evaluated with the correct
|
||||
* plural formula.
|
||||
*
|
||||
* @param string $plural
|
||||
* The plural expression.
|
||||
* @param array $expected
|
||||
* Array of expected plural positions keyed by plural value.
|
||||
*
|
||||
* @dataProvider providerTestPluralsFormula
|
||||
*/
|
||||
public function testPluralsFormula($plural, $expected) {
|
||||
$p = new PoHeader();
|
||||
$parsed = $p->parsePluralForms($plural);
|
||||
list($nplurals, $new_plural) = $parsed;
|
||||
foreach ($expected as $number => $plural_form) {
|
||||
$result = isset($new_plural[$number]) ? $new_plural[$number] : $new_plural['default'];
|
||||
$this->assertEquals($result, $plural_form, 'Difference found at ' . $number . ': ' . $plural_form . ' versus ' . $result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPluralsFormula.
|
||||
*
|
||||
* Gets pairs of plural expressions and expected plural positions keyed by
|
||||
* plural value.
|
||||
*
|
||||
* @return array
|
||||
* Pairs of plural expressions and expected plural positions keyed by plural
|
||||
* value.
|
||||
*/
|
||||
public function providerTestPluralsFormula() {
|
||||
return [
|
||||
[
|
||||
'nplurals=1; plural=0;',
|
||||
['default' => 0],
|
||||
],
|
||||
[
|
||||
'nplurals=2; plural=(n > 1);',
|
||||
[0 => 0, 1 => 0, 'default' => 1],
|
||||
],
|
||||
[
|
||||
'nplurals=2; plural=(n!=1);',
|
||||
[1 => 0, 'default' => 1],
|
||||
],
|
||||
[
|
||||
'nplurals=2; plural=(((n==1)||((n%10)==1))?(0):1);',
|
||||
[
|
||||
1 => 0,
|
||||
11 => 0,
|
||||
21 => 0,
|
||||
31 => 0,
|
||||
41 => 0,
|
||||
51 => 0,
|
||||
61 => 0,
|
||||
71 => 0,
|
||||
81 => 0,
|
||||
91 => 0,
|
||||
101 => 0,
|
||||
111 => 0,
|
||||
121 => 0,
|
||||
131 => 0,
|
||||
141 => 0,
|
||||
151 => 0,
|
||||
161 => 0,
|
||||
171 => 0,
|
||||
181 => 0,
|
||||
191 => 0,
|
||||
'default' => 1,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
|
||||
[
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 1,
|
||||
4 => 1,
|
||||
21 => 0,
|
||||
22 => 1,
|
||||
23 => 1,
|
||||
24 => 1,
|
||||
31 => 0,
|
||||
32 => 1,
|
||||
33 => 1,
|
||||
34 => 1,
|
||||
41 => 0,
|
||||
42 => 1,
|
||||
43 => 1,
|
||||
44 => 1,
|
||||
51 => 0,
|
||||
52 => 1,
|
||||
53 => 1,
|
||||
54 => 1,
|
||||
61 => 0,
|
||||
62 => 1,
|
||||
63 => 1,
|
||||
64 => 1,
|
||||
71 => 0,
|
||||
72 => 1,
|
||||
73 => 1,
|
||||
74 => 1,
|
||||
81 => 0,
|
||||
82 => 1,
|
||||
83 => 1,
|
||||
84 => 1,
|
||||
91 => 0,
|
||||
92 => 1,
|
||||
93 => 1,
|
||||
94 => 1,
|
||||
101 => 0,
|
||||
102 => 1,
|
||||
103 => 1,
|
||||
104 => 1,
|
||||
121 => 0,
|
||||
122 => 1,
|
||||
123 => 1,
|
||||
124 => 1,
|
||||
131 => 0,
|
||||
132 => 1,
|
||||
133 => 1,
|
||||
134 => 1,
|
||||
141 => 0,
|
||||
142 => 1,
|
||||
143 => 1,
|
||||
144 => 1,
|
||||
151 => 0,
|
||||
152 => 1,
|
||||
153 => 1,
|
||||
154 => 1,
|
||||
161 => 0,
|
||||
162 => 1,
|
||||
163 => 1,
|
||||
164 => 1,
|
||||
171 => 0,
|
||||
172 => 1,
|
||||
173 => 1,
|
||||
174 => 1,
|
||||
181 => 0,
|
||||
182 => 1,
|
||||
183 => 1,
|
||||
184 => 1,
|
||||
191 => 0,
|
||||
192 => 1,
|
||||
193 => 1,
|
||||
194 => 1,
|
||||
'default' => 2,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=3; plural=((n==1)?(0):(((n>=2)&&(n<=4))?(1):2));',
|
||||
[
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 1,
|
||||
4 => 1,
|
||||
'default' => 2,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=3; plural=((n==1)?(0):(((n==0)||(((n%100)>0)&&((n%100)<20)))?(1):2));',
|
||||
[
|
||||
0 => 1,
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 1,
|
||||
4 => 1,
|
||||
5 => 1,
|
||||
6 => 1,
|
||||
7 => 1,
|
||||
8 => 1,
|
||||
9 => 1,
|
||||
10 => 1,
|
||||
11 => 1,
|
||||
12 => 1,
|
||||
13 => 1,
|
||||
14 => 1,
|
||||
15 => 1,
|
||||
16 => 1,
|
||||
17 => 1,
|
||||
18 => 1,
|
||||
19 => 1,
|
||||
101 => 1,
|
||||
102 => 1,
|
||||
103 => 1,
|
||||
104 => 1,
|
||||
105 => 1,
|
||||
106 => 1,
|
||||
107 => 1,
|
||||
108 => 1,
|
||||
109 => 1,
|
||||
110 => 1,
|
||||
111 => 1,
|
||||
112 => 1,
|
||||
113 => 1,
|
||||
114 => 1,
|
||||
115 => 1,
|
||||
116 => 1,
|
||||
117 => 1,
|
||||
118 => 1,
|
||||
119 => 1,
|
||||
'default' => 2,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=3; plural=((n==1)?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
|
||||
[
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 1,
|
||||
4 => 1,
|
||||
22 => 1,
|
||||
23 => 1,
|
||||
24 => 1,
|
||||
32 => 1,
|
||||
33 => 1,
|
||||
34 => 1,
|
||||
42 => 1,
|
||||
43 => 1,
|
||||
44 => 1,
|
||||
52 => 1,
|
||||
53 => 1,
|
||||
54 => 1,
|
||||
62 => 1,
|
||||
63 => 1,
|
||||
64 => 1,
|
||||
72 => 1,
|
||||
73 => 1,
|
||||
74 => 1,
|
||||
82 => 1,
|
||||
83 => 1,
|
||||
84 => 1,
|
||||
92 => 1,
|
||||
93 => 1,
|
||||
94 => 1,
|
||||
102 => 1,
|
||||
103 => 1,
|
||||
104 => 1,
|
||||
122 => 1,
|
||||
123 => 1,
|
||||
124 => 1,
|
||||
132 => 1,
|
||||
133 => 1,
|
||||
134 => 1,
|
||||
142 => 1,
|
||||
143 => 1,
|
||||
144 => 1,
|
||||
152 => 1,
|
||||
153 => 1,
|
||||
154 => 1,
|
||||
162 => 1,
|
||||
163 => 1,
|
||||
164 => 1,
|
||||
172 => 1,
|
||||
173 => 1,
|
||||
174 => 1,
|
||||
182 => 1,
|
||||
183 => 1,
|
||||
184 => 1,
|
||||
192 => 1,
|
||||
193 => 1,
|
||||
194 => 1,
|
||||
'default' => 2,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=4; plural=(((n==1)||(n==11))?(0):(((n==2)||(n==12))?(1):(((n>2)&&(n<20))?(2):3)));',
|
||||
[
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 2,
|
||||
4 => 2,
|
||||
5 => 2,
|
||||
6 => 2,
|
||||
7 => 2,
|
||||
8 => 2,
|
||||
9 => 2,
|
||||
10 => 2,
|
||||
11 => 0,
|
||||
12 => 1,
|
||||
13 => 2,
|
||||
14 => 2,
|
||||
15 => 2,
|
||||
16 => 2,
|
||||
17 => 2,
|
||||
18 => 2,
|
||||
19 => 2,
|
||||
'default' => 3,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));',
|
||||
[
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 2,
|
||||
4 => 2,
|
||||
101 => 0,
|
||||
102 => 1,
|
||||
103 => 2,
|
||||
104 => 2,
|
||||
'default' => 3,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=5; plural=((n==1)?(0):((n==2)?(1):((n<7)?(2):((n<11)?(3):4))));',
|
||||
[
|
||||
0 => 2,
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 2,
|
||||
4 => 2,
|
||||
5 => 2,
|
||||
6 => 2,
|
||||
7 => 3,
|
||||
8 => 3,
|
||||
9 => 3,
|
||||
10 => 3,
|
||||
'default' => 4,
|
||||
],
|
||||
],
|
||||
[
|
||||
'nplurals=6; plural=((n==1)?(0):((n==0)?(1):((n==2)?(2):((((n%100)>=3)&&((n%100)<=10))?(3):((((n%100)>=11)&&((n%100)<=99))?(4):5)))));',
|
||||
[
|
||||
0 => 1,
|
||||
1 => 0,
|
||||
2 => 2,
|
||||
3 => 3,
|
||||
4 => 3,
|
||||
5 => 3,
|
||||
6 => 3,
|
||||
7 => 3,
|
||||
8 => 3,
|
||||
9 => 3,
|
||||
10 => 3,
|
||||
100 => 5,
|
||||
101 => 5,
|
||||
102 => 5,
|
||||
103 => 3,
|
||||
104 => 3,
|
||||
105 => 3,
|
||||
106 => 3,
|
||||
107 => 3,
|
||||
108 => 3,
|
||||
109 => 3,
|
||||
110 => 3,
|
||||
'default' => 4,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Gettext;
|
||||
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Component\Gettext\PoStreamWriter;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use org\bovigo\vfs\vfsStreamFile;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Gettext\PoStreamWriter
|
||||
* @group Gettext
|
||||
*/
|
||||
class PoStreamWriterTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The PO writer object under test.
|
||||
*
|
||||
* @var \Drupal\Component\Gettext\PoStreamWriter
|
||||
*/
|
||||
protected $poWriter;
|
||||
|
||||
/**
|
||||
* The mock po file.
|
||||
*
|
||||
* @var \org\bovigo\vfs\vfsStreamFile
|
||||
*/
|
||||
protected $poFile;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->poWriter = new PoStreamWriter();
|
||||
|
||||
$root = vfsStream::setup();
|
||||
$this->poFile = new vfsStreamFile('powriter.po');
|
||||
$root->addChild($this->poFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getURI
|
||||
*/
|
||||
public function testGetUriException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\Exception::class, 'No URI set.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\Exception::class, 'No URI set.');
|
||||
}
|
||||
|
||||
$this->poWriter->getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeItem
|
||||
* @dataProvider providerWriteData
|
||||
*/
|
||||
public function testWriteItem($poContent, $expected, $long) {
|
||||
if ($long) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\Exception::class, 'Unable to write data:');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\Exception::class, 'Unable to write data:');
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the file system quota to make the write fail on long strings.
|
||||
vfsStream::setQuota(10);
|
||||
|
||||
$this->poWriter->setURI($this->poFile->url());
|
||||
$this->poWriter->open();
|
||||
|
||||
$poItem = $this->prophesize(PoItem::class);
|
||||
$poItem->__toString()->willReturn($poContent);
|
||||
|
||||
$this->poWriter->writeItem($poItem->reveal());
|
||||
$this->poWriter->close();
|
||||
$this->assertEquals(file_get_contents($this->poFile->url()), $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* - Content to write.
|
||||
* - Written content.
|
||||
* - Content longer than 10 bytes.
|
||||
*/
|
||||
public function providerWriteData() {
|
||||
return [
|
||||
['', '', FALSE],
|
||||
["\r\n", "\r\n", FALSE],
|
||||
['write this if you can', 'write this', TRUE],
|
||||
['éáíó>&', 'éáíó>&', FALSE],
|
||||
['éáíó>&<', 'éáíó>&', TRUE],
|
||||
['中文 890', '中文 890', FALSE],
|
||||
['中文 89012', '中文 890', TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::close
|
||||
*/
|
||||
public function testCloseException() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\Exception::class, 'Cannot close stream that is not open.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\Exception::class, 'Cannot close stream that is not open.');
|
||||
}
|
||||
|
||||
$this->poWriter->close();
|
||||
}
|
||||
|
||||
}
|
||||
191
2017/web/core/tests/Drupal/Tests/Component/Graph/GraphTest.php
Normal file
191
2017/web/core/tests/Drupal/Tests/Component/Graph/GraphTest.php
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Graph;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Graph\Graph
|
||||
* @group Graph
|
||||
*/
|
||||
class GraphTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Test depth-first-search features.
|
||||
*/
|
||||
public function testDepthFirstSearch() {
|
||||
// The sample graph used is:
|
||||
// 1 --> 2 --> 3 5 ---> 6
|
||||
// | ^ ^
|
||||
// | | |
|
||||
// | | |
|
||||
// +---> 4 <-- 7 8 ---> 9
|
||||
$graph = $this->normalizeGraph([
|
||||
1 => [2],
|
||||
2 => [3, 4],
|
||||
3 => [],
|
||||
4 => [3],
|
||||
5 => [6],
|
||||
7 => [4, 5],
|
||||
8 => [9],
|
||||
9 => [],
|
||||
]);
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
|
||||
$expected_paths = [
|
||||
1 => [2, 3, 4],
|
||||
2 => [3, 4],
|
||||
3 => [],
|
||||
4 => [3],
|
||||
5 => [6],
|
||||
7 => [4, 3, 5, 6],
|
||||
8 => [9],
|
||||
9 => [],
|
||||
];
|
||||
$this->assertPaths($graph, $expected_paths);
|
||||
|
||||
$expected_reverse_paths = [
|
||||
1 => [],
|
||||
2 => [1],
|
||||
3 => [2, 1, 4, 7],
|
||||
4 => [2, 1, 7],
|
||||
5 => [7],
|
||||
7 => [],
|
||||
8 => [],
|
||||
9 => [8],
|
||||
];
|
||||
$this->assertReversePaths($graph, $expected_reverse_paths);
|
||||
|
||||
// Assert that DFS didn't created "missing" vertexes automatically.
|
||||
$this->assertFalse(isset($graph[6]), 'Vertex 6 has not been created');
|
||||
|
||||
$expected_components = [
|
||||
[1, 2, 3, 4, 5, 7],
|
||||
[8, 9],
|
||||
];
|
||||
$this->assertComponents($graph, $expected_components);
|
||||
|
||||
$expected_weights = [
|
||||
[1, 2, 3],
|
||||
[2, 4, 3],
|
||||
[7, 4, 3],
|
||||
[7, 5],
|
||||
[8, 9],
|
||||
];
|
||||
$this->assertWeights($graph, $expected_weights);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
*
|
||||
* @return array
|
||||
* The normalized version of a graph.
|
||||
*/
|
||||
protected function normalizeGraph($graph) {
|
||||
$normalized_graph = [];
|
||||
foreach ($graph as $vertex => $edges) {
|
||||
// Create vertex even if it hasn't any edges.
|
||||
$normalized_graph[$vertex] = [];
|
||||
foreach ($edges as $edge) {
|
||||
$normalized_graph[$vertex]['edges'][$edge] = TRUE;
|
||||
}
|
||||
}
|
||||
return $normalized_graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param $expected_paths
|
||||
* An associative array containing vertices with their expected paths.
|
||||
*/
|
||||
protected function assertPaths($graph, $expected_paths) {
|
||||
foreach ($expected_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : [];
|
||||
$this->assertEquals($expected, $result, sprintf('Expected paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected reverse paths in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param $expected_reverse_paths
|
||||
* An associative array containing vertices with their expected reverse
|
||||
* paths.
|
||||
*/
|
||||
protected function assertReversePaths($graph, $expected_reverse_paths) {
|
||||
foreach ($expected_reverse_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : [];
|
||||
$this->assertEquals($expected, $result, sprintf('Expected reverse paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected components in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param $expected_components
|
||||
* An array containing of components defined as a list of their vertices.
|
||||
*/
|
||||
protected function assertComponents($graph, $expected_components) {
|
||||
$unassigned_vertices = array_fill_keys(array_keys($graph), TRUE);
|
||||
foreach ($expected_components as $component) {
|
||||
$result_components = [];
|
||||
foreach ($component as $vertex) {
|
||||
$result_components[] = $graph[$vertex]['component'];
|
||||
unset($unassigned_vertices[$vertex]);
|
||||
}
|
||||
$this->assertEquals(1, count(array_unique($result_components)), sprintf('Expected one unique component for vertices %s, got %s', $this->displayArray($component), $this->displayArray($result_components)));
|
||||
}
|
||||
$this->assertEquals([], $unassigned_vertices, sprintf('Vertices not assigned to a component: %s', $this->displayArray($unassigned_vertices, TRUE)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify expected order in a graph.
|
||||
*
|
||||
* @param $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param $expected_orders
|
||||
* An array containing lists of vertices in their expected order.
|
||||
*/
|
||||
protected function assertWeights($graph, $expected_orders) {
|
||||
foreach ($expected_orders as $order) {
|
||||
$previous_vertex = array_shift($order);
|
||||
foreach ($order as $vertex) {
|
||||
$this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], sprintf('Weights of %s and %s are correct relative to each other', $previous_vertex, $vertex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to output vertices as comma-separated list.
|
||||
*
|
||||
* @param $paths
|
||||
* An array containing a list of vertices.
|
||||
* @param $keys
|
||||
* (optional) Whether to output the keys of $paths instead of the values.
|
||||
*/
|
||||
protected function displayArray($paths, $keys = FALSE) {
|
||||
if (!empty($paths)) {
|
||||
return implode(', ', $keys ? array_keys($paths) : $paths);
|
||||
}
|
||||
else {
|
||||
return '(empty)';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\HttpFoundation\SecuredRedirectResponseTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\HttpFoundation;
|
||||
|
||||
use Drupal\Component\HttpFoundation\SecuredRedirectResponse;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
/**
|
||||
* Test secure redirect base class.
|
||||
*
|
||||
* @group Routing
|
||||
* @coversDefaultClass \Drupal\Component\HttpFoundation\SecuredRedirectResponse
|
||||
*/
|
||||
class SecuredRedirectResponseTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Test copying of redirect response.
|
||||
*
|
||||
* @covers ::createFromRedirectResponse
|
||||
* @covers ::fromResponse
|
||||
*/
|
||||
public function testRedirectCopy() {
|
||||
$redirect = new RedirectResponse('/magic_redirect_url', 301, ['x-cache-foobar' => 123]);
|
||||
$redirect->setProtocolVersion('2.0');
|
||||
$redirect->setCharset('ibm-943_P14A-2000');
|
||||
$redirect->headers->setCookie(new Cookie('name', 'value'));
|
||||
|
||||
// Make a cloned redirect.
|
||||
$secureRedirect = SecuredRedirectStub::createFromRedirectResponse($redirect);
|
||||
$this->assertEquals('/magic_redirect_url', $secureRedirect->getTargetUrl());
|
||||
$this->assertEquals(301, $secureRedirect->getStatusCode());
|
||||
// We pull the headers from the original redirect because there are default headers applied.
|
||||
$headers1 = $redirect->headers->allPreserveCase();
|
||||
$headers2 = $secureRedirect->headers->allPreserveCase();
|
||||
// We unset cache headers so we don't test arcane Symfony weirdness.
|
||||
// https://github.com/symfony/symfony/issues/16171
|
||||
unset($headers1['Cache-Control'], $headers2['Cache-Control']);
|
||||
$this->assertEquals($headers1, $headers2);
|
||||
$this->assertEquals('2.0', $secureRedirect->getProtocolVersion());
|
||||
$this->assertEquals('ibm-943_P14A-2000', $secureRedirect->getCharset());
|
||||
$this->assertEquals($redirect->headers->getCookies(), $secureRedirect->headers->getCookies());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SecuredRedirectStub extends SecuredRedirectResponse {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isSafe($url) {
|
||||
// Empty implementation for testing.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use Drupal\Component\PhpStorage\FileReadOnlyStorage;
|
||||
use Drupal\Component\Utility\Random;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\FileReadOnlyStorage
|
||||
*
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class FileStorageReadOnlyTest extends PhpStorageTestBase {
|
||||
|
||||
/**
|
||||
* Standard test settings to pass to storage instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $standardSettings;
|
||||
|
||||
/**
|
||||
* Read only test settings to pass to storage instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $readonlyStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->standardSettings = [
|
||||
'directory' => $this->directory,
|
||||
'bin' => 'test',
|
||||
];
|
||||
$this->readonlyStorage = [
|
||||
'directory' => $this->directory,
|
||||
// Let this read from the bin where the other instance is writing.
|
||||
'bin' => 'test',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests writing with one class and reading with another.
|
||||
*/
|
||||
public function testReadOnly() {
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
$name = $random->name(8, TRUE) . '/' . $random->name(8, TRUE) . '.php';
|
||||
|
||||
// Find a global that doesn't exist.
|
||||
do {
|
||||
$random = mt_rand(10000, 100000);
|
||||
} while (isset($GLOBALS[$random]));
|
||||
|
||||
// Write out a PHP file and ensure it's successfully loaded.
|
||||
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
|
||||
$success = $php->save($name, $code);
|
||||
$this->assertSame(TRUE, $success);
|
||||
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
|
||||
$php_read->load($name);
|
||||
$this->assertTrue($GLOBALS[$random]);
|
||||
|
||||
// If the file was successfully loaded, it must also exist, but ensure the
|
||||
// exists() method returns that correctly.
|
||||
$this->assertSame(TRUE, $php_read->exists($name));
|
||||
// Saving and deleting should always fail.
|
||||
$this->assertFalse($php_read->save($name, $code));
|
||||
$this->assertFalse($php_read->delete($name));
|
||||
unset($GLOBALS[$random]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeable
|
||||
*/
|
||||
public function testWriteable() {
|
||||
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
|
||||
$this->assertFalse($php_read->writeable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteAll
|
||||
*/
|
||||
public function testDeleteAll() {
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
$name = $random->name(8, TRUE) . '/' . $random->name(8, TRUE) . '.php';
|
||||
|
||||
// Find a global that doesn't exist.
|
||||
do {
|
||||
$random = mt_rand(10000, 100000);
|
||||
} while (isset($GLOBALS[$random]));
|
||||
|
||||
// Write our the file so we can test deleting.
|
||||
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
|
||||
$this->assertTrue($php->save($name, $code));
|
||||
|
||||
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
|
||||
$this->assertFalse($php_read->deleteAll());
|
||||
|
||||
// Make sure directory exists prior to removal.
|
||||
$this->assertTrue(file_exists($this->directory . '/test'), 'File storage directory does not exist.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use org\bovigo\vfs\vfsStreamDirectory;
|
||||
use PHPUnit\Framework\Error\Warning;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class FileStorageTest extends PhpStorageTestBase {
|
||||
|
||||
/**
|
||||
* Standard test settings to pass to storage instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $standardSettings;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->standardSettings = [
|
||||
'directory' => $this->directory,
|
||||
'bin' => 'test',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic load/save/delete operations.
|
||||
*
|
||||
* @covers ::load
|
||||
* @covers ::save
|
||||
* @covers ::exists
|
||||
* @covers ::delete
|
||||
*/
|
||||
public function testCRUD() {
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
$this->assertCRUD($php);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeable
|
||||
*/
|
||||
public function testWriteable() {
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
$this->assertTrue($php->writeable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteAll
|
||||
*/
|
||||
public function testDeleteAll() {
|
||||
// Random generator.
|
||||
$random_generator = new Random();
|
||||
|
||||
// Write out some files.
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
|
||||
$name = $random_generator->name(8, TRUE) . '/' . $random_generator->name(8, TRUE) . '.php';
|
||||
|
||||
// Find a global that doesn't exist.
|
||||
do {
|
||||
$random = mt_rand(10000, 100000);
|
||||
} while (isset($GLOBALS[$random]));
|
||||
|
||||
// Write out a PHP file and ensure it's successfully loaded.
|
||||
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
|
||||
$this->assertTrue($php->save($name, $code), 'Saved php file');
|
||||
$php->load($name);
|
||||
$this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
|
||||
|
||||
// Make sure directory exists prior to removal.
|
||||
$this->assertTrue(file_exists($this->directory . '/test'), 'File storage directory does not exist.');
|
||||
|
||||
$this->assertTrue($php->deleteAll(), 'Delete all reported success');
|
||||
$this->assertFalse($php->load($name));
|
||||
$this->assertFalse(file_exists($this->directory . '/test'), 'File storage directory does not exist after call to deleteAll()');
|
||||
|
||||
// Should still return TRUE if directory has already been deleted.
|
||||
$this->assertTrue($php->deleteAll(), 'Delete all succeeds with nothing to delete');
|
||||
unset($GLOBALS[$random]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::createDirectory
|
||||
*/
|
||||
public function testCreateDirectoryFailWarning() {
|
||||
$directory = new vfsStreamDirectory('permissionDenied', 0200);
|
||||
$storage = new FileStorage([
|
||||
'directory' => $directory->url(),
|
||||
'bin' => 'test',
|
||||
]);
|
||||
$code = "<?php\n echo 'here';";
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(Warning::class);
|
||||
$this->expectExceptionMessage('mkdir(): Permission Denied');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\PHPUnit_Framework_Error_Warning::class, 'mkdir(): Permission Denied');
|
||||
}
|
||||
$storage->save('subdirectory/foo.php', $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Tests the MTimeProtectedFastFileStorage implementation.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage
|
||||
*
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class MTimeProtectedFastFileStorageTest extends MTimeProtectedFileStorageBase {
|
||||
|
||||
/**
|
||||
* The expected test results for the security test.
|
||||
*
|
||||
* The first iteration does not change the directory mtime so this class will
|
||||
* include the hacked file on the first try but the second test will change
|
||||
* the directory mtime and so on the second try the file will not be included.
|
||||
*/
|
||||
protected $expected = [TRUE, FALSE];
|
||||
|
||||
/**
|
||||
* The PHP storage class to test.
|
||||
*/
|
||||
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Random;
|
||||
|
||||
/**
|
||||
* Base test class for MTime protected storage.
|
||||
*/
|
||||
abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase {
|
||||
|
||||
/**
|
||||
* The PHP storage class to test.
|
||||
*
|
||||
* This should be overridden by extending classes.
|
||||
*/
|
||||
protected $storageClass;
|
||||
|
||||
/**
|
||||
* The secret string to use for file creation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secret;
|
||||
|
||||
/**
|
||||
* Test settings to pass to storage instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
$this->secret = $random->name(8, TRUE);
|
||||
|
||||
$this->settings = [
|
||||
'directory' => $this->directory,
|
||||
'bin' => 'test',
|
||||
'secret' => $this->secret,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic load/save/delete operations.
|
||||
*
|
||||
* @covers ::load
|
||||
* @covers ::save
|
||||
* @covers ::delete
|
||||
* @covers ::exists
|
||||
*/
|
||||
public function testCRUD() {
|
||||
$php = new $this->storageClass($this->settings);
|
||||
$this->assertCRUD($php);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the security of the MTimeProtectedFileStorage implementation.
|
||||
*
|
||||
* We test two attacks: first changes the file mtime, then the directory
|
||||
* mtime too.
|
||||
*
|
||||
* We need to delay over 1 second for mtime test.
|
||||
* @medium
|
||||
*/
|
||||
public function testSecurity() {
|
||||
$php = new $this->storageClass($this->settings);
|
||||
$name = 'simpletest.php';
|
||||
$php->save($name, '<?php');
|
||||
$expected_root_directory = $this->directory . '/test';
|
||||
if (substr($name, -4) === '.php') {
|
||||
$expected_directory = $expected_root_directory . '/' . substr($name, 0, -4);
|
||||
}
|
||||
else {
|
||||
$expected_directory = $expected_root_directory . '/' . $name;
|
||||
}
|
||||
$directory_mtime = filemtime($expected_directory);
|
||||
$expected_filename = $expected_directory . '/' . Crypt::hmacBase64($name, $this->secret . $directory_mtime) . '.php';
|
||||
|
||||
// Ensure the file exists and that it and the containing directory have
|
||||
// minimal permissions. fileperms() can return high bits unrelated to
|
||||
// permissions, so mask with 0777.
|
||||
$this->assertTrue(file_exists($expected_filename));
|
||||
$this->assertSame(0444, fileperms($expected_filename) & 0777);
|
||||
$this->assertSame(0777, fileperms($expected_directory) & 0777);
|
||||
|
||||
// Ensure the root directory for the bin has a .htaccess file denying web
|
||||
// access.
|
||||
$this->assertSame(file_get_contents($expected_root_directory . '/.htaccess'), call_user_func([$this->storageClass, 'htaccessLines']));
|
||||
|
||||
// Ensure that if the file is replaced with an untrusted one (due to another
|
||||
// script's file upload vulnerability), it does not get loaded. Since mtime
|
||||
// granularity is 1 second, we cannot prevent an attack that happens within
|
||||
// a second of the initial save().
|
||||
sleep(1);
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$php = new $this->storageClass($this->settings);
|
||||
$GLOBALS['hacked'] = FALSE;
|
||||
$untrusted_code = "<?php\n" . '$GLOBALS["hacked"] = TRUE;';
|
||||
chmod($expected_directory, 0700);
|
||||
chmod($expected_filename, 0700);
|
||||
if ($i) {
|
||||
// Now try to write the file in such a way that the directory mtime
|
||||
// changes and invalidates the hash.
|
||||
file_put_contents($expected_filename . '.tmp', $untrusted_code);
|
||||
rename($expected_filename . '.tmp', $expected_filename);
|
||||
}
|
||||
else {
|
||||
// On the first try do not change the directory mtime but the filemtime
|
||||
// is now larger than the directory mtime.
|
||||
file_put_contents($expected_filename, $untrusted_code);
|
||||
}
|
||||
chmod($expected_filename, 0400);
|
||||
chmod($expected_directory, 0100);
|
||||
$this->assertSame(file_get_contents($expected_filename), $untrusted_code);
|
||||
$this->assertSame($this->expected[$i], $php->exists($name));
|
||||
$this->assertSame($this->expected[$i], $php->load($name));
|
||||
$this->assertSame($this->expected[$i], $GLOBALS['hacked']);
|
||||
}
|
||||
unset($GLOBALS['hacked']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Tests the MTimeProtectedFileStorage implementation.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFileStorage
|
||||
*
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class MTimeProtectedFileStorageTest extends MTimeProtectedFileStorageBase {
|
||||
|
||||
/**
|
||||
* The expected test results for the security test.
|
||||
*
|
||||
* The default implementation protects against even the filemtime change so
|
||||
* both iterations will return FALSE.
|
||||
*/
|
||||
protected $expected = [FALSE, FALSE];
|
||||
|
||||
/**
|
||||
* The PHP storage class to test.
|
||||
*/
|
||||
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\PhpStorageInterface;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Base test for PHP storages.
|
||||
*/
|
||||
abstract class PhpStorageTestBase extends TestCase {
|
||||
|
||||
/**
|
||||
* A unique per test class directory path to test php storage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
vfsStream::setup('exampleDir');
|
||||
$this->directory = vfsStream::url('exampleDir');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a PHP storage's load/save/delete operations work.
|
||||
*/
|
||||
public function assertCRUD($php) {
|
||||
// Random generator.
|
||||
$random_generator = new Random();
|
||||
|
||||
$name = $random_generator->name(8, TRUE) . '/' . $random_generator->name(8, TRUE) . '.php';
|
||||
|
||||
// Find a global that doesn't exist.
|
||||
do {
|
||||
$random = mt_rand(10000, 100000);
|
||||
} while (isset($GLOBALS[$random]));
|
||||
|
||||
// Write out a PHP file and ensure it's successfully loaded.
|
||||
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
|
||||
$success = $php->save($name, $code);
|
||||
$this->assertTrue($success, 'Saved php file');
|
||||
$php->load($name);
|
||||
$this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
|
||||
|
||||
// Run additional asserts.
|
||||
$this->additionalAssertCRUD($php, $name);
|
||||
|
||||
// If the file was successfully loaded, it must also exist, but ensure the
|
||||
// exists() method returns that correctly.
|
||||
$this->assertTrue($php->exists($name), 'Exists works correctly');
|
||||
|
||||
// Delete the file, and then ensure exists() returns FALSE.
|
||||
$this->assertTrue($php->delete($name), 'Delete succeeded');
|
||||
$this->assertFalse($php->exists($name), 'Delete deleted file');
|
||||
|
||||
// Ensure delete() can be called on a non-existing file. It should return
|
||||
// FALSE, but not trigger errors.
|
||||
$this->assertFalse($php->delete($name), 'Delete fails on missing file');
|
||||
unset($GLOBALS[$random]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional asserts to be run.
|
||||
*
|
||||
* @param \Drupal\Component\PhpStorage\PhpStorageInterface $php
|
||||
* The PHP storage object.
|
||||
* @param string $name
|
||||
* The name of an object. It should exist in the storage.
|
||||
*/
|
||||
protected function additionalAssertCRUD(PhpStorageInterface $php, $name) {
|
||||
// By default do not do any additional asserts. This is a way of extending
|
||||
// tests in contrib.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Context;
|
||||
|
||||
use Drupal\Component\Plugin\Context\Context;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Context\Context
|
||||
* @group Plugin
|
||||
*/
|
||||
class ContextTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Data provider for testGetContextValue.
|
||||
*/
|
||||
public function providerGetContextValue() {
|
||||
return [
|
||||
['context_value', 'context_value', FALSE, 'data_type'],
|
||||
[NULL, NULL, FALSE, 'data_type'],
|
||||
['will throw exception', NULL, TRUE, 'data_type'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getContextValue
|
||||
* @dataProvider providerGetContextValue
|
||||
*/
|
||||
public function testGetContextValue($expected, $context_value, $is_required, $data_type) {
|
||||
// Mock a Context object.
|
||||
$mock_context = $this->getMockBuilder('Drupal\Component\Plugin\Context\Context')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['getContextDefinition'])
|
||||
->getMock();
|
||||
|
||||
// If the context value exists, getContextValue() behaves like a normal
|
||||
// getter.
|
||||
if ($context_value) {
|
||||
// Set visibility of contextValue.
|
||||
$ref_context_value = new \ReflectionProperty($mock_context, 'contextValue');
|
||||
$ref_context_value->setAccessible(TRUE);
|
||||
// Set contextValue to a testable state.
|
||||
$ref_context_value->setValue($mock_context, $context_value);
|
||||
// Exercise getContextValue().
|
||||
$this->assertEquals($context_value, $mock_context->getContextValue());
|
||||
}
|
||||
// If no context value exists, we have to cover either returning NULL or
|
||||
// throwing an exception if the definition requires it.
|
||||
else {
|
||||
// Create a mock definition.
|
||||
$mock_definition = $this->getMockBuilder('Drupal\Component\Plugin\Context\ContextDefinitionInterface')
|
||||
->setMethods(['isRequired', 'getDataType'])
|
||||
->getMockForAbstractClass();
|
||||
|
||||
// Set expectation for isRequired().
|
||||
$mock_definition->expects($this->once())
|
||||
->method('isRequired')
|
||||
->willReturn($is_required);
|
||||
|
||||
// Set expectation for getDataType().
|
||||
$mock_definition->expects($this->exactly(
|
||||
$is_required ? 1 : 0
|
||||
))
|
||||
->method('getDataType')
|
||||
->willReturn($data_type);
|
||||
|
||||
// Set expectation for getContextDefinition().
|
||||
$mock_context->expects($this->once())
|
||||
->method('getContextDefinition')
|
||||
->willReturn($mock_definition);
|
||||
|
||||
// Set expectation for exception.
|
||||
if ($is_required) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('Drupal\Component\Plugin\Exception\ContextException');
|
||||
$this->expectExceptionMessage(sprintf("The %s context is required and not present.", $data_type));
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(
|
||||
'Drupal\Component\Plugin\Exception\ContextException',
|
||||
sprintf("The %s context is required and not present.", $data_type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise getContextValue().
|
||||
$this->assertEquals($context_value, $mock_context->getContextValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getContextValue
|
||||
*/
|
||||
public function testDefaultValue() {
|
||||
$mock_definition = $this->getMockBuilder('Drupal\Component\Plugin\Context\ContextDefinitionInterface')
|
||||
->setMethods(['getDefaultValue'])
|
||||
->getMockForAbstractClass();
|
||||
|
||||
$mock_definition->expects($this->once())
|
||||
->method('getDefaultValue')
|
||||
->willReturn('test');
|
||||
|
||||
$context = new Context($mock_definition);
|
||||
$this->assertEquals('test', $context->getContextValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli;
|
||||
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Corn;
|
||||
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Factory\DefaultFactory
|
||||
* @group Plugin
|
||||
*/
|
||||
class DefaultFactoryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a valid array plugin definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithValidArrayPluginDefinition() {
|
||||
$plugin_class = Corn::class;
|
||||
$class = DefaultFactory::getPluginClass('corn', ['class' => $plugin_class]);
|
||||
|
||||
$this->assertEquals($plugin_class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a valid object plugin definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithValidObjectPluginDefinition() {
|
||||
$plugin_class = Corn::class;
|
||||
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
|
||||
$plugin_definition->expects($this->atLeastOnce())
|
||||
->method('getClass')
|
||||
->willReturn($plugin_class);
|
||||
$class = DefaultFactory::getPluginClass('corn', $plugin_definition);
|
||||
|
||||
$this->assertEquals($plugin_class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a missing class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithMissingClassWithArrayPluginDefinition() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('The plugin (corn) did not specify an instance class.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class, 'The plugin (corn) did not specify an instance class.');
|
||||
}
|
||||
DefaultFactory::getPluginClass('corn', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a missing class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithMissingClassWithObjectPluginDefinition() {
|
||||
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('The plugin (corn) did not specify an instance class.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class, 'The plugin (corn) did not specify an instance class.');
|
||||
}
|
||||
DefaultFactory::getPluginClass('corn', $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a not existing class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('Plugin (carrot) instance class "Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot" does not exist.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class, 'Plugin (carrot) instance class "Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot" does not exist.');
|
||||
}
|
||||
DefaultFactory::getPluginClass('carrot', ['class' => 'Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a not existing class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition() {
|
||||
$plugin_class = 'Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot';
|
||||
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
|
||||
$plugin_definition->expects($this->atLeastOnce())
|
||||
->method('getClass')
|
||||
->willReturn($plugin_class);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class);
|
||||
}
|
||||
DefaultFactory::getPluginClass('carrot', $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a required interface.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithInterfaceWithArrayPluginDefinition() {
|
||||
$plugin_class = Corn::class;
|
||||
$class = DefaultFactory::getPluginClass('corn', ['class' => $plugin_class], VegetableInterface::class);
|
||||
|
||||
$this->assertEquals($plugin_class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a required interface.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithInterfaceWithObjectPluginDefinition() {
|
||||
$plugin_class = Corn::class;
|
||||
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
|
||||
$plugin_definition->expects($this->atLeastOnce())
|
||||
->method('getClass')
|
||||
->willReturn($plugin_class);
|
||||
$class = DefaultFactory::getPluginClass('corn', $plugin_definition, VegetableInterface::class);
|
||||
|
||||
$this->assertEquals($plugin_class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a required interface but no implementation.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithInterfaceAndInvalidClassWithArrayPluginDefinition() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('Plugin "corn" (Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli) must implement interface Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class, 'Plugin "corn" (Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli) must implement interface Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface.');
|
||||
}
|
||||
DefaultFactory::getPluginClass('corn', ['class' => Broccoli::class], VegetableInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getPluginClass() with a required interface but no implementation.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithInterfaceAndInvalidClassWithObjectPluginDefinition() {
|
||||
$plugin_class = Broccoli::class;
|
||||
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
|
||||
$plugin_definition->expects($this->atLeastOnce())
|
||||
->method('getClass')
|
||||
->willReturn($plugin_class);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginException::class);
|
||||
}
|
||||
DefaultFactory::getPluginClass('corn', $plugin_definition, VegetableInterface::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait
|
||||
* @uses \Drupal\Component\Plugin\Discovery\DiscoveryTrait
|
||||
* @group Plugin
|
||||
*/
|
||||
class DiscoveryCachedTraitTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Data provider for testGetDefinition().
|
||||
*
|
||||
* @return array
|
||||
* - Expected result from getDefinition().
|
||||
* - Cached definitions to be placed into self::$definitions
|
||||
* - Definitions to be returned by getDefinitions().
|
||||
* - Plugin name to query for.
|
||||
*/
|
||||
public function providerGetDefinition() {
|
||||
return [
|
||||
['definition', [], ['plugin_name' => 'definition'], 'plugin_name'],
|
||||
['definition', ['plugin_name' => 'definition'], [], 'plugin_name'],
|
||||
[NULL, ['plugin_name' => 'definition'], [], 'bad_plugin_name'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinition
|
||||
* @dataProvider providerGetDefinition
|
||||
*/
|
||||
public function testGetDefinition($expected, $cached_definitions, $get_definitions, $plugin_id) {
|
||||
// Mock a DiscoveryCachedTrait.
|
||||
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait');
|
||||
$reflection_definitions = new \ReflectionProperty($trait, 'definitions');
|
||||
$reflection_definitions->setAccessible(TRUE);
|
||||
// getDefinition() needs the ::$definitions property to be set in one of two
|
||||
// ways: 1) As existing cached data, or 2) as a side-effect of calling
|
||||
// getDefinitions().
|
||||
// If there are no cached definitions, then we have to fake the side-effect
|
||||
// of getDefinitions().
|
||||
if (count($cached_definitions) < 1) {
|
||||
$trait->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
// Use a callback method, so we can perform the side-effects.
|
||||
->willReturnCallback(function () use ($reflection_definitions, $trait, $get_definitions) {
|
||||
$reflection_definitions->setValue($trait, $get_definitions);
|
||||
return $get_definitions;
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Put $cached_definitions into our mocked ::$definitions.
|
||||
$reflection_definitions->setValue($trait, $cached_definitions);
|
||||
}
|
||||
// Call getDefinition(), with $exception_on_invalid always FALSE.
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$trait->getDefinition($plugin_id, FALSE)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @group Plugin
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryTrait
|
||||
*/
|
||||
class DiscoveryTraitTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Data provider for testDoGetDefinition().
|
||||
*
|
||||
* @return array
|
||||
* - Expected plugin definition.
|
||||
* - Plugin definition array, to pass to doGetDefinition().
|
||||
* - Plugin ID to get, passed to doGetDefinition().
|
||||
*/
|
||||
public function providerDoGetDefinition() {
|
||||
return [
|
||||
['definition', ['plugin_name' => 'definition'], 'plugin_name'],
|
||||
[NULL, ['plugin_name' => 'definition'], 'bad_plugin_name'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::doGetDefinition
|
||||
* @dataProvider providerDoGetDefinition
|
||||
*/
|
||||
public function testDoGetDefinition($expected, $definitions, $plugin_id) {
|
||||
// Mock the trait.
|
||||
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
|
||||
// Un-protect the method using reflection.
|
||||
$method_ref = new \ReflectionMethod($trait, 'doGetDefinition');
|
||||
$method_ref->setAccessible(TRUE);
|
||||
// Call doGetDefinition, with $exception_on_invalid always FALSE.
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$method_ref->invoke($trait, $definitions, $plugin_id, FALSE)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testDoGetDefinitionException()
|
||||
*
|
||||
* @return array
|
||||
* - Expected plugin definition.
|
||||
* - Plugin definition array, to pass to doGetDefinition().
|
||||
* - Plugin ID to get, passed to doGetDefinition().
|
||||
*/
|
||||
public function providerDoGetDefinitionException() {
|
||||
return [
|
||||
[FALSE, ['plugin_name' => 'definition'], 'bad_plugin_name'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::doGetDefinition
|
||||
* @dataProvider providerDoGetDefinitionException
|
||||
* @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
*/
|
||||
public function testDoGetDefinitionException($expected, $definitions, $plugin_id) {
|
||||
// Mock the trait.
|
||||
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
|
||||
// Un-protect the method using reflection.
|
||||
$method_ref = new \ReflectionMethod($trait, 'doGetDefinition');
|
||||
$method_ref->setAccessible(TRUE);
|
||||
// Call doGetDefinition, with $exception_on_invalid always TRUE.
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginNotFoundException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginNotFoundException::class);
|
||||
}
|
||||
$method_ref->invoke($trait, $definitions, $plugin_id, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinition
|
||||
* @dataProvider providerDoGetDefinition
|
||||
*/
|
||||
public function testGetDefinition($expected, $definitions, $plugin_id) {
|
||||
// Since getDefinition is a wrapper around doGetDefinition(), we can re-use
|
||||
// its data provider. We just have to tell abstract method getDefinitions()
|
||||
// to use the $definitions array.
|
||||
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
|
||||
$trait->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
// Call getDefinition(), with $exception_on_invalid always FALSE.
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$trait->getDefinition($plugin_id, FALSE)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinition
|
||||
* @dataProvider providerDoGetDefinitionException
|
||||
* @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
*/
|
||||
public function testGetDefinitionException($expected, $definitions, $plugin_id) {
|
||||
// Since getDefinition is a wrapper around doGetDefinition(), we can re-use
|
||||
// its data provider. We just have to tell abstract method getDefinitions()
|
||||
// to use the $definitions array.
|
||||
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
|
||||
$trait->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
// Call getDefinition(), with $exception_on_invalid always TRUE.
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(PluginNotFoundException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(PluginNotFoundException::class);
|
||||
}
|
||||
$trait->getDefinition($plugin_id, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testHasDefinition().
|
||||
*
|
||||
* @return array
|
||||
* - Expected TRUE or FALSE.
|
||||
* - Plugin ID to look for.
|
||||
*/
|
||||
public function providerHasDefinition() {
|
||||
return [
|
||||
[TRUE, 'valid'],
|
||||
[FALSE, 'not_valid'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::hasDefinition
|
||||
* @dataProvider providerHasDefinition
|
||||
*/
|
||||
public function testHasDefinition($expected, $plugin_id) {
|
||||
$trait = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryTrait')
|
||||
->setMethods(['getDefinition'])
|
||||
->getMockForTrait();
|
||||
// Set up our mocked getDefinition() to return TRUE for 'valid' and FALSE
|
||||
// for 'not_valid'.
|
||||
$trait->expects($this->once())
|
||||
->method('getDefinition')
|
||||
->will($this->returnValueMap([
|
||||
['valid', FALSE, TRUE],
|
||||
['not_valid', FALSE, FALSE],
|
||||
]));
|
||||
// Call hasDefinition().
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$trait->hasDefinition($plugin_id)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @group Plugin
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator
|
||||
*/
|
||||
class StaticDiscoveryDecoratorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Helper method to provide a mocked callback object with expectations.
|
||||
*
|
||||
* If there should be a registered definition, then we have to place a
|
||||
* \Callable in the mock object. The return value of this callback is
|
||||
* never used.
|
||||
*
|
||||
* @return \PHPUnit_Framework_MockObject_MockObject
|
||||
* Mocked object with expectation of registerDefinitionsCallback() being
|
||||
* called once.
|
||||
*/
|
||||
public function getRegisterDefinitionsCallback() {
|
||||
$mock_callable = $this->getMockBuilder('\stdClass')
|
||||
->setMethods(['registerDefinitionsCallback'])
|
||||
->getMock();
|
||||
// Set expectations for the callback method.
|
||||
$mock_callable->expects($this->once())
|
||||
->method('registerDefinitionsCallback');
|
||||
return $mock_callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetDefinitions().
|
||||
*
|
||||
* @return array
|
||||
* - Expected plugin definition.
|
||||
* - Whether we require the method to register definitions through a
|
||||
* callback.
|
||||
* - Whether to throw an exception if the definition is invalid.
|
||||
* - A plugin definition.
|
||||
* - Base plugin ID.
|
||||
*/
|
||||
public function providerGetDefinition() {
|
||||
return [
|
||||
['is_defined', TRUE, FALSE, ['plugin-definition' => 'is_defined'], 'plugin-definition'],
|
||||
// Make sure we don't call the decorated method if we shouldn't.
|
||||
['is_defined', FALSE, FALSE, ['plugin-definition' => 'is_defined'], 'plugin-definition'],
|
||||
// Return NULL for bad plugin id.
|
||||
[NULL, FALSE, FALSE, ['plugin-definition' => 'is_defined'], 'BAD-plugin-definition'],
|
||||
// Generate an exception.
|
||||
[NULL, FALSE, TRUE, ['plugin-definition' => 'is_defined'], 'BAD-plugin-definition'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinition
|
||||
* @dataProvider providerGetDefinition
|
||||
*/
|
||||
public function testGetDefinition($expected, $has_register_definitions, $exception_on_invalid, $definitions, $base_plugin_id) {
|
||||
// Mock our StaticDiscoveryDecorator.
|
||||
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['registeredDefintionCallback'])
|
||||
->getMock();
|
||||
|
||||
// Set up the ::$registerDefinitions property.
|
||||
$ref_register_definitions = new \ReflectionProperty($mock_decorator, 'registerDefinitions');
|
||||
$ref_register_definitions->setAccessible(TRUE);
|
||||
if ($has_register_definitions) {
|
||||
// Set the callback object on the mocked decorator.
|
||||
$ref_register_definitions->setValue(
|
||||
$mock_decorator,
|
||||
[$this->getRegisterDefinitionsCallback(), 'registerDefinitionsCallback']
|
||||
);
|
||||
}
|
||||
else {
|
||||
// There should be no registerDefinitions callback.
|
||||
$ref_register_definitions->setValue($mock_decorator, NULL);
|
||||
}
|
||||
|
||||
// Set up ::$definitions to an empty array.
|
||||
$ref_definitions = new \ReflectionProperty($mock_decorator, 'definitions');
|
||||
$ref_definitions->setAccessible(TRUE);
|
||||
$ref_definitions->setValue($mock_decorator, []);
|
||||
|
||||
// Mock a decorated object.
|
||||
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
|
||||
->setMethods(['getDefinitions'])
|
||||
->getMockForAbstractClass();
|
||||
// Return our definitions from getDefinitions().
|
||||
$mock_decorated->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
|
||||
// Set up ::$decorated to our mocked decorated object.
|
||||
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
|
||||
$ref_decorated->setAccessible(TRUE);
|
||||
$ref_decorated->setValue($mock_decorator, $mock_decorated);
|
||||
|
||||
if ($exception_on_invalid) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('Drupal\Component\Plugin\Exception\PluginNotFoundException');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException('Drupal\Component\Plugin\Exception\PluginNotFoundException');
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise getDefinition(). It calls parent::getDefinition().
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$mock_decorator->getDefinition($base_plugin_id, $exception_on_invalid)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetDefinitions().
|
||||
*
|
||||
* @return array
|
||||
* - bool Whether the test mock has a callback.
|
||||
* - array Plugin definitions.
|
||||
*/
|
||||
public function providerGetDefinitions() {
|
||||
return [
|
||||
[TRUE, ['definition' => 'is_fake']],
|
||||
[FALSE, ['definition' => 'array_of_stuff']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
* @dataProvider providerGetDefinitions
|
||||
*/
|
||||
public function testGetDefinitions($has_register_definitions, $definitions) {
|
||||
// Mock our StaticDiscoveryDecorator.
|
||||
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['registeredDefintionCallback'])
|
||||
->getMock();
|
||||
|
||||
// Set up the ::$registerDefinitions property.
|
||||
$ref_register_definitions = new \ReflectionProperty($mock_decorator, 'registerDefinitions');
|
||||
$ref_register_definitions->setAccessible(TRUE);
|
||||
if ($has_register_definitions) {
|
||||
// Set the callback object on the mocked decorator.
|
||||
$ref_register_definitions->setValue(
|
||||
$mock_decorator,
|
||||
[$this->getRegisterDefinitionsCallback(), 'registerDefinitionsCallback']
|
||||
);
|
||||
}
|
||||
else {
|
||||
// There should be no registerDefinitions callback.
|
||||
$ref_register_definitions->setValue($mock_decorator, NULL);
|
||||
}
|
||||
|
||||
// Set up ::$definitions to an empty array.
|
||||
$ref_definitions = new \ReflectionProperty($mock_decorator, 'definitions');
|
||||
$ref_definitions->setAccessible(TRUE);
|
||||
$ref_definitions->setValue($mock_decorator, []);
|
||||
|
||||
// Mock a decorated object.
|
||||
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
|
||||
->setMethods(['getDefinitions'])
|
||||
->getMockForAbstractClass();
|
||||
// Our mocked method will return any arguments sent to it.
|
||||
$mock_decorated->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
|
||||
// Set up ::$decorated to our mocked decorated object.
|
||||
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
|
||||
$ref_decorated->setAccessible(TRUE);
|
||||
$ref_decorated->setValue($mock_decorator, $mock_decorated);
|
||||
|
||||
// Exercise getDefinitions(). It calls parent::getDefinitions() but in this
|
||||
// case there will be no side-effects.
|
||||
$this->assertEquals(
|
||||
$definitions,
|
||||
$mock_decorator->getDefinitions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testCall().
|
||||
*
|
||||
* @return array
|
||||
* - Method name.
|
||||
* - Array of arguments to pass to the method, with the expectation that our
|
||||
* mocked __call() will return them.
|
||||
*/
|
||||
public function providerCall() {
|
||||
return [
|
||||
['complexArguments', ['1', 2.0, 3, ['4' => 'five']]],
|
||||
['noArguments', []],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__call
|
||||
* @dataProvider providerCall
|
||||
*/
|
||||
public function testCall($method, $args) {
|
||||
// Mock a decorated object.
|
||||
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
|
||||
->setMethods([$method])
|
||||
->getMockForAbstractClass();
|
||||
// Our mocked method will return any arguments sent to it.
|
||||
$mock_decorated->expects($this->once())
|
||||
->method($method)
|
||||
->willReturnCallback(
|
||||
function () {
|
||||
return \func_get_args();
|
||||
}
|
||||
);
|
||||
|
||||
// Create a mock decorator.
|
||||
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
// Poke the decorated object into our decorator.
|
||||
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
|
||||
$ref_decorated->setAccessible(TRUE);
|
||||
$ref_decorated->setValue($mock_decorator, $mock_decorated);
|
||||
|
||||
// Exercise __call.
|
||||
$this->assertEquals(
|
||||
$args,
|
||||
\call_user_func_array([$mock_decorated, $method], $args)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\Plugin\Factory\ReflectionFactoryTest.
|
||||
*
|
||||
* Also contains Argument* classes used as data for testing.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Factory;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\ReflectionFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @group Plugin
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Factory\ReflectionFactory
|
||||
*/
|
||||
class ReflectionFactoryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Data provider for testGetInstanceArguments.
|
||||
*
|
||||
* The classes used here are defined at the bottom of this file.
|
||||
*
|
||||
* @return array
|
||||
* - Expected output.
|
||||
* - Class to reflect for input to getInstanceArguments().
|
||||
* - $plugin_id parameter to getInstanceArguments().
|
||||
* - $plugin_definition parameter to getInstanceArguments().
|
||||
* - $configuration parameter to getInstanceArguments().
|
||||
*/
|
||||
public function providerGetInstanceArguments() {
|
||||
return [
|
||||
[
|
||||
['arguments_plugin_id'],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsPluginId',
|
||||
'arguments_plugin_id',
|
||||
['arguments_plugin_id' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsPluginId']],
|
||||
[],
|
||||
],
|
||||
[
|
||||
[[], ['arguments_many' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany']], 'arguments_many', 'default_value', 'what_default'],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany',
|
||||
'arguments_many',
|
||||
['arguments_many' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany']],
|
||||
[],
|
||||
],
|
||||
[
|
||||
// Config array key exists and is set.
|
||||
['thing'],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey',
|
||||
'arguments_config_array_key',
|
||||
['arguments_config_array_key' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey']],
|
||||
['config_name' => 'thing'],
|
||||
],
|
||||
[
|
||||
// Config array key exists and is not set.
|
||||
[NULL],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey',
|
||||
'arguments_config_array_key',
|
||||
['arguments_config_array_key' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey']],
|
||||
['config_name' => NULL],
|
||||
],
|
||||
[
|
||||
// Touch the else clause at the end of the method.
|
||||
[NULL, NULL, NULL, NULL],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsAllNull',
|
||||
'arguments_all_null',
|
||||
['arguments_all_null' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsAllNull']],
|
||||
[],
|
||||
],
|
||||
[
|
||||
// A plugin with no constructor.
|
||||
[NULL, NULL, NULL, NULL],
|
||||
'Drupal\Tests\Component\Plugin\Factory\ArgumentsNoConstructor',
|
||||
'arguments_no_constructor',
|
||||
['arguments_no_constructor' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsNoConstructor']],
|
||||
[],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::createInstance
|
||||
* @dataProvider providerGetInstanceArguments
|
||||
*/
|
||||
public function testCreateInstance($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration) {
|
||||
// Create a mock DiscoveryInterface which can return our plugin definition.
|
||||
$mock_discovery = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
|
||||
->setMethods(['getDefinition', 'getDefinitions', 'hasDefinition'])
|
||||
->getMock();
|
||||
$mock_discovery->expects($this->never())->method('getDefinitions');
|
||||
$mock_discovery->expects($this->never())->method('hasDefinition');
|
||||
$mock_discovery->expects($this->once())
|
||||
->method('getDefinition')
|
||||
->willReturn($plugin_definition);
|
||||
|
||||
// Create a stub ReflectionFactory object. We use StubReflectionFactory
|
||||
// because createInstance() has a dependency on a static method.
|
||||
// StubReflectionFactory overrides this static method.
|
||||
$reflection_factory = new StubReflectionFactory($mock_discovery);
|
||||
|
||||
// Finally test that createInstance() returns an object of the class we
|
||||
// want.
|
||||
$this->assertInstanceOf($reflector_name, $reflection_factory->createInstance($plugin_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getInstanceArguments
|
||||
* @dataProvider providerGetInstanceArguments
|
||||
*/
|
||||
public function testGetInstanceArguments($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration) {
|
||||
$reflection_factory = $this->getMockBuilder('Drupal\Component\Plugin\Factory\ReflectionFactory')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$get_instance_arguments_ref = new \ReflectionMethod($reflection_factory, 'getInstanceArguments');
|
||||
$get_instance_arguments_ref->setAccessible(TRUE);
|
||||
|
||||
// Special case for plugin class without a constructor.
|
||||
// getInstanceArguments() throws an exception if there's no constructor.
|
||||
// This is not a documented behavior of getInstanceArguments(), but allows
|
||||
// us to use one data set for this test method as well as
|
||||
// testCreateInstance().
|
||||
if ($plugin_id == 'arguments_no_constructor') {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('\ReflectionException');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException('\ReflectionException');
|
||||
}
|
||||
}
|
||||
|
||||
// Finally invoke getInstanceArguments() on our mocked factory.
|
||||
$ref = new \ReflectionClass($reflector_name);
|
||||
$result = $get_instance_arguments_ref->invoke(
|
||||
$reflection_factory, $ref, $plugin_id, $plugin_definition, $configuration);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Override ReflectionFactory because ::createInstance() calls a static method.
|
||||
*
|
||||
* We have to override getPluginClass so that we can stub out its return value.
|
||||
*/
|
||||
class StubReflectionFactory extends ReflectionFactory {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getPluginClass($plugin_id, $plugin_definition = NULL, $required_interface = NULL) {
|
||||
// Return the class name from the plugin definition.
|
||||
return $plugin_definition[$plugin_id]['class'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub class used by testGetInstanceArguments().
|
||||
*
|
||||
* @see providerGetInstanceArguments()
|
||||
*/
|
||||
class ArgumentsPluginId {
|
||||
|
||||
public function __construct($plugin_id) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub class used by testGetInstanceArguments().
|
||||
*
|
||||
* @see providerGetInstanceArguments()
|
||||
*/
|
||||
class ArgumentsMany {
|
||||
|
||||
public function __construct($configuration, $plugin_definition, $plugin_id, $foo = 'default_value', $what_am_i_doing_here = 'what_default') {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub class used by testGetInstanceArguments().
|
||||
*
|
||||
* @see providerGetInstanceArguments()
|
||||
*/
|
||||
class ArgumentsConfigArrayKey {
|
||||
|
||||
public function __construct($config_name) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub class used by testGetInstanceArguments().
|
||||
*
|
||||
* @see providerGetInstanceArguments()
|
||||
*/
|
||||
class ArgumentsAllNull {
|
||||
|
||||
public function __construct($charismatic, $demure, $delightful, $electrostatic) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub class used by testGetInstanceArguments().
|
||||
*
|
||||
* @see providerGetInstanceArguments()
|
||||
*/
|
||||
class ArgumentsNoConstructor {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
|
||||
|
||||
/**
|
||||
* @Plugin(
|
||||
* id = "broccoli",
|
||||
* label = "Broccoli",
|
||||
* color = "green"
|
||||
* )
|
||||
*/
|
||||
class Broccoli {}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
|
||||
|
||||
/**
|
||||
* @Plugin(
|
||||
* id = "corn",
|
||||
* label = "Corn",
|
||||
* color = "yellow"
|
||||
* )
|
||||
*/
|
||||
class Corn implements VegetableInterface {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
|
||||
|
||||
/**
|
||||
* Provides an interface for test plugins.
|
||||
*/
|
||||
interface VegetableInterface {}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\PluginBase
|
||||
* @group Plugin
|
||||
*/
|
||||
class PluginBaseTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestGetPluginId
|
||||
* @covers ::getPluginId
|
||||
*/
|
||||
public function testGetPluginId($plugin_id, $expected) {
|
||||
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
|
||||
[],
|
||||
$plugin_id,
|
||||
[],
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $plugin_base->getPluginId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns test data for testGetPluginId().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestGetPluginId() {
|
||||
return [
|
||||
['base_id', 'base_id'],
|
||||
['base_id:derivative', 'base_id:derivative'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestGetBaseId
|
||||
* @coves ::getBaseId
|
||||
*/
|
||||
public function testGetBaseId($plugin_id, $expected) {
|
||||
/** @var \Drupal\Component\Plugin\PluginBase|\PHPUnit_Framework_MockObject_MockObject $plugin_base */
|
||||
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
|
||||
[],
|
||||
$plugin_id,
|
||||
[],
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $plugin_base->getBaseId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns test data for testGetBaseId().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestGetBaseId() {
|
||||
return [
|
||||
['base_id', 'base_id'],
|
||||
['base_id:derivative', 'base_id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestGetDerivativeId
|
||||
* @covers ::getDerivativeId
|
||||
*/
|
||||
public function testGetDerivativeId($plugin_id = NULL, $expected = NULL) {
|
||||
/** @var \Drupal\Component\Plugin\PluginBase|\PHPUnit_Framework_MockObject_MockObject $plugin_base */
|
||||
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
|
||||
[],
|
||||
$plugin_id,
|
||||
[],
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $plugin_base->getDerivativeId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns test data for testGetDerivativeId().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestGetDerivativeId() {
|
||||
return [
|
||||
['base_id', NULL],
|
||||
['base_id:derivative', 'derivative'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getPluginDefinition
|
||||
*/
|
||||
public function testGetPluginDefinition() {
|
||||
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
|
||||
[],
|
||||
'plugin_id',
|
||||
['value', ['key' => 'value']],
|
||||
]);
|
||||
|
||||
$this->assertEquals(['value', ['key' => 'value']], $plugin_base->getPluginDefinition());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\Component\Plugin\Mapper\MapperInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\PluginManagerBase
|
||||
* @group Plugin
|
||||
*/
|
||||
class PluginManagerBaseTest extends TestCase {
|
||||
|
||||
/**
|
||||
* A callback method for mocking FactoryInterface objects.
|
||||
*/
|
||||
public function createInstanceCallback() {
|
||||
$args = func_get_args();
|
||||
$plugin_id = $args[0];
|
||||
$configuration = $args[1];
|
||||
if ('invalid' == $plugin_id) {
|
||||
throw new PluginNotFoundException($plugin_id);
|
||||
}
|
||||
return [
|
||||
'plugin_id' => $plugin_id,
|
||||
'configuration' => $configuration,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a mocked FactoryInterface object with known properties.
|
||||
*/
|
||||
public function getMockFactoryInterface($expects_count) {
|
||||
$mock_factory = $this->getMockBuilder('Drupal\Component\Plugin\Factory\FactoryInterface')
|
||||
->setMethods(['createInstance'])
|
||||
->getMockForAbstractClass();
|
||||
$mock_factory->expects($this->exactly($expects_count))
|
||||
->method('createInstance')
|
||||
->willReturnCallback([$this, 'createInstanceCallback']);
|
||||
return $mock_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests createInstance() with no fallback methods.
|
||||
*
|
||||
* @covers ::createInstance
|
||||
*/
|
||||
public function testCreateInstance() {
|
||||
$manager = $this->getMockBuilder('Drupal\Component\Plugin\PluginManagerBase')
|
||||
->getMockForAbstractClass();
|
||||
// PluginManagerBase::createInstance() looks for a factory object and then
|
||||
// calls createInstance() on it. So we have to mock a factory object.
|
||||
$factory_ref = new \ReflectionProperty($manager, 'factory');
|
||||
$factory_ref->setAccessible(TRUE);
|
||||
$factory_ref->setValue($manager, $this->getMockFactoryInterface(1));
|
||||
|
||||
// Finally the test.
|
||||
$configuration_array = ['config' => 'something'];
|
||||
$result = $manager->createInstance('valid', $configuration_array);
|
||||
$this->assertEquals('valid', $result['plugin_id']);
|
||||
$this->assertEquals($configuration_array, $result['configuration']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests createInstance() with a fallback method.
|
||||
*
|
||||
* @covers ::createInstance
|
||||
*/
|
||||
public function testCreateInstanceFallback() {
|
||||
// We use our special stub class which extends PluginManagerBase and also
|
||||
// implements FallbackPluginManagerInterface.
|
||||
$manager = new StubFallbackPluginManager();
|
||||
// Put our stubbed factory on the base object.
|
||||
$factory_ref = new \ReflectionProperty($manager, 'factory');
|
||||
$factory_ref->setAccessible(TRUE);
|
||||
|
||||
// Set up the configuration array.
|
||||
$configuration_array = ['config' => 'something'];
|
||||
|
||||
// Test with fallback interface and valid plugin_id.
|
||||
$factory_ref->setValue($manager, $this->getMockFactoryInterface(1));
|
||||
$no_fallback_result = $manager->createInstance('valid', $configuration_array);
|
||||
$this->assertEquals('valid', $no_fallback_result['plugin_id']);
|
||||
$this->assertEquals($configuration_array, $no_fallback_result['configuration']);
|
||||
|
||||
// Test with fallback interface and invalid plugin_id.
|
||||
$factory_ref->setValue($manager, $this->getMockFactoryInterface(2));
|
||||
$fallback_result = $manager->createInstance('invalid', $configuration_array);
|
||||
$this->assertEquals('invalid_fallback', $fallback_result['plugin_id']);
|
||||
$this->assertEquals($configuration_array, $fallback_result['configuration']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getInstance
|
||||
*/
|
||||
public function testGetInstance() {
|
||||
$options = [
|
||||
'foo' => 'F00',
|
||||
'bar' => 'bAr',
|
||||
];
|
||||
$instance = new \stdClass();
|
||||
$mapper = $this->prophesize(MapperInterface::class);
|
||||
$mapper->getInstance($options)
|
||||
->shouldBeCalledTimes(1)
|
||||
->willReturn($instance);
|
||||
$manager = new StubPluginManagerBaseWithMapper($mapper->reveal());
|
||||
$this->assertEquals($instance, $manager->getInstance($options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getInstance
|
||||
*/
|
||||
public function testGetInstanceWithoutMapperShouldThrowException() {
|
||||
$options = [
|
||||
'foo' => 'F00',
|
||||
'bar' => 'bAr',
|
||||
];
|
||||
/** @var \Drupal\Component\Plugin\PluginManagerBase $manager */
|
||||
$manager = $this->getMockBuilder(PluginManagerBase::class)
|
||||
->getMockForAbstractClass();
|
||||
// Set the expected exception thrown by ::getInstance.
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$this->expectExceptionMessage(sprintf('%s does not support this method unless %s::$mapper is set.', get_class($manager), get_class($manager)));
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\BadMethodCallException::class, sprintf('%s does not support this method unless %s::$mapper is set.', get_class($manager), get_class($manager)));
|
||||
}
|
||||
$manager->getInstance($options);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
|
||||
/**
|
||||
* Stubs \Drupal\Component\Plugin\FallbackPluginManagerInterface.
|
||||
*
|
||||
* We have to stub \Drupal\Component\Plugin\FallbackPluginManagerInterface for
|
||||
* \Drupal\Tests\Component\Plugin\PluginManagerBaseTest so that we can
|
||||
* implement ::getFallbackPluginId().
|
||||
*
|
||||
* We do this so we can have it just return the plugin ID passed to it, with
|
||||
* '_fallback' appended.
|
||||
*/
|
||||
class StubFallbackPluginManager extends PluginManagerBase implements FallbackPluginManagerInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFallbackPluginId($plugin_id, array $configuration = []) {
|
||||
// Minimally implement getFallbackPluginId so that we can test it.
|
||||
return $plugin_id . '_fallback';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Mapper\MapperInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
|
||||
/**
|
||||
* Stubs \Drupal\Component\Plugin\PluginManagerBase to take a MapperInterface.
|
||||
*/
|
||||
final class StubPluginManagerBaseWithMapper extends PluginManagerBase {
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Mapper\MapperInterface $mapper
|
||||
*/
|
||||
public function __construct(MapperInterface $mapper) {
|
||||
$this->mapper = $mapper;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,445 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\ProxyBuilder\ProxyBuilderTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\ProxyBuilder;
|
||||
|
||||
use Drupal\Component\ProxyBuilder\ProxyBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\ProxyBuilder\ProxyBuilder
|
||||
* @group proxy_builder
|
||||
*/
|
||||
class ProxyBuilderTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The tested proxy builder.
|
||||
*
|
||||
* @var \Drupal\Component\ProxyBuilder\ProxyBuilder
|
||||
*/
|
||||
protected $proxyBuilder;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->proxyBuilder = new ProxyBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildProxyClassName
|
||||
*/
|
||||
public function testBuildProxyClassName() {
|
||||
$class_name = $this->proxyBuilder->buildProxyClassName('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod');
|
||||
$this->assertEquals('Drupal\Tests\ProxyClass\Component\ProxyBuilder\TestServiceNoMethod', $class_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildProxyClassName
|
||||
*/
|
||||
public function testBuildProxyClassNameForModule() {
|
||||
$class_name = $this->proxyBuilder->buildProxyClassName('Drupal\views_ui\ParamConverter\ViewUIConverter');
|
||||
$this->assertEquals('Drupal\views_ui\ProxyClass\ParamConverter\ViewUIConverter', $class_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildProxyNamespace
|
||||
*/
|
||||
public function testBuildProxyNamespace() {
|
||||
$class_name = $this->proxyBuilder->buildProxyNamespace('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod');
|
||||
$this->assertEquals('Drupal\Tests\ProxyClass\Component\ProxyBuilder', $class_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic methods like the constructor and the lazyLoadItself method.
|
||||
*
|
||||
* @covers ::build
|
||||
* @covers ::buildConstructorMethod
|
||||
* @covers ::buildLazyLoadItselfMethod
|
||||
*/
|
||||
public function testBuildNoMethod() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
$this->assertEquals($this->buildExpectedClass($class, ''), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildSimpleMethod() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceSimpleMethod';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function method()
|
||||
{
|
||||
return $this->lazyLoadItself()->method();
|
||||
}
|
||||
|
||||
EOS;
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildParameter
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildMethodWithParameter() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceMethodWithParameter';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function methodWithParameter($parameter)
|
||||
{
|
||||
return $this->lazyLoadItself()->methodWithParameter($parameter);
|
||||
}
|
||||
|
||||
EOS;
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildParameter
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildComplexMethod() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceComplexMethod';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
// @todo Solve the silly linebreak for array()
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function complexMethod($parameter, callable $function, \Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod $test_service = NULL, array &$elements = array (
|
||||
))
|
||||
{
|
||||
return $this->lazyLoadItself()->complexMethod($parameter, $function, $test_service, $elements);
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildReturnReference() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceReturnReference';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
// @todo Solve the silly linebreak for array()
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &returnReference()
|
||||
{
|
||||
return $this->lazyLoadItself()->returnReference();
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildParameter
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildWithInterface() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithInterface';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testMethod($parameter)
|
||||
{
|
||||
return $this->lazyLoadItself()->testMethod($parameter);
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$interface_string = ' implements \Drupal\Tests\Component\ProxyBuilder\TestInterface';
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body, $interface_string), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::build
|
||||
*/
|
||||
public function testBuildWithNestedInterface() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithChildInterfaces';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
$method_body = '';
|
||||
|
||||
$interface_string = ' implements \Drupal\Tests\Component\ProxyBuilder\TestChildInterface';
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body, $interface_string), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildParameter
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildWithProtectedAndPrivateMethod() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithProtectedMethods';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testMethod($parameter)
|
||||
{
|
||||
return $this->lazyLoadItself()->testMethod($parameter);
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::buildMethod
|
||||
* @covers ::buildParameter
|
||||
* @covers ::buildMethodBody
|
||||
*/
|
||||
public function testBuildWithPublicStaticMethod() {
|
||||
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithPublicStaticMethod';
|
||||
|
||||
$result = $this->proxyBuilder->build($class);
|
||||
|
||||
// Ensure that the static method is not wrapped.
|
||||
$method_body = <<<'EOS'
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function testMethod($parameter)
|
||||
{
|
||||
\Drupal\Tests\Component\ProxyBuilder\TestServiceWithPublicStaticMethod::testMethod($parameter);
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the expected class output.
|
||||
*
|
||||
* @param string $expected_methods_body
|
||||
* The expected body of decorated methods.
|
||||
*
|
||||
* @return string
|
||||
* The code of the entire proxy.
|
||||
*/
|
||||
protected function buildExpectedClass($class, $expected_methods_body, $interface_string = '') {
|
||||
$namespace = ProxyBuilder::buildProxyNamespace($class);
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$proxy_class = $reflection->getShortName();
|
||||
|
||||
$expected_string = <<<'EOS'
|
||||
|
||||
namespace {{ namespace }} {
|
||||
|
||||
/**
|
||||
* Provides a proxy class for \{{ class }}.
|
||||
*
|
||||
* @see \Drupal\Component\ProxyBuilder
|
||||
*/
|
||||
class {{ proxy_class }}{{ interface_string }}
|
||||
{
|
||||
|
||||
/**
|
||||
* The id of the original proxied service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $drupalProxyOriginalServiceId;
|
||||
|
||||
/**
|
||||
* The real proxied service, after it was lazy loaded.
|
||||
*
|
||||
* @var \{{ class }}
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* The service container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructs a ProxyClass Drupal proxy object.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The container.
|
||||
* @param string $drupal_proxy_original_service_id
|
||||
* The service ID of the original service.
|
||||
*/
|
||||
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy loads the real service from the container.
|
||||
*
|
||||
* @return object
|
||||
* Returns the constructed real service.
|
||||
*/
|
||||
protected function lazyLoadItself()
|
||||
{
|
||||
if (!isset($this->service)) {
|
||||
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
{{ expected_methods_body }}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EOS;
|
||||
|
||||
$expected_methods_body = implode("\n", array_map(function ($value) {
|
||||
if ($value === '') {
|
||||
return $value;
|
||||
}
|
||||
return " $value";
|
||||
}, explode("\n", $expected_methods_body)));
|
||||
|
||||
$expected_string = str_replace('{{ proxy_class }}', $proxy_class, $expected_string);
|
||||
$expected_string = str_replace('{{ namespace }}', $namespace, $expected_string);
|
||||
$expected_string = str_replace('{{ class }}', $class, $expected_string);
|
||||
$expected_string = str_replace('{{ expected_methods_body }}', $expected_methods_body, $expected_string);
|
||||
$expected_string = str_replace('{{ interface_string }}', $interface_string, $expected_string);
|
||||
|
||||
return $expected_string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceNoMethod {
|
||||
|
||||
}
|
||||
|
||||
class TestServiceSimpleMethod {
|
||||
|
||||
public function method() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceMethodWithParameter {
|
||||
|
||||
public function methodWithParameter($parameter) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceComplexMethod {
|
||||
|
||||
public function complexMethod($parameter, callable $function, TestServiceNoMethod $test_service = NULL, array &$elements = []) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceReturnReference {
|
||||
|
||||
public function &returnReference() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface TestInterface {
|
||||
|
||||
public function testMethod($parameter);
|
||||
|
||||
}
|
||||
|
||||
class TestServiceWithInterface implements TestInterface {
|
||||
|
||||
public function testMethod($parameter) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceWithProtectedMethods {
|
||||
|
||||
public function testMethod($parameter) {
|
||||
|
||||
}
|
||||
|
||||
protected function protectedMethod($parameter) {
|
||||
|
||||
}
|
||||
|
||||
protected function privateMethod($parameter) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestServiceWithPublicStaticMethod {
|
||||
|
||||
public static function testMethod($parameter) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface TestBaseInterface {
|
||||
|
||||
}
|
||||
|
||||
interface TestChildInterface extends TestBaseInterface {
|
||||
|
||||
}
|
||||
|
||||
class TestServiceWithChildInterfaces implements TestChildInterface {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Render;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the TranslatableMarkup class.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Render\FormattableMarkup
|
||||
* @group utility
|
||||
*/
|
||||
class FormattableMarkupTest extends TestCase {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__toString
|
||||
* @dataProvider providerTestUnexpectedPlaceholder
|
||||
*/
|
||||
public function testUnexpectedPlaceholder($string, $arguments, $error_number, $error_message) {
|
||||
// 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.
|
||||
$markup = new FormattableMarkup($string, $arguments);
|
||||
// Cast it to a string which will generate the errors.
|
||||
$output = (string) $markup;
|
||||
restore_error_handler();
|
||||
// The string should not change.
|
||||
$this->assertEquals($string, $output);
|
||||
$this->assertEquals($error_number, $this->lastErrorNumber);
|
||||
$this->assertEquals($error_message, $this->lastErrorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for FormattableMarkupTest::testUnexpectedPlaceholder().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestUnexpectedPlaceholder() {
|
||||
return [
|
||||
['Non alpha starting character: ~placeholder', ['~placeholder' => 'replaced'], E_USER_ERROR, 'Invalid placeholder (~placeholder) in string: Non alpha starting character: ~placeholder'],
|
||||
['Alpha starting character: placeholder', ['placeholder' => 'replaced'], E_USER_DEPRECATED, 'Invalid placeholder (placeholder) in string: Alpha starting character: placeholder'],
|
||||
// Ensure that where the placeholder is located in the the string is
|
||||
// irrelevant.
|
||||
['placeholder', ['placeholder' => 'replaced'], E_USER_DEPRECATED, 'Invalid placeholder (placeholder) in string: placeholder'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Render;
|
||||
|
||||
use Drupal\Component\Render\HtmlEscapedText;
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the HtmlEscapedText class.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Render\HtmlEscapedText
|
||||
* @group utility
|
||||
*/
|
||||
class HtmlEscapedTextTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @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()
|
||||
*/
|
||||
public function providerToString() {
|
||||
// Checks that invalid multi-byte sequences are escaped.
|
||||
$tests[] = ["Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"'];
|
||||
$tests[] = ["\xc2\"", '<27>"', 'Escapes invalid sequence "\xc2\""'];
|
||||
$tests[] = ["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[] = [$script_tag, '<script>', 'Escapes <script> even inside an object that implements MarkupInterface.'];
|
||||
$tests[] = ["<script>", '<script>', 'Escapes <script>'];
|
||||
$tests[] = ['<>&"\'', '<>&"'', 'Escapes reserved HTML characters.'];
|
||||
$specialchars = $this->prophesize(MarkupInterface::class);
|
||||
$specialchars->__toString()->willReturn('<>&"\'');
|
||||
$specialchars = $specialchars->reveal();
|
||||
$tests[] = [$specialchars, '<>&"'', '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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Render;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Render\PlainTextOutput
|
||||
* @group Utility
|
||||
*/
|
||||
class PlainTextOutputTest extends TestCase {
|
||||
|
||||
/**
|
||||
* 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 = new FormattableMarkup($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 <em> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Serialization;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Serialization\Json
|
||||
* @group Serialization
|
||||
*/
|
||||
class JsonTest extends TestCase {
|
||||
|
||||
/**
|
||||
* A test string with the full ASCII table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $string;
|
||||
|
||||
/**
|
||||
* An array of unsafe html characters which has to be encoded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $htmlUnsafe;
|
||||
|
||||
/**
|
||||
* An array of unsafe html characters which are already escaped.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $htmlUnsafeEscaped;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Setup a string with the full ASCII table.
|
||||
// @todo: Add tests for non-ASCII characters and Unicode.
|
||||
$this->string = '';
|
||||
for ($i = 1; $i < 128; $i++) {
|
||||
$this->string .= chr($i);
|
||||
}
|
||||
|
||||
// Characters that must be escaped.
|
||||
// We check for unescaped " separately.
|
||||
$this->htmlUnsafe = ['<', '>', '\'', '&'];
|
||||
// The following are the encoded forms of: < > ' & "
|
||||
$this->htmlUnsafeEscaped = ['\u003C', '\u003E', '\u0027', '\u0026', '\u0022'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests encoding for every ASCII character.
|
||||
*/
|
||||
public function testEncodingAscii() {
|
||||
// Verify there aren't character encoding problems with the source string.
|
||||
$this->assertSame(127, strlen($this->string), 'A string with the full ASCII table has the correct length.');
|
||||
foreach ($this->htmlUnsafe as $char) {
|
||||
$this->assertTrue(strpos($this->string, $char) > 0, sprintf('A string with the full ASCII table includes %s.', $char));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests encoding length.
|
||||
*/
|
||||
public function testEncodingLength() {
|
||||
// Verify that JSON encoding produces a string with all of the characters.
|
||||
$json = Json::encode($this->string);
|
||||
$this->assertTrue(strlen($json) > strlen($this->string), 'A JSON encoded string is larger than the source string.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests end and start of the encoded string.
|
||||
*/
|
||||
public function testEncodingStartEnd() {
|
||||
$json = Json::encode($this->string);
|
||||
// The first and last characters should be ", and no others.
|
||||
$this->assertTrue($json[0] == '"', 'A JSON encoded string begins with ".');
|
||||
$this->assertTrue($json[strlen($json) - 1] == '"', 'A JSON encoded string ends with ".');
|
||||
$this->assertTrue(substr_count($json, '"') == 2, 'A JSON encoded string contains exactly two ".');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests converting PHP variables to JSON strings and back.
|
||||
*/
|
||||
public function testReversibility() {
|
||||
$json = Json::encode($this->string);
|
||||
// Verify that encoding/decoding is reversible.
|
||||
$json_decoded = Json::decode($json);
|
||||
$this->assertSame($this->string, $json_decoded, 'Encoding a string to JSON and decoding back results in the original string.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the reversibility of structured data
|
||||
*/
|
||||
public function testStructuredReversibility() {
|
||||
// Verify reversibility for structured data. Also verify that necessary
|
||||
// characters are escaped.
|
||||
$source = [TRUE, FALSE, 0, 1, '0', '1', $this->string, ['key1' => $this->string, 'key2' => ['nested' => TRUE]]];
|
||||
$json = Json::encode($source);
|
||||
foreach ($this->htmlUnsafe as $char) {
|
||||
$this->assertTrue(strpos($json, $char) === FALSE, sprintf('A JSON encoded string does not contain %s.', $char));
|
||||
}
|
||||
// Verify that JSON encoding escapes the HTML unsafe characters
|
||||
foreach ($this->htmlUnsafeEscaped as $char) {
|
||||
$this->assertTrue(strpos($json, $char) > 0, sprintf('A JSON encoded string contains %s.', $char));
|
||||
}
|
||||
$json_decoded = Json::decode($json);
|
||||
$this->assertNotSame($source, $json, 'An array encoded in JSON is identical to the source.');
|
||||
$this->assertSame($source, $json_decoded, 'Encoding structured data to JSON and decoding back not results in the original data.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Serialization;
|
||||
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use Drupal\Component\Serialization\YamlPecl;
|
||||
|
||||
/**
|
||||
* Tests the YamlPecl serialization implementation.
|
||||
*
|
||||
* @group Drupal
|
||||
* @group Serialization
|
||||
* @coversDefaultClass \Drupal\Component\Serialization\YamlPecl
|
||||
* @requires extension yaml
|
||||
*/
|
||||
class YamlPeclTest extends YamlTestBase {
|
||||
|
||||
/**
|
||||
* Tests encoding and decoding basic data structures.
|
||||
*
|
||||
* @covers ::encode
|
||||
* @covers ::decode
|
||||
* @dataProvider providerEncodeDecodeTests
|
||||
*/
|
||||
public function testEncodeDecode($data) {
|
||||
$this->assertEquals($data, YamlPecl::decode(YamlPecl::encode($data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that php object support is disabled.
|
||||
*/
|
||||
public function testObjectSupportDisabled() {
|
||||
$object = new \stdClass();
|
||||
$object->foo = 'bar';
|
||||
$this->assertEquals(['O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}'], YamlPecl::decode(YamlPecl::encode([$object])));
|
||||
$this->assertEquals(0, ini_get('yaml.decode_php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests decoding YAML node anchors.
|
||||
*
|
||||
* @covers ::decode
|
||||
* @dataProvider providerDecodeTests
|
||||
*/
|
||||
public function testDecode($string, $data) {
|
||||
$this->assertEquals($data, YamlPecl::decode($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests our encode settings.
|
||||
*
|
||||
* @covers ::encode
|
||||
*/
|
||||
public function testEncode() {
|
||||
$this->assertEquals('---
|
||||
foo:
|
||||
bar: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis
|
||||
...
|
||||
', YamlPecl::encode(['foo' => ['bar' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML boolean callback.
|
||||
*
|
||||
* @param string $string
|
||||
* String value for the YAML boolean.
|
||||
* @param string|bool $expected
|
||||
* The expected return value.
|
||||
*
|
||||
* @covers ::applyBooleanCallbacks
|
||||
* @dataProvider providerBoolTest
|
||||
*/
|
||||
public function testApplyBooleanCallbacks($string, $expected) {
|
||||
$this->assertEquals($expected, YamlPecl::applyBooleanCallbacks($string, 'bool', NULL));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFileExtension
|
||||
*/
|
||||
public function testGetFileExtension() {
|
||||
$this->assertEquals('yml', YamlPecl::getFileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that invalid YAML throws an exception.
|
||||
*
|
||||
* @covers ::errorHandler
|
||||
*/
|
||||
public function testError() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(InvalidDataTypeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(InvalidDataTypeException::class);
|
||||
}
|
||||
YamlPecl::decode('foo: [ads');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Serialization;
|
||||
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use Drupal\Component\Serialization\YamlSymfony;
|
||||
|
||||
/**
|
||||
* Tests the YamlSymfony serialization implementation.
|
||||
*
|
||||
* @group Drupal
|
||||
* @group Serialization
|
||||
* @coversDefaultClass \Drupal\Component\Serialization\YamlSymfony
|
||||
*/
|
||||
class YamlSymfonyTest extends YamlTestBase {
|
||||
|
||||
/**
|
||||
* Tests encoding and decoding basic data structures.
|
||||
*
|
||||
* @covers ::encode
|
||||
* @covers ::decode
|
||||
* @dataProvider providerEncodeDecodeTests
|
||||
*/
|
||||
public function testEncodeDecode($data) {
|
||||
$this->assertEquals($data, YamlSymfony::decode(YamlSymfony::encode($data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests decoding YAML node anchors.
|
||||
*
|
||||
* @covers ::decode
|
||||
* @dataProvider providerDecodeTests
|
||||
*/
|
||||
public function testDecode($string, $data) {
|
||||
$this->assertEquals($data, YamlSymfony::decode($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests our encode settings.
|
||||
*
|
||||
* @covers ::encode
|
||||
*/
|
||||
public function testEncode() {
|
||||
$this->assertEquals('foo:
|
||||
bar: \'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis\'
|
||||
', YamlSymfony::encode(['foo' => ['bar' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis']]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFileExtension
|
||||
*/
|
||||
public function testGetFileExtension() {
|
||||
$this->assertEquals('yml', YamlSymfony::getFileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that invalid YAML throws an exception.
|
||||
*
|
||||
* @covers ::decode
|
||||
*/
|
||||
public function testError() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(InvalidDataTypeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(InvalidDataTypeException::class);
|
||||
}
|
||||
YamlSymfony::decode('foo: [ads');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that php object support is disabled.
|
||||
*
|
||||
* @covers ::encode
|
||||
*/
|
||||
public function testObjectSupportDisabled() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(InvalidDataTypeException::class);
|
||||
$this->expectExceptionMessage('Object support when dumping a YAML file has been disabled.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(InvalidDataTypeException::class, 'Object support when dumping a YAML file has been disabled.');
|
||||
}
|
||||
$object = new \stdClass();
|
||||
$object->foo = 'bar';
|
||||
YamlSymfony::encode([$object]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Serialization;
|
||||
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use Drupal\Component\Serialization\SerializationInterface;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Component\Serialization\YamlPecl;
|
||||
use Drupal\Component\Serialization\YamlSymfony;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Serialization\Yaml
|
||||
* @group Serialization
|
||||
*/
|
||||
class YamlTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $mockParser;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->mockParser = $this->getMockBuilder('\stdClass')
|
||||
->setMethods(['encode', 'decode', 'getFileExtension'])
|
||||
->getMock();
|
||||
YamlParserProxy::setMock($this->mockParser);
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
YamlParserProxy::setMock(NULL);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::decode
|
||||
*/
|
||||
public function testDecode() {
|
||||
$this->mockParser
|
||||
->expects($this->once())
|
||||
->method('decode');
|
||||
YamlStub::decode('test');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFileExtension
|
||||
*/
|
||||
public function testGetFileExtension() {
|
||||
$this->mockParser
|
||||
->expects($this->never())
|
||||
->method('getFileExtension');
|
||||
$this->assertEquals('yml', YamlStub::getFileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all YAML files are decoded in the same way with Symfony and PECL.
|
||||
*
|
||||
* This test is a little bit slow but it tests that we do not have any bugs in
|
||||
* our YAML that might not be decoded correctly in any of our implementations.
|
||||
*
|
||||
* @todo This should exist as an integration test not part of our unit tests.
|
||||
* https://www.drupal.org/node/2597730
|
||||
*
|
||||
* @requires extension yaml
|
||||
* @dataProvider providerYamlFilesInCore
|
||||
*/
|
||||
public function testYamlFiles($file) {
|
||||
$data = file_get_contents($file);
|
||||
try {
|
||||
$this->assertEquals(YamlSymfony::decode($data), YamlPecl::decode($data), $file);
|
||||
}
|
||||
catch (InvalidDataTypeException $e) {
|
||||
// Provide file context to the failure so the exception message is useful.
|
||||
$this->fail("Exception thrown parsing $file:\n" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that decoding php objects does not work in PECL.
|
||||
*
|
||||
* @requires extension yaml
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Serialization\YamlTest::testObjectSupportDisabledSymfony()
|
||||
*/
|
||||
public function testObjectSupportDisabledPecl() {
|
||||
$object = new \stdClass();
|
||||
$object->foo = 'bar';
|
||||
// In core all Yaml encoding is done via Symfony and it does not support
|
||||
// objects so in order to encode an object we have to use the PECL
|
||||
// extension.
|
||||
// @see \Drupal\Component\Serialization\Yaml::encode()
|
||||
$yaml = YamlPecl::encode([$object]);
|
||||
$this->assertEquals(['O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}'], YamlPecl::decode($yaml));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that decoding php objects does not work in Symfony.
|
||||
*
|
||||
* @requires extension yaml
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Serialization\YamlTest::testObjectSupportDisabledPecl()
|
||||
*/
|
||||
public function testObjectSupportDisabledSymfony() {
|
||||
if (method_exists($this, 'setExpectedExceptionRegExp')) {
|
||||
$this->setExpectedExceptionRegExp(InvalidDataTypeException::class, '/^Object support when parsing a YAML file has been disabled/');
|
||||
}
|
||||
else {
|
||||
$this->expectException(InvalidDataTypeException::class);
|
||||
$this->expectExceptionMessageRegExp('/^Object support when parsing a YAML file has been disabled/');
|
||||
}
|
||||
$object = new \stdClass();
|
||||
$object->foo = 'bar';
|
||||
// In core all Yaml encoding is done via Symfony and it does not support
|
||||
// objects so in order to encode an object we have to use the PECL
|
||||
// extension.
|
||||
// @see \Drupal\Component\Serialization\Yaml::encode()
|
||||
$yaml = YamlPecl::encode([$object]);
|
||||
YamlSymfony::decode($yaml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider that lists all YAML files in core.
|
||||
*/
|
||||
public function providerYamlFilesInCore() {
|
||||
$files = [];
|
||||
$dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/../../../../../', \RecursiveDirectoryIterator::FOLLOW_SYMLINKS));
|
||||
foreach ($dirs as $dir) {
|
||||
$pathname = $dir->getPathname();
|
||||
// Exclude core/node_modules.
|
||||
if ($dir->getExtension() == 'yml' && strpos($pathname, '/../../../../../node_modules') === FALSE) {
|
||||
if (strpos($dir->getRealPath(), 'invalid_file') !== FALSE) {
|
||||
// There are some intentionally invalid files provided for testing
|
||||
// library API behaviours, ignore them.
|
||||
continue;
|
||||
}
|
||||
$files[] = [$dir->getRealPath()];
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class YamlStub extends Yaml {
|
||||
|
||||
public static function getSerializer() {
|
||||
return '\Drupal\Tests\Component\Serialization\YamlParserProxy';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class YamlParserProxy implements SerializationInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Component\Serialization\SerializationInterface
|
||||
*/
|
||||
protected static $mock;
|
||||
|
||||
public static function setMock($mock) {
|
||||
static::$mock = $mock;
|
||||
}
|
||||
|
||||
public static function encode($data) {
|
||||
return static::$mock->encode($data);
|
||||
}
|
||||
|
||||
public static function decode($raw) {
|
||||
return static::$mock->decode($raw);
|
||||
}
|
||||
|
||||
public static function getFileExtension() {
|
||||
return static::$mock->getFileExtension();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Serialization;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Provides standard data to validate different YAML implementations.
|
||||
*/
|
||||
abstract class YamlTestBase extends TestCase {
|
||||
|
||||
/**
|
||||
* Some data that should be able to be serialized.
|
||||
*/
|
||||
public function providerEncodeDecodeTests() {
|
||||
return [
|
||||
[
|
||||
'foo' => 'bar',
|
||||
'id' => 'schnitzel',
|
||||
'ponies' => ['nope', 'thanks'],
|
||||
'how' => [
|
||||
'about' => 'if',
|
||||
'i' => 'ask',
|
||||
'nicely',
|
||||
],
|
||||
'the' => [
|
||||
'answer' => [
|
||||
'still' => 'would',
|
||||
'be' => 'Y',
|
||||
],
|
||||
],
|
||||
'how_many_times' => 123,
|
||||
'should_i_ask' => FALSE,
|
||||
1,
|
||||
FALSE,
|
||||
[1, FALSE],
|
||||
[10],
|
||||
[0 => '123456'],
|
||||
],
|
||||
[NULL],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Some data that should be able to be de-serialized.
|
||||
*/
|
||||
public function providerDecodeTests() {
|
||||
$data = [
|
||||
// NULL files.
|
||||
['', NULL],
|
||||
["\n", NULL],
|
||||
["---\n...\n", NULL],
|
||||
|
||||
// Node anchors.
|
||||
[
|
||||
"
|
||||
jquery.ui:
|
||||
version: &jquery_ui 1.10.2
|
||||
|
||||
jquery.ui.accordion:
|
||||
version: *jquery_ui
|
||||
",
|
||||
[
|
||||
'jquery.ui' => [
|
||||
'version' => '1.10.2',
|
||||
],
|
||||
'jquery.ui.accordion' => [
|
||||
'version' => '1.10.2',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// 1.2 Bool values.
|
||||
foreach ($this->providerBoolTest() as $test) {
|
||||
$data[] = ['bool: ' . $test[0], ['bool' => $test[1]]];
|
||||
}
|
||||
$data = array_merge($data, $this->providerBoolTest());
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests different boolean serialization and de-serialization.
|
||||
*/
|
||||
public function providerBoolTest() {
|
||||
return [
|
||||
['true', TRUE],
|
||||
['TRUE', TRUE],
|
||||
['True', TRUE],
|
||||
['y', 'y'],
|
||||
['Y', 'Y'],
|
||||
['false', FALSE],
|
||||
['FALSE', FALSE],
|
||||
['False', FALSE],
|
||||
['n', 'n'],
|
||||
['N', 'N'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Transliteration;
|
||||
|
||||
use Drupal\Component\Transliteration\PhpTransliteration;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests Transliteration component functionality.
|
||||
*
|
||||
* @group Transliteration
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Transliteration\PhpTransliteration
|
||||
*/
|
||||
class PhpTransliterationTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests the PhpTransliteration::removeDiacritics() function.
|
||||
*
|
||||
* @param string $original
|
||||
* The language code to test.
|
||||
* @param string $expected
|
||||
* The expected return from PhpTransliteration::removeDiacritics().
|
||||
*
|
||||
* @dataProvider providerTestPhpTransliterationRemoveDiacritics
|
||||
*/
|
||||
public function testRemoveDiacritics($original, $expected) {
|
||||
$transliterator_class = new PhpTransliteration();
|
||||
$result = $transliterator_class->removeDiacritics($original);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testRemoveDiacritics().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters for
|
||||
* self::testRemoveDiacritics().
|
||||
*/
|
||||
public function providerTestPhpTransliterationRemoveDiacritics() {
|
||||
return [
|
||||
// Test all characters in the Unicode range 0x00bf to 0x017f.
|
||||
['ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ', 'AAAAAAÆCEEEEIIII'],
|
||||
['ÐÑÒÓÔÕÖרÙÚÛÜÝÞß', 'ÐNOOOOO×OUUUUYÞß'],
|
||||
['àáâãäåæçèéêëìíîï', 'aaaaaaæceeeeiiii'],
|
||||
['ðñòóôõö÷øùúûüýþÿ', 'ðnooooo÷ouuuuyþy'],
|
||||
['ĀāĂ㥹ĆćĈĉĊċČčĎď', 'AaAaAaCcCcCcCcDd'],
|
||||
['ĐđĒēĔĕĖėĘęĚěĜĝĞğ', 'DdEeEeEeEeEeGgGg'],
|
||||
['ĠġĢģĤĥĦħĨĩĪīĬĭĮį', 'GgGgHhHhIiIiIiIi'],
|
||||
['İıIJijĴĵĶķĸĹĺĻļĽľĿ', 'IiIJijJjKkĸLlLlLlL'],
|
||||
['ŀŁłŃńŅņŇňʼnŊŋŌōŎŏ', 'lLlNnNnNnʼnŊŋOoOo'],
|
||||
['ŐőŒœŔŕŖŗŘřŚśŜŝŞş', 'OoŒœRrRrRrSsSsSs'],
|
||||
['ŠšŢţŤťŦŧŨũŪūŬŭŮů', 'SsTtTtTtUuUuUuUu'],
|
||||
['ŰűŲųŴŵŶŷŸŹźŻżŽž', 'UuUuWwYyYZzZzZz'],
|
||||
|
||||
// Test all characters in the Unicode range 0x01CD to 0x024F.
|
||||
['ǍǎǏ', 'AaI'],
|
||||
['ǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟ', 'iOoUuUuUuUuUuǝAa'],
|
||||
['ǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯ', 'AaǢǣGgGgKkOoOoǮǯ'],
|
||||
['ǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿ', 'jDZDzdzGgǶǷNnAaǼǽOo'],
|
||||
['ȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏ', 'AaAaEeEeIiIiOoOo'],
|
||||
['ȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟ', 'RrRrUuUuSsTtȜȝHh'],
|
||||
['ȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯ', 'ȠȡȢȣZzAaEeOoOoOo'],
|
||||
['ȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿ', 'OoYylntjȸȹACcLTs'],
|
||||
['ɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ', 'zɁɂBUɅEeJjQqRrYy'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the PhpTransliteration class.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code to test.
|
||||
* @param string $original
|
||||
* The original string.
|
||||
* @param string $expected
|
||||
* The expected return from PhpTransliteration::transliterate().
|
||||
* @param string $unknown_character
|
||||
* (optional) The character to substitute for characters in $string without
|
||||
* transliterated equivalents. Defaults to '?'.
|
||||
* @param int $max_length
|
||||
* (optional) If provided, return at most this many characters, ensuring
|
||||
* that the transliteration does not split in the middle of an input
|
||||
* character's transliteration.
|
||||
*
|
||||
* @dataProvider providerTestPhpTransliteration
|
||||
*/
|
||||
public function testPhpTransliteration($langcode, $original, $expected, $unknown_character = '?', $max_length = NULL) {
|
||||
$transliterator_class = new PhpTransliteration();
|
||||
$actual = $transliterator_class->transliterate($original, $langcode, $unknown_character, $max_length);
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testPhpTransliteration().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters for
|
||||
* self::testPhpTransliteration().
|
||||
*/
|
||||
public function providerTestPhpTransliteration() {
|
||||
$random_generator = new Random();
|
||||
$random = $random_generator->string(10);
|
||||
// Make some strings with two, three, and four-byte characters for testing.
|
||||
// Note that the 3-byte character is overridden by the 'kg' language.
|
||||
$two_byte = 'Ä Ö Ü Å Ø äöüåøhello';
|
||||
// This is a Cyrillic character that looks something like a "u". See
|
||||
// http://www.unicode.org/charts/PDF/U0400.pdf
|
||||
$three_byte = html_entity_decode('ц', ENT_NOQUOTES, 'UTF-8');
|
||||
// This is a Canadian Aboriginal character like a triangle. See
|
||||
// http://www.unicode.org/charts/PDF/U1400.pdf
|
||||
$four_byte = html_entity_decode('ᐑ', ENT_NOQUOTES, 'UTF-8');
|
||||
// These are two Gothic alphabet letters. See
|
||||
// http://wikipedia.org/wiki/Gothic_alphabet
|
||||
// They are not in our tables, but should at least give us '?' (unknown).
|
||||
$five_byte = html_entity_decode('𐌰𐌸', ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
return [
|
||||
// Each test case is (language code, input, output).
|
||||
// Test ASCII in English.
|
||||
['en', $random, $random],
|
||||
// Test ASCII in some other language with no overrides.
|
||||
['fr', $random, $random],
|
||||
// Test 3 and 4-byte characters in a language without overrides.
|
||||
// Note: if the data tables change, these will need to change too! They
|
||||
// are set up to test that data table loading works, so values come
|
||||
// directly from the data files.
|
||||
['fr', $three_byte, 'c'],
|
||||
['fr', $four_byte, 'wii'],
|
||||
// Test 5-byte characters.
|
||||
['en', $five_byte, '??'],
|
||||
// Test a language with no overrides.
|
||||
['en', $two_byte, 'A O U A O aouaohello'],
|
||||
// Test language overrides provided by core.
|
||||
['de', $two_byte, 'Ae Oe Ue A O aeoeueaohello'],
|
||||
['de', $random, $random],
|
||||
['dk', $two_byte, 'A O U Aa Oe aouaaoehello'],
|
||||
['dk', $random, $random],
|
||||
['kg', $three_byte, 'ts'],
|
||||
// Test strings in some other languages.
|
||||
// Turkish, provided by drupal.org user Kartagis.
|
||||
['tr', 'Abayı serdiler bize. Söyleyeceğim yüzlerine. Sanırım hepimiz aynı şeyi düşünüyoruz.', 'Abayi serdiler bize. Soyleyecegim yuzlerine. Sanirim hepimiz ayni seyi dusunuyoruz.'],
|
||||
// Max length.
|
||||
['de', $two_byte, 'Ae Oe', '?', 5],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the transliteration with max length.
|
||||
*/
|
||||
public function testTransliterationWithMaxLength() {
|
||||
$transliteration = new PhpTransliteration();
|
||||
|
||||
// Test with max length, using German. It should never split up the
|
||||
// transliteration of a single character.
|
||||
$input = 'Ä Ö Ü Å Ø äöüåøhello';
|
||||
$trunc_output = 'Ae Oe Ue A O aeoe';
|
||||
|
||||
$this->assertSame($trunc_output, $transliteration->transliterate($input, 'de', '?', 17), 'Truncating to 17 characters works');
|
||||
$this->assertSame($trunc_output, $transliteration->transliterate($input, 'de', '?', 18), 'Truncating to 18 characters works');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the unknown character replacement.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code to test.
|
||||
* @param string $original
|
||||
* The original string.
|
||||
* @param string $expected
|
||||
* The expected return from PhpTransliteration::transliterate().
|
||||
* @param string $unknown_character
|
||||
* The character to substitute for characters in $string without
|
||||
* transliterated equivalents.
|
||||
* @param int $max_length
|
||||
* The maximum length of the string that returns the transliteration.
|
||||
*
|
||||
* @dataProvider providerTestTransliterationUnknownCharacter
|
||||
*/
|
||||
public function testTransliterationUnknownCharacter($langcode, $original, $expected, $unknown_character = '?', $max_length = NULL) {
|
||||
$transliteration = new PhpTransliteration();
|
||||
$actual = $transliteration->transliterate($original, $langcode, $unknown_character, $max_length);
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testTransliterationUnknownCharacter().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters for
|
||||
* self::testTransliterationUnknownCharacter().
|
||||
*/
|
||||
public function providerTestTransliterationUnknownCharacter() {
|
||||
return [
|
||||
// Each test case is (language code, input, output, unknown character, max
|
||||
// length).
|
||||
|
||||
// Illegal/unknown unicode.
|
||||
['en', chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80), '?????'],
|
||||
['en', chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80), '-----', '-'],
|
||||
['en', 'Hel' . chr(0x80) . 'o World', 'Hel?o World'],
|
||||
['en', 'Hell' . chr(0x80) . ' World', 'Hell? World'],
|
||||
// Non default replacement.
|
||||
['en', chr(0x80) . 'ello World', '_ello World', '_'],
|
||||
// Keep the original question marks.
|
||||
['en', chr(0xF8) . '?' . chr(0x80), '???'],
|
||||
['en', chr(0x80) . 'ello ? World?', '_ello ? World?', '_'],
|
||||
['pl', 'aąeę' . chr(0x80) . 'oółżźz ?', 'aaee?oolzzz ?'],
|
||||
// Non-US-ASCII replacement.
|
||||
['en', chr(0x80) . 'ello World?', 'Oello World?', 'Ö'],
|
||||
['pl', chr(0x80) . 'óóść', 'ooosc', 'ó'],
|
||||
// Ensure question marks are replaced when max length used.
|
||||
['en', chr(0x80) . 'ello ? World?', '_ello ?', '_', 7],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests inclusion is safe.
|
||||
*
|
||||
* @covers ::readLanguageOverrides
|
||||
*/
|
||||
public function testSafeInclude() {
|
||||
// The overrides in the transliteration data directory transliterates 0x82
|
||||
// into "safe" but the overrides one directory higher transliterates the
|
||||
// same character into "security hole". So by using "../index" as the
|
||||
// language code we can test the ../ is stripped from the langcode.
|
||||
vfsStream::setup('transliteration', NULL, [
|
||||
'index.php' => '<?php $overrides = ["../index" => [0x82 => "security hole"]];',
|
||||
'dir' => [
|
||||
'index.php' => '<?php $overrides = ["../index" => [0x82 => "safe"]];',
|
||||
],
|
||||
]);
|
||||
$transliteration = new PhpTransliteration(vfsStream::url('transliteration/dir'));
|
||||
$transliterated = $transliteration->transliterate(chr(0xC2) . chr(0x82), '../index');
|
||||
$this->assertSame('safe', $transliterated);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\Utility\ArgumentsResolverTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\ArgumentsResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Utility\ArgumentsResolver
|
||||
* @group Access
|
||||
*/
|
||||
class ArgumentsResolverTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getArgument() method.
|
||||
*
|
||||
* @dataProvider providerTestGetArgument
|
||||
*/
|
||||
public function testGetArgument($callable, $scalars, $objects, $wildcards, $expected) {
|
||||
$arguments = (new ArgumentsResolver($scalars, $objects, $wildcards))->getArguments($callable);
|
||||
$this->assertSame($expected, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data to testGetArgument().
|
||||
*/
|
||||
public function providerTestGetArgument() {
|
||||
$data = [];
|
||||
|
||||
// Test an optional parameter with no provided value.
|
||||
$data[] = [
|
||||
function ($foo = 'foo') {}, [], [], [] , ['foo'],
|
||||
];
|
||||
|
||||
// Test an optional parameter with a provided value.
|
||||
$data[] = [
|
||||
function ($foo = 'foo') {}, ['foo' => 'bar'], [], [], ['bar'],
|
||||
];
|
||||
|
||||
// Test with a provided value.
|
||||
$data[] = [
|
||||
function ($foo) {}, ['foo' => 'bar'], [], [], ['bar'],
|
||||
];
|
||||
|
||||
// Test with an explicitly NULL value.
|
||||
$data[] = [
|
||||
function ($foo) {}, [], ['foo' => NULL], [], [NULL],
|
||||
];
|
||||
|
||||
// Test with a raw value that overrides the provided upcast value, since
|
||||
// it is not typehinted.
|
||||
$scalars = ['foo' => 'baz'];
|
||||
$objects = ['foo' => new \stdClass()];
|
||||
$data[] = [
|
||||
function ($foo) {}, $scalars, $objects, [], ['baz'],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getArgument() with an object.
|
||||
*/
|
||||
public function testGetArgumentObject() {
|
||||
$callable = function (\stdClass $object) {};
|
||||
|
||||
$object = new \stdClass();
|
||||
$arguments = (new ArgumentsResolver([], ['object' => $object], []))->getArguments($callable);
|
||||
$this->assertSame([$object], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getArgument() with a wildcard object for a parameter with a custom name.
|
||||
*/
|
||||
public function testGetWildcardArgument() {
|
||||
$callable = function (\stdClass $custom_name) {};
|
||||
|
||||
$object = new \stdClass();
|
||||
$arguments = (new ArgumentsResolver([], [], [$object]))->getArguments($callable);
|
||||
$this->assertSame([$object], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getArgument() with a Route, Request, and Account object.
|
||||
*/
|
||||
public function testGetArgumentOrder() {
|
||||
$a1 = $this->getMockBuilder('\Drupal\Tests\Component\Utility\Test1Interface')->getMock();
|
||||
$a2 = $this->getMockBuilder('\Drupal\Tests\Component\Utility\TestClass')->getMock();
|
||||
$a3 = $this->getMockBuilder('\Drupal\Tests\Component\Utility\Test2Interface')->getMock();
|
||||
|
||||
$objects = [
|
||||
't1' => $a1,
|
||||
'tc' => $a2,
|
||||
];
|
||||
$wildcards = [$a3];
|
||||
$resolver = new ArgumentsResolver([], $objects, $wildcards);
|
||||
|
||||
$callable = function (Test1Interface $t1, TestClass $tc, Test2Interface $t2) {};
|
||||
$arguments = $resolver->getArguments($callable);
|
||||
$this->assertSame([$a1, $a2, $a3], $arguments);
|
||||
|
||||
// Test again, but with the arguments in a different order.
|
||||
$callable = function (Test2Interface $t2, TestClass $tc, Test1Interface $t1) {};
|
||||
$arguments = $resolver->getArguments($callable);
|
||||
$this->assertSame([$a3, $a2, $a1], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getArgument() with a wildcard parameter with no typehint.
|
||||
*
|
||||
* Without the typehint, the wildcard object will not be passed to the callable.
|
||||
*/
|
||||
public function testGetWildcardArgumentNoTypehint() {
|
||||
$a = $this->getMockBuilder('\Drupal\Tests\Component\Utility\Test1Interface')->getMock();
|
||||
$wildcards = [$a];
|
||||
$resolver = new ArgumentsResolver([], [], $wildcards);
|
||||
|
||||
$callable = function ($route) {};
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('requires a value for the "$route" argument.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class, 'requires a value for the "$route" argument.');
|
||||
}
|
||||
$resolver->getArguments($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getArgument() with a named parameter with no typehint and a value.
|
||||
*
|
||||
* Without the typehint, passing a value to a named parameter will still
|
||||
* receive the provided value.
|
||||
*/
|
||||
public function testGetArgumentRouteNoTypehintAndValue() {
|
||||
$scalars = ['route' => 'foo'];
|
||||
$resolver = new ArgumentsResolver($scalars, [], []);
|
||||
|
||||
$callable = function ($route) {};
|
||||
$arguments = $resolver->getArguments($callable);
|
||||
$this->assertSame(['foo'], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests handleUnresolvedArgument() for a scalar argument.
|
||||
*/
|
||||
public function testHandleNotUpcastedArgument() {
|
||||
$objects = ['foo' => 'bar'];
|
||||
$scalars = ['foo' => 'baz'];
|
||||
$resolver = new ArgumentsResolver($scalars, $objects, []);
|
||||
|
||||
$callable = function (\stdClass $foo) {};
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('requires a value for the "$foo" argument.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class, 'requires a value for the "$foo" argument.');
|
||||
}
|
||||
$resolver->getArguments($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests handleUnresolvedArgument() for missing arguments.
|
||||
*
|
||||
* @dataProvider providerTestHandleUnresolvedArgument
|
||||
*/
|
||||
public function testHandleUnresolvedArgument($callable) {
|
||||
$resolver = new ArgumentsResolver([], [], []);
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('requires a value for the "$foo" argument.');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class, 'requires a value for the "$foo" argument.');
|
||||
}
|
||||
$resolver->getArguments($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data to testHandleUnresolvedArgument().
|
||||
*/
|
||||
public function providerTestHandleUnresolvedArgument() {
|
||||
$data = [];
|
||||
$data[] = [function ($foo) {}];
|
||||
$data[] = [[new TestClass(), 'access']];
|
||||
$data[] = ['Drupal\Tests\Component\Utility\test_access_arguments_resolver_access'];
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a test class.
|
||||
*/
|
||||
class TestClass {
|
||||
|
||||
public function access($foo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a test interface.
|
||||
*/
|
||||
interface Test1Interface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a different test interface.
|
||||
*/
|
||||
interface Test2Interface {
|
||||
}
|
||||
|
||||
function test_access_arguments_resolver_access($foo) {
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests bytes size parsing helper methods.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Bytes
|
||||
*/
|
||||
class BytesTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Component\Utility\Bytes::toInt().
|
||||
*
|
||||
* @param int $size
|
||||
* The value for the size argument for
|
||||
* \Drupal\Component\Utility\Bytes::toInt().
|
||||
* @param int $expected_int
|
||||
* The expected return value from
|
||||
* \Drupal\Component\Utility\Bytes::toInt().
|
||||
*
|
||||
* @dataProvider providerTestToInt
|
||||
* @covers ::toInt
|
||||
*/
|
||||
public function testToInt($size, $expected_int) {
|
||||
$this->assertEquals($expected_int, Bytes::toInt($size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testToInt.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the argument for
|
||||
* \Drupal\Component\Utility\Bytes::toInt(): size, and the expected return
|
||||
* value.
|
||||
*/
|
||||
public function providerTestToInt() {
|
||||
return [
|
||||
['1', 1],
|
||||
['1 byte', 1],
|
||||
['1 KB' , Bytes::KILOBYTE],
|
||||
['1 MB' , pow(Bytes::KILOBYTE, 2)],
|
||||
['1 GB' , pow(Bytes::KILOBYTE, 3)],
|
||||
['1 TB' , pow(Bytes::KILOBYTE, 4)],
|
||||
['1 PB' , pow(Bytes::KILOBYTE, 5)],
|
||||
['1 EB' , pow(Bytes::KILOBYTE, 6)],
|
||||
['1 ZB' , pow(Bytes::KILOBYTE, 7)],
|
||||
['1 YB' , pow(Bytes::KILOBYTE, 8)],
|
||||
['23476892 bytes', 23476892],
|
||||
// 76 MB.
|
||||
['76MRandomStringThatShouldBeIgnoredByParseSize.', 79691776],
|
||||
// 76.24 GB (with typo).
|
||||
['76.24 Giggabyte', 81862076662],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
164
2017/web/core/tests/Drupal/Tests/Component/Utility/ColorTest.php
Normal file
164
2017/web/core/tests/Drupal/Tests/Component/Utility/ColorTest.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Color;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests Color utility class conversions.
|
||||
*
|
||||
* @group Utility
|
||||
*/
|
||||
class ColorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests Color::hexToRgb().
|
||||
*
|
||||
* @param string $value
|
||||
* The hex color value.
|
||||
* @param string $expected
|
||||
* The expected rgb color value.
|
||||
* @param bool $invalid
|
||||
* Whether this value is invalid and exception should be expected.
|
||||
*
|
||||
* @dataProvider providerTestHexToRgb
|
||||
*/
|
||||
public function testHexToRgb($value, $expected, $invalid = FALSE) {
|
||||
if ($invalid) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('InvalidArgumentException');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
}
|
||||
}
|
||||
$this->assertSame($expected, Color::hexToRgb($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testHexToRgb().
|
||||
*
|
||||
* @see testHexToRgb()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing:
|
||||
* - The hex color value.
|
||||
* - The rgb color array value.
|
||||
* - (optional) Boolean indicating invalid status. Defaults to FALSE.
|
||||
*/
|
||||
public function providerTestHexToRgb() {
|
||||
$invalid = [];
|
||||
// Any invalid arguments should throw an exception.
|
||||
foreach (['', '-1', '1', '12', '12345', '1234567', '123456789', '123456789a', 'foo'] as $value) {
|
||||
$invalid[] = [$value, '', TRUE];
|
||||
}
|
||||
// Duplicate all invalid value tests with additional '#' prefix.
|
||||
// The '#' prefix inherently turns the data type into a string.
|
||||
foreach ($invalid as $value) {
|
||||
$invalid[] = ['#' . $value[0], '', TRUE];
|
||||
}
|
||||
// Add invalid data types (hex value must be a string).
|
||||
foreach ([
|
||||
1, 12, 1234, 12345, 123456, 1234567, 12345678, 123456789, 123456789,
|
||||
-1, PHP_INT_MAX, PHP_INT_MAX + 1, -PHP_INT_MAX, 0x0, 0x010,
|
||||
] as $value) {
|
||||
$invalid[] = [$value, '', TRUE];
|
||||
}
|
||||
// And some valid values.
|
||||
$valid = [
|
||||
// Shorthands without alpha.
|
||||
['hex' => '#000', 'rgb' => ['red' => 0, 'green' => 0, 'blue' => 0]],
|
||||
['hex' => '#fff', 'rgb' => ['red' => 255, 'green' => 255, 'blue' => 255]],
|
||||
['hex' => '#abc', 'rgb' => ['red' => 170, 'green' => 187, 'blue' => 204]],
|
||||
['hex' => 'cba', 'rgb' => ['red' => 204, 'green' => 187, 'blue' => 170]],
|
||||
// Full without alpha.
|
||||
['hex' => '#000000', 'rgb' => ['red' => 0, 'green' => 0, 'blue' => 0]],
|
||||
['hex' => '#ffffff', 'rgb' => ['red' => 255, 'green' => 255, 'blue' => 255]],
|
||||
['hex' => '#010203', 'rgb' => ['red' => 1, 'green' => 2, 'blue' => 3]],
|
||||
];
|
||||
return array_merge($invalid, $valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Color::rgbToHex().
|
||||
*
|
||||
* @param string $value
|
||||
* The rgb color value.
|
||||
* @param string $expected
|
||||
* The expected hex color value.
|
||||
*
|
||||
* @dataProvider providerTestRbgToHex
|
||||
*/
|
||||
public function testRgbToHex($value, $expected) {
|
||||
$this->assertSame($expected, Color::rgbToHex($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testRgbToHex().
|
||||
*
|
||||
* @see testRgbToHex()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing:
|
||||
* - The rgb color array value.
|
||||
* - The hex color value.
|
||||
*/
|
||||
public function providerTestRbgToHex() {
|
||||
// Input using named RGB array (e.g., as returned by Color::hexToRgb()).
|
||||
$tests = [
|
||||
[['red' => 0, 'green' => 0, 'blue' => 0], '#000000'],
|
||||
[['red' => 255, 'green' => 255, 'blue' => 255], '#ffffff'],
|
||||
[['red' => 119, 'green' => 119, 'blue' => 119], '#777777'],
|
||||
[['red' => 1, 'green' => 2, 'blue' => 3], '#010203'],
|
||||
];
|
||||
// Input using indexed RGB array (e.g.: array(10, 10, 10)).
|
||||
foreach ($tests as $test) {
|
||||
$tests[] = [array_values($test[0]), $test[1]];
|
||||
}
|
||||
// Input using CSS RGB string notation (e.g.: 10, 10, 10).
|
||||
foreach ($tests as $test) {
|
||||
$tests[] = [implode(', ', $test[0]), $test[1]];
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testNormalizeHexLength().
|
||||
*
|
||||
* @see testNormalizeHexLength()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing:
|
||||
* - The hex color value.
|
||||
* - The 6 character length hex color value.
|
||||
*/
|
||||
public function providerTestNormalizeHexLength() {
|
||||
$data = [
|
||||
['#000', '#000000'],
|
||||
['#FFF', '#FFFFFF'],
|
||||
['#abc', '#aabbcc'],
|
||||
['cba', '#ccbbaa'],
|
||||
['#000000', '#000000'],
|
||||
['ffffff', '#ffffff'],
|
||||
['#010203', '#010203'],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Color::normalizeHexLength().
|
||||
*
|
||||
* @param string $value
|
||||
* The input hex color value.
|
||||
* @param string $expected
|
||||
* The expected normalized hex color value.
|
||||
*
|
||||
* @dataProvider providerTestNormalizeHexLength
|
||||
*/
|
||||
public function testNormalizeHexLength($value, $expected) {
|
||||
$this->assertSame($expected, Color::normalizeHexLength($value));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests random byte generation fallback exception situations.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Crypt
|
||||
*/
|
||||
class CryptRandomFallbackTest extends TestCase {
|
||||
|
||||
static protected $functionCalled = 0;
|
||||
|
||||
/**
|
||||
* Allows the test to confirm that the namespaced random_bytes() was called.
|
||||
*/
|
||||
public static function functionCalled() {
|
||||
static::$functionCalled++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests random byte generation using the fallback generator.
|
||||
*
|
||||
* If the call to random_bytes() throws an exception, Crypt::random_bytes()
|
||||
* should still return a useful string of random bytes.
|
||||
*
|
||||
* @covers ::randomBytes
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\CryptTest::testRandomBytes()
|
||||
*/
|
||||
public function testRandomBytesFallback() {
|
||||
// This loop is a copy of
|
||||
// \Drupal\Tests\Component\Utility\CryptTest::testRandomBytes().
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$count = rand(10, 10000);
|
||||
// Check that different values are being generated.
|
||||
$this->assertNotEquals(Crypt::randomBytes($count), Crypt::randomBytes($count));
|
||||
// Check the length.
|
||||
$this->assertEquals($count, strlen(Crypt::randomBytes($count)));
|
||||
}
|
||||
$this->assertEquals(30, static::$functionCalled, 'The namespaced function was called the expected number of times.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
use Drupal\Tests\Component\Utility\CryptRandomFallbackTest;
|
||||
|
||||
/**
|
||||
* Defines a function in same namespace as Drupal\Component\Utility\Crypt.
|
||||
*
|
||||
* Forces throwing an exception in this test environment because the function
|
||||
* in the namespace is used in preference to the global function.
|
||||
*
|
||||
* @param int $count
|
||||
* Matches the global function definition.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
function random_bytes($count) {
|
||||
CryptRandomFallbackTest::functionCalled();
|
||||
throw new \Exception($count);
|
||||
}
|
||||
157
2017/web/core/tests/Drupal/Tests/Component/Utility/CryptTest.php
Normal file
157
2017/web/core/tests/Drupal/Tests/Component/Utility/CryptTest.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests random byte generation.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Crypt
|
||||
*/
|
||||
class CryptTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests random byte generation.
|
||||
*
|
||||
* @covers ::randomBytes
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\CryptRandomFallbackTest::testRandomBytesFallback
|
||||
*/
|
||||
public function testRandomBytes() {
|
||||
for ($i = 1; $i < 10; $i++) {
|
||||
$count = rand(10, 10000);
|
||||
// Check that different values are being generated.
|
||||
$this->assertNotEquals(Crypt::randomBytes($count), Crypt::randomBytes($count));
|
||||
// Check the length.
|
||||
$this->assertEquals(strlen(Crypt::randomBytes($count)), $count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hash generation.
|
||||
*
|
||||
* @dataProvider providerTestHashBase64
|
||||
* @covers ::hashBase64
|
||||
*
|
||||
* @param string $data
|
||||
* Data to hash.
|
||||
* @param string $expected_hash
|
||||
* Expected result from hashing $data.
|
||||
*/
|
||||
public function testHashBase64($data, $expected_hash) {
|
||||
$hash = Crypt::hashBase64($data);
|
||||
$this->assertEquals($expected_hash, $hash, 'The correct hash was not calculated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests HMAC generation.
|
||||
*
|
||||
* @dataProvider providerTestHmacBase64
|
||||
* @covers ::hmacBase64
|
||||
*
|
||||
* @param string $data
|
||||
* Data to hash.
|
||||
* @param string $key
|
||||
* Key to use in hashing process.
|
||||
* @param string $expected_hmac
|
||||
* Expected result from hashing $data using $key.
|
||||
*/
|
||||
public function testHmacBase64($data, $key, $expected_hmac) {
|
||||
$hmac = Crypt::hmacBase64($data, $key);
|
||||
$this->assertEquals($expected_hmac, $hmac, 'The correct hmac was not calculated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the hmacBase64 method with invalid parameters.
|
||||
*
|
||||
* @dataProvider providerTestHmacBase64Invalid
|
||||
* @covers ::hmacBase64
|
||||
*
|
||||
* @param string $data
|
||||
* Data to hash.
|
||||
* @param string $key
|
||||
* Key to use in hashing process.
|
||||
*/
|
||||
public function testHmacBase64Invalid($data, $key) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException('InvalidArgumentException');
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
}
|
||||
Crypt::hmacBase64($data, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testHashBase64().
|
||||
*
|
||||
* @return array Test data.
|
||||
*/
|
||||
public function providerTestHashBase64() {
|
||||
return [
|
||||
[
|
||||
'data' => 'The SHA (Secure Hash Algorithm) is one of a number of cryptographic hash functions. A cryptographic hash is like a signature for a text or a data file. SHA-256 algorithm generates an almost-unique, fixed size 256-bit (32-byte) hash. Hash is a one way function – it cannot be decrypted back. This makes it suitable for password validation, challenge hash authentication, anti-tamper, digital signatures.',
|
||||
'expectedHash' => '034rT6smZAVRxpq8O98cFFNLIVx_Ph1EwLZQKcmRR_s',
|
||||
],
|
||||
[
|
||||
'data' => 'SHA-256 is one of the successor hash functions to SHA-1, and is one of the strongest hash functions available.',
|
||||
'expected_hash' => 'yuqkDDYqprL71k4xIb6K6D7n76xldO4jseRhEkEE6SI',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testHmacBase64().
|
||||
*
|
||||
* @return array Test data.
|
||||
*/
|
||||
public function providerTestHmacBase64() {
|
||||
return [
|
||||
[
|
||||
'data' => 'Calculates a base-64 encoded, URL-safe sha-256 hmac.',
|
||||
'key' => 'secret-key',
|
||||
'expected_hmac' => '2AaH63zwjhekWZlEpAiufyfhAHIzbQhl9Hd9oCi3_c8',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testHmacBase64().
|
||||
*
|
||||
* @return array Test data.
|
||||
*/
|
||||
public function providerTestHmacBase64Invalid() {
|
||||
return [
|
||||
[new \stdClass(), new \stdClass()],
|
||||
[new \stdClass(), 'string'],
|
||||
[new \stdClass(), 1],
|
||||
[new \stdClass(), 0],
|
||||
[NULL, new \stdClass()],
|
||||
['string', new \stdClass()],
|
||||
[1, new \stdClass()],
|
||||
[0, new \stdClass()],
|
||||
[[], []],
|
||||
[[], NULL],
|
||||
[[], 'string'],
|
||||
[[], 1],
|
||||
[[], 0],
|
||||
[NULL, []],
|
||||
[1, []],
|
||||
[0, []],
|
||||
['string', []],
|
||||
[[], NULL],
|
||||
[NULL, NULL],
|
||||
[NULL, 'string'],
|
||||
[NULL, 1],
|
||||
[NULL, 0],
|
||||
[1, NULL],
|
||||
[0, NULL],
|
||||
['string', NULL],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Environment;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test PHP Environment helper methods.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Environment
|
||||
*/
|
||||
class EnvironmentTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Component\Utility\Environment::checkMemoryLimit().
|
||||
*
|
||||
* @dataProvider providerTestCheckMemoryLimit
|
||||
* @covers ::checkMemoryLimit
|
||||
*
|
||||
* @param string $required
|
||||
* The required memory argument for
|
||||
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
|
||||
* @param string $custom_memory_limit
|
||||
* The custom memory limit argument for
|
||||
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
|
||||
* @param bool $expected
|
||||
* The expected return value from
|
||||
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
|
||||
*/
|
||||
public function testCheckMemoryLimit($required, $custom_memory_limit, $expected) {
|
||||
$actual = Environment::checkMemoryLimit($required, $custom_memory_limit);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testCheckMemoryLimit().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the arguments for
|
||||
* \Drupal\Component\Utility\Environment::checkMemoryLimit():
|
||||
* required and memory_limit, and the expected return value.
|
||||
*/
|
||||
public function providerTestCheckMemoryLimit() {
|
||||
$memory_limit = ini_get('memory_limit');
|
||||
$twice_avail_memory = ($memory_limit * 2) . 'MB';
|
||||
|
||||
return [
|
||||
// Minimal amount of memory should be available.
|
||||
['30MB', NULL, TRUE],
|
||||
// Exceed a custom (unlimited) memory limit.
|
||||
[$twice_avail_memory, -1, TRUE],
|
||||
// Exceed a custom memory limit.
|
||||
['30MB', '16MB', FALSE],
|
||||
// Available = required.
|
||||
['30MB', '30MB', TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
417
2017/web/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
Normal file
417
2017/web/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Render\MarkupTrait;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Component\Utility\Html.
|
||||
*
|
||||
* @group Common
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Html
|
||||
*/
|
||||
class HtmlTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
|
||||
$property->setAccessible(TRUE);
|
||||
$property->setValue(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Html::cleanCssIdentifier() method.
|
||||
*
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $source
|
||||
* The string being transformed to an ID.
|
||||
* @param array|null $filter
|
||||
* (optional) An array of string replacements to use on the identifier. If
|
||||
* NULL, no filter will be passed and a default will be used.
|
||||
*
|
||||
* @dataProvider providerTestCleanCssIdentifier
|
||||
*
|
||||
* @covers ::cleanCssIdentifier
|
||||
*/
|
||||
public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
|
||||
if ($filter !== NULL) {
|
||||
$this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
|
||||
}
|
||||
else {
|
||||
$this->assertSame($expected, Html::cleanCssIdentifier($source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testCleanCssIdentifier().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestCleanCssIdentifier() {
|
||||
$id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
|
||||
$id2 = '¡¢£¤¥';
|
||||
$id3 = 'css__identifier__with__double__underscores';
|
||||
return [
|
||||
// Verify that no valid ASCII characters are stripped from the identifier.
|
||||
[$id1, $id1, []],
|
||||
// Verify that valid UTF-8 characters are not stripped from the identifier.
|
||||
[$id2, $id2, []],
|
||||
// Verify that double underscores are not stripped from the identifier.
|
||||
[$id3, $id3],
|
||||
// Verify that invalid characters (including non-breaking space) are
|
||||
// stripped from the identifier.
|
||||
['invalididentifier', 'invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', []],
|
||||
// Verify that an identifier starting with a digit is replaced.
|
||||
['_cssidentifier', '1cssidentifier', []],
|
||||
// Verify that an identifier starting with a hyphen followed by a digit is
|
||||
// replaced.
|
||||
['__cssidentifier', '-1cssidentifier', []],
|
||||
// Verify that an identifier starting with two hyphens is replaced.
|
||||
['__cssidentifier', '--cssidentifier', []],
|
||||
// Verify that passing double underscores as a filter is processed.
|
||||
['_cssidentifier', '__cssidentifier', ['__' => '_']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Html::getClass() cleans the class name properly.
|
||||
*
|
||||
* @coversDefaultClass ::getClass
|
||||
*/
|
||||
public function testHtmlClass() {
|
||||
// Verify Drupal coding standards are enforced.
|
||||
$this->assertSame('class-name--ü', Html::getClass('CLASS NAME_[Ü]'), 'Enforce Drupal coding standards.');
|
||||
|
||||
// Test Html::getClass() handles Drupal\Component\Render\MarkupInterface
|
||||
// input.
|
||||
$markup = HtmlTestMarkup::create('CLASS_FROM_OBJECT');
|
||||
$this->assertSame('class-from-object', Html::getClass($markup), 'Markup object is converted to CSS class.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Html::getUniqueId() method.
|
||||
*
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $source
|
||||
* The string being transformed to an ID.
|
||||
* @param bool $reset
|
||||
* (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
|
||||
*
|
||||
* @dataProvider providerTestHtmlGetUniqueId
|
||||
*
|
||||
* @covers ::getUniqueId
|
||||
*/
|
||||
public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
|
||||
if ($reset) {
|
||||
Html::resetSeenIds();
|
||||
}
|
||||
$this->assertSame($expected, Html::getUniqueId($source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testHtmlGetId().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestHtmlGetUniqueId() {
|
||||
$id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
|
||||
return [
|
||||
// Verify that letters, digits, and hyphens are not stripped from the ID.
|
||||
[$id, $id],
|
||||
// Verify that invalid characters are stripped from the ID.
|
||||
['invalididentifier', 'invalid,./:@\\^`{Üidentifier'],
|
||||
// Verify Drupal coding standards are enforced.
|
||||
['id-name-1', 'ID NAME_[1]'],
|
||||
// Verify that a repeated ID is made unique.
|
||||
['test-unique-id', 'test-unique-id', TRUE],
|
||||
['test-unique-id--2', 'test-unique-id'],
|
||||
['test-unique-id--3', 'test-unique-id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Html::getUniqueId() method.
|
||||
*
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $source
|
||||
* The string being transformed to an ID.
|
||||
*
|
||||
* @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
|
||||
*
|
||||
* @covers ::getUniqueId
|
||||
*/
|
||||
public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
|
||||
Html::setIsAjax(TRUE);
|
||||
$id = Html::getUniqueId($source);
|
||||
|
||||
// Note, we truncate two hyphens at the end.
|
||||
// @see \Drupal\Component\Utility\Html::getId()
|
||||
if (strpos($source, '--') !== FALSE) {
|
||||
$random_suffix = substr($id, strlen($source) + 1);
|
||||
}
|
||||
else {
|
||||
$random_suffix = substr($id, strlen($source) + 2);
|
||||
}
|
||||
$expected = $expected . $random_suffix;
|
||||
$this->assertSame($expected, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testHtmlGetId().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestHtmlGetUniqueIdWithAjaxIds() {
|
||||
return [
|
||||
['test-unique-id1--', 'test-unique-id1'],
|
||||
// Note, we truncate two hyphens at the end.
|
||||
// @see \Drupal\Component\Utility\Html::getId()
|
||||
['test-unique-id1---', 'test-unique-id1--'],
|
||||
['test-unique-id2--', 'test-unique-id2'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Html::getUniqueId() method.
|
||||
*
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $source
|
||||
* The string being transformed to an ID.
|
||||
*
|
||||
* @dataProvider providerTestHtmlGetId
|
||||
*
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testHtmlGetId($expected, $source) {
|
||||
Html::setIsAjax(FALSE);
|
||||
$this->assertSame($expected, Html::getId($source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testHtmlGetId().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestHtmlGetId() {
|
||||
$id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
|
||||
return [
|
||||
// Verify that letters, digits, and hyphens are not stripped from the ID.
|
||||
[$id, $id],
|
||||
// Verify that invalid characters are stripped from the ID.
|
||||
['invalididentifier', 'invalid,./:@\\^`{Üidentifier'],
|
||||
// Verify Drupal coding standards are enforced.
|
||||
['id-name-1', 'ID NAME_[1]'],
|
||||
// Verify that a repeated ID is made unique.
|
||||
['test-unique-id', 'test-unique-id'],
|
||||
['test-unique-id', 'test-unique-id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Html::decodeEntities().
|
||||
*
|
||||
* @dataProvider providerDecodeEntities
|
||||
* @covers ::decodeEntities
|
||||
*/
|
||||
public function testDecodeEntities($text, $expected) {
|
||||
$this->assertEquals($expected, Html::decodeEntities($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testDecodeEntities().
|
||||
*
|
||||
* @see testDecodeEntities()
|
||||
*/
|
||||
public function providerDecodeEntities() {
|
||||
return [
|
||||
['Drupal', 'Drupal'],
|
||||
['<script>', '<script>'],
|
||||
['<script>', '<script>'],
|
||||
['<script>', '<script>'],
|
||||
['&lt;script&gt;', '<script>'],
|
||||
['"', '"'],
|
||||
['"', '"'],
|
||||
['&#34;', '"'],
|
||||
['"', '"'],
|
||||
['&quot;', '"'],
|
||||
["'", "'"],
|
||||
[''', "'"],
|
||||
['&#39;', '''],
|
||||
['©', '©'],
|
||||
['©', '©'],
|
||||
['©', '©'],
|
||||
['→', '→'],
|
||||
['→', '→'],
|
||||
['➼', '➼'],
|
||||
['➼', '➼'],
|
||||
['€', '€'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Html::escape().
|
||||
*
|
||||
* @dataProvider providerEscape
|
||||
* @covers ::escape
|
||||
*/
|
||||
public function testEscape($expected, $text) {
|
||||
$this->assertEquals($expected, Html::escape($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testEscape().
|
||||
*
|
||||
* @see testEscape()
|
||||
*/
|
||||
public function providerEscape() {
|
||||
return [
|
||||
['Drupal', 'Drupal'],
|
||||
['<script>', '<script>'],
|
||||
['&lt;script&gt;', '<script>'],
|
||||
['&#34;', '"'],
|
||||
['"', '"'],
|
||||
['&quot;', '"'],
|
||||
[''', "'"],
|
||||
['&#039;', '''],
|
||||
['©', '©'],
|
||||
['→', '→'],
|
||||
['➼', '➼'],
|
||||
['€', '€'],
|
||||
['Drup<75>al', "Drup\x80al"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests relationship between escaping and decoding HTML entities.
|
||||
*
|
||||
* @covers ::decodeEntities
|
||||
* @covers ::escape
|
||||
*/
|
||||
public function testDecodeEntitiesAndEscape() {
|
||||
$string = "<em>répété</em>";
|
||||
$escaped = Html::escape($string);
|
||||
$this->assertSame('<em>répét&eacute;</em>', $escaped);
|
||||
$decoded = Html::decodeEntities($escaped);
|
||||
$this->assertSame('<em>répété</em>', $decoded);
|
||||
$decoded = Html::decodeEntities($decoded);
|
||||
$this->assertSame('<em>répété</em>', $decoded);
|
||||
$escaped = Html::escape($decoded);
|
||||
$this->assertSame('<em>répété</em>', $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::transformRootRelativeUrlsToAbsolute
|
||||
* @dataProvider providerTestTransformRootRelativeUrlsToAbsolute
|
||||
*/
|
||||
public function testTransformRootRelativeUrlsToAbsolute($html, $scheme_and_host, $expected_html) {
|
||||
$this->assertSame($expected_html ?: $html, Html::transformRootRelativeUrlsToAbsolute($html, $scheme_and_host));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::transformRootRelativeUrlsToAbsolute
|
||||
* @dataProvider providerTestTransformRootRelativeUrlsToAbsoluteAssertion
|
||||
*/
|
||||
public function testTransformRootRelativeUrlsToAbsoluteAssertion($scheme_and_host) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\AssertionError::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\AssertionError::class);
|
||||
}
|
||||
Html::transformRootRelativeUrlsToAbsolute('', $scheme_and_host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testTransformRootRelativeUrlsToAbsolute().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestTransformRootRelativeUrlsToAbsolute() {
|
||||
$data = [];
|
||||
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
// One random tag name.
|
||||
$tag_name = strtolower($random->name(8, TRUE));
|
||||
|
||||
// A site installed either in the root of a domain or a subdirectory.
|
||||
$base_paths = ['/', '/subdir/' . $random->name(8, TRUE) . '/'];
|
||||
|
||||
foreach ($base_paths as $base_path) {
|
||||
// The only attribute that has more than just a URL as its value, is
|
||||
// 'srcset', so special-case it.
|
||||
$data += [
|
||||
"$tag_name, srcset, $base_path: root-relative" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, {$base_path}root-relative 300w\">root-relative test</$tag_name>", 'http://example.com', "<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, http://example.com{$base_path}root-relative 300w\">root-relative test</$tag_name>"],
|
||||
"$tag_name, srcset, $base_path: protocol-relative" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, //example.com{$base_path}protocol-relative 300w\">protocol-relative test</$tag_name>", 'http://example.com', FALSE],
|
||||
"$tag_name, srcset, $base_path: absolute" => ["<$tag_name srcset=\"http://example.com{$base_path}already-absolute 200w, http://example.com{$base_path}absolute 300w\">absolute test</$tag_name>", 'http://example.com', FALSE],
|
||||
];
|
||||
|
||||
foreach (['href', 'poster', 'src', 'cite', 'data', 'action', 'formaction', 'about'] as $attribute) {
|
||||
$data += [
|
||||
"$tag_name, $attribute, $base_path: root-relative" => ["<$tag_name $attribute=\"{$base_path}root-relative\">root-relative test</$tag_name>", 'http://example.com', "<$tag_name $attribute=\"http://example.com{$base_path}root-relative\">root-relative test</$tag_name>"],
|
||||
"$tag_name, $attribute, $base_path: protocol-relative" => ["<$tag_name $attribute=\"//example.com{$base_path}protocol-relative\">protocol-relative test</$tag_name>", 'http://example.com', FALSE],
|
||||
"$tag_name, $attribute, $base_path: absolute" => ["<$tag_name $attribute=\"http://example.com{$base_path}absolute\">absolute test</$tag_name>", 'http://example.com', FALSE],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testTransformRootRelativeUrlsToAbsoluteAssertion().
|
||||
*
|
||||
* @return array
|
||||
* Test data.
|
||||
*/
|
||||
public function providerTestTransformRootRelativeUrlsToAbsoluteAssertion() {
|
||||
return [
|
||||
'only relative path' => ['llama'],
|
||||
'only root-relative path' => ['/llama'],
|
||||
'host and path' => ['example.com/llama'],
|
||||
'scheme, host and path' => ['http://example.com/llama'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an object's __toString() method as returning markup.
|
||||
*/
|
||||
class HtmlTestMarkup implements MarkupInterface {
|
||||
use MarkupTrait;
|
||||
|
||||
}
|
||||
158
2017/web/core/tests/Drupal/Tests/Component/Utility/ImageTest.php
Normal file
158
2017/web/core/tests/Drupal/Tests/Component/Utility/ImageTest.php
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Image;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Image
|
||||
* @group Image
|
||||
*/
|
||||
class ImageTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests all control flow branches in image_dimensions_scale().
|
||||
*
|
||||
* @dataProvider providerTestScaleDimensions
|
||||
*/
|
||||
public function testScaleDimensions($input, $output) {
|
||||
// Process the test dataset.
|
||||
$return_value = Image::scaleDimensions($input['dimensions'], $input['width'], $input['height'], $input['upscale']);
|
||||
|
||||
// Check the width.
|
||||
$this->assertEquals($output['dimensions']['width'], $input['dimensions']['width'], sprintf('Computed width (%s) does not equal expected width (%s)', $output['dimensions']['width'], $input['dimensions']['width']));
|
||||
|
||||
// Check the height.
|
||||
$this->assertEquals($output['dimensions']['height'], $input['dimensions']['height'], sprintf('Computed height (%s) does not equal expected height (%s)', $output['dimensions']['height'], $input['dimensions']['height']));
|
||||
|
||||
// Check the return value.
|
||||
$this->assertEquals($output['return_value'], $return_value, 'Incorrect return value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for image dimension scale tests.
|
||||
*
|
||||
* @return array
|
||||
* Keyed array containing:
|
||||
* - 'input' - Array which contains input for Image::scaleDimensions().
|
||||
* - 'output' - Array which contains expected output after passing
|
||||
* through Image::scaleDimensions. Also contains a boolean
|
||||
* 'return_value' which should match the expected return value.
|
||||
*
|
||||
* @see testScaleDimensions()
|
||||
*/
|
||||
public function providerTestScaleDimensions() {
|
||||
// Define input / output datasets to test different branch conditions.
|
||||
$tests = [];
|
||||
|
||||
// Test branch conditions:
|
||||
// - No height.
|
||||
// - Upscale, don't need to upscale.
|
||||
$tests[] = [
|
||||
'input' => [
|
||||
'dimensions' => [
|
||||
'width' => 1000,
|
||||
'height' => 2000,
|
||||
],
|
||||
'width' => 200,
|
||||
'height' => NULL,
|
||||
'upscale' => TRUE,
|
||||
],
|
||||
'output' => [
|
||||
'dimensions' => [
|
||||
'width' => 200,
|
||||
'height' => 400,
|
||||
],
|
||||
'return_value' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
// Test branch conditions:
|
||||
// - No width.
|
||||
// - Don't upscale, don't need to upscale.
|
||||
$tests[] = [
|
||||
'input' => [
|
||||
'dimensions' => [
|
||||
'width' => 1000,
|
||||
'height' => 800,
|
||||
],
|
||||
'width' => NULL,
|
||||
'height' => 140,
|
||||
'upscale' => FALSE,
|
||||
],
|
||||
'output' => [
|
||||
'dimensions' => [
|
||||
'width' => 175,
|
||||
'height' => 140,
|
||||
],
|
||||
'return_value' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
// Test branch conditions:
|
||||
// - Source aspect ratio greater than target.
|
||||
// - Upscale, need to upscale.
|
||||
$tests[] = [
|
||||
'input' => [
|
||||
'dimensions' => [
|
||||
'width' => 8,
|
||||
'height' => 20,
|
||||
],
|
||||
'width' => 200,
|
||||
'height' => 140,
|
||||
'upscale' => TRUE,
|
||||
],
|
||||
'output' => [
|
||||
'dimensions' => [
|
||||
'width' => 56,
|
||||
'height' => 140,
|
||||
],
|
||||
'return_value' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
// Test branch condition: target aspect ratio greater than source.
|
||||
$tests[] = [
|
||||
'input' => [
|
||||
'dimensions' => [
|
||||
'width' => 2000,
|
||||
'height' => 800,
|
||||
],
|
||||
'width' => 200,
|
||||
'height' => 140,
|
||||
'upscale' => FALSE,
|
||||
],
|
||||
'output' => [
|
||||
'dimensions' => [
|
||||
'width' => 200,
|
||||
'height' => 80,
|
||||
],
|
||||
'return_value' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
// Test branch condition: don't upscale, need to upscale.
|
||||
$tests[] = [
|
||||
'input' => [
|
||||
'dimensions' => [
|
||||
'width' => 100,
|
||||
'height' => 50,
|
||||
],
|
||||
'width' => 200,
|
||||
'height' => 140,
|
||||
'upscale' => FALSE,
|
||||
],
|
||||
'output' => [
|
||||
'dimensions' => [
|
||||
'width' => 100,
|
||||
'height' => 50,
|
||||
],
|
||||
'return_value' => FALSE,
|
||||
],
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Mail;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test mail helpers implemented in Mail component.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Mail
|
||||
*/
|
||||
class MailTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests RFC-2822 'display-name' formatter.
|
||||
*
|
||||
* @dataProvider providerTestDisplayName
|
||||
* @covers ::formatDisplayName
|
||||
*/
|
||||
public function testFormatDisplayName($string, $safe_display_name) {
|
||||
$this->assertEquals($safe_display_name, Mail::formatDisplayName($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFormatDisplayName().
|
||||
*
|
||||
* @see testFormatDisplayName()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its 'display-name' safe value.
|
||||
*/
|
||||
public function providerTestDisplayName() {
|
||||
return [
|
||||
// Simple ASCII characters.
|
||||
['Test site', 'Test site'],
|
||||
// ASCII with html entity.
|
||||
['Test & site', 'Test & site'],
|
||||
// Non-ASCII characters.
|
||||
['Tést site', '=?UTF-8?B?VMOpc3Qgc2l0ZQ==?='],
|
||||
// Non-ASCII with special characters.
|
||||
['Tést; site', '=?UTF-8?B?VMOpc3Q7IHNpdGU=?='],
|
||||
// Non-ASCII with html entity.
|
||||
['Tést; site', '=?UTF-8?B?VMOpc3Q7IHNpdGU=?='],
|
||||
// ASCII with special characters.
|
||||
['Test; site', '"Test; site"'],
|
||||
// ASCII with special characters as html entity.
|
||||
['Test < site', '"Test < site"'],
|
||||
// ASCII with special characters and '\'.
|
||||
['Test; \ "site"', '"Test; \\\\ \"site\""'],
|
||||
// String already RFC-2822 compliant.
|
||||
['"Test; site"', '"Test; site"'],
|
||||
// String already RFC-2822 compliant.
|
||||
['"Test; \\\\ \"site\""', '"Test; \\\\ \"site\""'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Utility\NestedArray
|
||||
* @group Utility
|
||||
*/
|
||||
class NestedArrayTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Form array to check.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* Array of parents for the nested element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parents;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a form structure with a nested element.
|
||||
$this->form['details']['element'] = [
|
||||
'#value' => 'Nested element',
|
||||
];
|
||||
|
||||
// Set up parent array.
|
||||
$this->parents = ['details', 'element'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting nested array values.
|
||||
*
|
||||
* @covers ::getValue
|
||||
*/
|
||||
public function testGetValue() {
|
||||
// Verify getting a value of a nested element.
|
||||
$value = NestedArray::getValue($this->form, $this->parents);
|
||||
$this->assertSame('Nested element', $value['#value'], 'Nested element value found.');
|
||||
|
||||
// Verify changing a value of a nested element by reference.
|
||||
$value = &NestedArray::getValue($this->form, $this->parents);
|
||||
$value['#value'] = 'New value';
|
||||
$value = NestedArray::getValue($this->form, $this->parents);
|
||||
$this->assertSame('New value', $value['#value'], 'Nested element value was changed by reference.');
|
||||
$this->assertSame('New value', $this->form['details']['element']['#value'], 'Nested element value was changed by reference.');
|
||||
|
||||
// Verify that an existing key is reported back.
|
||||
$key_exists = NULL;
|
||||
NestedArray::getValue($this->form, $this->parents, $key_exists);
|
||||
$this->assertTrue($key_exists, 'Existing key found.');
|
||||
|
||||
// Verify that a non-existing key is reported back and throws no errors.
|
||||
$key_exists = NULL;
|
||||
$parents = $this->parents;
|
||||
$parents[] = 'foo';
|
||||
NestedArray::getValue($this->form, $parents, $key_exists);
|
||||
$this->assertFalse($key_exists, 'Non-existing key not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting nested array values.
|
||||
*
|
||||
* @covers ::setValue
|
||||
*/
|
||||
public function testSetValue() {
|
||||
$new_value = [
|
||||
'#value' => 'New value',
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
// Verify setting the value of a nested element.
|
||||
NestedArray::setValue($this->form, $this->parents, $new_value);
|
||||
$this->assertSame('New value', $this->form['details']['element']['#value'], 'Changed nested element value found.');
|
||||
$this->assertTrue($this->form['details']['element']['#required'], 'New nested element value found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests force-setting values.
|
||||
*
|
||||
* @covers ::setValue
|
||||
*/
|
||||
public function testSetValueForce() {
|
||||
$new_value = [
|
||||
'one',
|
||||
];
|
||||
$this->form['details']['non-array-parent'] = 'string';
|
||||
$parents = ['details', 'non-array-parent', 'child'];
|
||||
NestedArray::setValue($this->form, $parents, $new_value, TRUE);
|
||||
$this->assertSame($new_value, $this->form['details']['non-array-parent']['child'], 'The nested element was not forced to the new value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests unsetting nested array values.
|
||||
*
|
||||
* @covers ::unsetValue
|
||||
*/
|
||||
public function testUnsetValue() {
|
||||
// Verify unsetting a non-existing nested element throws no errors and the
|
||||
// non-existing key is properly reported.
|
||||
$key_existed = NULL;
|
||||
$parents = $this->parents;
|
||||
$parents[] = 'foo';
|
||||
NestedArray::unsetValue($this->form, $parents, $key_existed);
|
||||
$this->assertTrue(isset($this->form['details']['element']['#value']), 'Outermost nested element key still exists.');
|
||||
$this->assertFalse($key_existed, 'Non-existing key not found.');
|
||||
|
||||
// Verify unsetting a nested element.
|
||||
$key_existed = NULL;
|
||||
NestedArray::unsetValue($this->form, $this->parents, $key_existed);
|
||||
$this->assertFalse(isset($this->form['details']['element']), 'Removed nested element not found.');
|
||||
$this->assertTrue($key_existed, 'Existing key was found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests existence of array key.
|
||||
*/
|
||||
public function testKeyExists() {
|
||||
// Verify that existing key is found.
|
||||
$this->assertTrue(NestedArray::keyExists($this->form, $this->parents), 'Nested key found.');
|
||||
|
||||
// Verify that non-existing keys are not found.
|
||||
$parents = $this->parents;
|
||||
$parents[] = 'foo';
|
||||
$this->assertFalse(NestedArray::keyExists($this->form, $parents), 'Non-existing nested key not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests NestedArray::mergeDeepArray().
|
||||
*
|
||||
* @covers ::mergeDeep
|
||||
* @covers ::mergeDeepArray
|
||||
*/
|
||||
public function testMergeDeepArray() {
|
||||
$link_options_1 = [
|
||||
'fragment' => 'x',
|
||||
'attributes' => ['title' => 'X', 'class' => ['a', 'b']],
|
||||
'language' => 'en',
|
||||
];
|
||||
$link_options_2 = [
|
||||
'fragment' => 'y',
|
||||
'attributes' => ['title' => 'Y', 'class' => ['c', 'd']],
|
||||
'absolute' => TRUE,
|
||||
];
|
||||
$expected = [
|
||||
'fragment' => 'y',
|
||||
'attributes' => ['title' => 'Y', 'class' => ['a', 'b', 'c', 'd']],
|
||||
'language' => 'en',
|
||||
'absolute' => TRUE,
|
||||
];
|
||||
$this->assertSame($expected, NestedArray::mergeDeepArray([$link_options_1, $link_options_2]), 'NestedArray::mergeDeepArray() returned a properly merged array.');
|
||||
// Test wrapper function, NestedArray::mergeDeep().
|
||||
$this->assertSame($expected, NestedArray::mergeDeep($link_options_1, $link_options_2), 'NestedArray::mergeDeep() returned a properly merged array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that arrays with implicit keys are appended, not merged.
|
||||
*
|
||||
* @covers ::mergeDeepArray
|
||||
*/
|
||||
public function testMergeImplicitKeys() {
|
||||
$a = [
|
||||
'subkey' => ['X', 'Y'],
|
||||
];
|
||||
$b = [
|
||||
'subkey' => ['X'],
|
||||
];
|
||||
|
||||
// Drupal core behavior.
|
||||
$expected = [
|
||||
'subkey' => ['X', 'Y', 'X'],
|
||||
];
|
||||
$actual = NestedArray::mergeDeepArray([$a, $b]);
|
||||
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() creates new numeric keys in the implicit sequence.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that even with explicit keys, values are appended, not merged.
|
||||
*
|
||||
* @covers ::mergeDeepArray
|
||||
*/
|
||||
public function testMergeExplicitKeys() {
|
||||
$a = [
|
||||
'subkey' => [
|
||||
0 => 'A',
|
||||
1 => 'B',
|
||||
],
|
||||
];
|
||||
$b = [
|
||||
'subkey' => [
|
||||
0 => 'C',
|
||||
1 => 'D',
|
||||
],
|
||||
];
|
||||
|
||||
// Drupal core behavior.
|
||||
$expected = [
|
||||
'subkey' => [
|
||||
0 => 'A',
|
||||
1 => 'B',
|
||||
2 => 'C',
|
||||
3 => 'D',
|
||||
],
|
||||
];
|
||||
$actual = NestedArray::mergeDeepArray([$a, $b]);
|
||||
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() creates new numeric keys in the explicit sequence.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that array keys values on the first array are ignored when merging.
|
||||
*
|
||||
* Even if the initial ordering would place the data from the second array
|
||||
* before those in the first one, they are still appended, and the keys on
|
||||
* the first array are deleted and regenerated.
|
||||
*
|
||||
* @covers ::mergeDeepArray
|
||||
*/
|
||||
public function testMergeOutOfSequenceKeys() {
|
||||
$a = [
|
||||
'subkey' => [
|
||||
10 => 'A',
|
||||
30 => 'B',
|
||||
],
|
||||
];
|
||||
$b = [
|
||||
'subkey' => [
|
||||
20 => 'C',
|
||||
0 => 'D',
|
||||
],
|
||||
];
|
||||
|
||||
// Drupal core behavior.
|
||||
$expected = [
|
||||
'subkey' => [
|
||||
0 => 'A',
|
||||
1 => 'B',
|
||||
2 => 'C',
|
||||
3 => 'D',
|
||||
],
|
||||
];
|
||||
$actual = NestedArray::mergeDeepArray([$a, $b]);
|
||||
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() ignores numeric key order when merging.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::filter
|
||||
* @dataProvider providerTestFilter
|
||||
*/
|
||||
public function testFilter($array, $callable, $expected) {
|
||||
$this->assertEquals($expected, NestedArray::filter($array, $callable));
|
||||
}
|
||||
|
||||
public function providerTestFilter() {
|
||||
$data = [];
|
||||
$data['1d-array'] = [
|
||||
[0, 1, '', TRUE], NULL, [1 => 1, 3 => TRUE],
|
||||
];
|
||||
$data['1d-array-callable'] = [
|
||||
[0, 1, '', TRUE],
|
||||
function ($element) {
|
||||
return $element === '';
|
||||
},
|
||||
[2 => ''],
|
||||
];
|
||||
$data['2d-array'] = [
|
||||
[[0, 1, '', TRUE], [0, 1, 2, 3]], NULL, [0 => [1 => 1, 3 => TRUE], 1 => [1 => 1, 2 => 2, 3 => 3]],
|
||||
];
|
||||
$data['2d-array-callable'] = [
|
||||
[[0, 1, '', TRUE], [0, 1, 2, 3]],
|
||||
function ($element) {
|
||||
return is_array($element) || $element === 3;
|
||||
},
|
||||
[0 => [], 1 => [3 => 3]],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Number;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests number manipulation utilities.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Number
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Number
|
||||
*/
|
||||
class NumberTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests Number::validStep() without offset.
|
||||
*
|
||||
* @dataProvider providerTestValidStep
|
||||
* @covers ::validStep
|
||||
*
|
||||
* @param numeric $value
|
||||
* The value argument for Number::validStep().
|
||||
* @param numeric $step
|
||||
* The step argument for Number::validStep().
|
||||
* @param bool $expected
|
||||
* Expected return value from Number::validStep().
|
||||
*/
|
||||
public function testValidStep($value, $step, $expected) {
|
||||
$return = Number::validStep($value, $step);
|
||||
$this->assertEquals($expected, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Number::validStep() with offset.
|
||||
*
|
||||
* @dataProvider providerTestValidStepOffset
|
||||
* @covers ::validStep
|
||||
*
|
||||
* @param numeric $value
|
||||
* The value argument for Number::validStep().
|
||||
* @param numeric $step
|
||||
* The step argument for Number::validStep().
|
||||
* @param numeric $offset
|
||||
* The offset argument for Number::validStep().
|
||||
* @param bool $expected
|
||||
* Expected return value from Number::validStep().
|
||||
*/
|
||||
public function testValidStepOffset($value, $step, $offset, $expected) {
|
||||
$return = Number::validStep($value, $step, $offset);
|
||||
$this->assertEquals($expected, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testNumberStep().
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\Number::testValidStep
|
||||
*/
|
||||
public static function providerTestValidStep() {
|
||||
return [
|
||||
// Value and step equal.
|
||||
[10.3, 10.3, TRUE],
|
||||
|
||||
// Valid integer steps.
|
||||
[42, 21, TRUE],
|
||||
[42, 3, TRUE],
|
||||
|
||||
// Valid float steps.
|
||||
[42, 10.5, TRUE],
|
||||
[1, 1 / 3, TRUE],
|
||||
[-100, 100 / 7, TRUE],
|
||||
[1000, -10, TRUE],
|
||||
|
||||
// Valid and very small float steps.
|
||||
[1000.12345, 1e-10, TRUE],
|
||||
[3.9999999999999, 1e-13, TRUE],
|
||||
|
||||
// Invalid integer steps.
|
||||
[100, 30, FALSE],
|
||||
[-10, 4, FALSE],
|
||||
|
||||
// Invalid float steps.
|
||||
[6, 5 / 7, FALSE],
|
||||
[10.3, 10.25, FALSE],
|
||||
|
||||
// Step mismatches very close to being valid.
|
||||
[70 + 9e-7, 10 + 9e-7, FALSE],
|
||||
[1936.5, 3e-8, FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset().
|
||||
*
|
||||
* @see \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset()
|
||||
*/
|
||||
public static function providerTestValidStepOffset() {
|
||||
return [
|
||||
// Try obvious fits.
|
||||
[11.3, 10.3, 1, TRUE],
|
||||
[100, 10, 50, TRUE],
|
||||
[-100, 90 / 7, -10, TRUE],
|
||||
[2 / 7 + 5 / 9, 1 / 7, 5 / 9, TRUE],
|
||||
|
||||
// Ensure a small offset is still invalid.
|
||||
[10.3, 10.3, 0.0001, FALSE],
|
||||
[1 / 5, 1 / 7, 1 / 11, FALSE],
|
||||
|
||||
// Try negative values and offsets.
|
||||
[1000, 10, -5, FALSE],
|
||||
[-10, 4, 0, FALSE],
|
||||
[-10, 4, -4, FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the alphadecimal conversion functions.
|
||||
*
|
||||
* @dataProvider providerTestConversions
|
||||
* @covers ::intToAlphadecimal
|
||||
* @covers ::alphadecimalToInt
|
||||
*
|
||||
* @param int $value
|
||||
* The integer value.
|
||||
* @param string $expected
|
||||
* The expected alphadecimal value.
|
||||
*/
|
||||
public function testConversions($value, $expected) {
|
||||
$this->assertSame(Number::intToAlphadecimal($value), $expected);
|
||||
$this->assertSame($value, Number::alphadecimalToInt($expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testConversions().
|
||||
*
|
||||
* @see testConversions()
|
||||
*
|
||||
* @return array
|
||||
* An array containing:
|
||||
* - The integer value.
|
||||
* - The alphadecimal value.
|
||||
*/
|
||||
public function providerTestConversions() {
|
||||
return [
|
||||
[0, '00'],
|
||||
[1, '01'],
|
||||
[10, '0a'],
|
||||
[20, '0k'],
|
||||
[35, '0z'],
|
||||
[36, '110'],
|
||||
[100, '12s'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests random data generation.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Random
|
||||
*/
|
||||
class RandomTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The first random string passed to the test callback.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\RandomTest::_RandomStringValidate()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $firstStringGenerated = '';
|
||||
|
||||
/**
|
||||
* Tests unique random string generation.
|
||||
*
|
||||
* @covers ::string
|
||||
*/
|
||||
public function testRandomStringUniqueness() {
|
||||
$strings = [];
|
||||
$random = new Random();
|
||||
for ($i = 0; $i <= 50; $i++) {
|
||||
$str = $random->string(1, TRUE);
|
||||
$this->assertFalse(isset($strings[$str]), 'Generated duplicate random string ' . $str);
|
||||
$strings[$str] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests unique random name generation.
|
||||
*
|
||||
* @covers ::name
|
||||
*/
|
||||
public function testRandomNamesUniqueness() {
|
||||
$names = [];
|
||||
$random = new Random();
|
||||
for ($i = 0; $i <= 10; $i++) {
|
||||
$str = $random->name(1, TRUE);
|
||||
$this->assertFalse(isset($names[$str]), 'Generated duplicate random name ' . $str);
|
||||
$names[$str] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests infinite loop prevention whilst generating random names.
|
||||
*
|
||||
* @covers ::name
|
||||
*/
|
||||
public function testRandomNameException() {
|
||||
// There are fewer than 100 possibilities so an exception should occur to
|
||||
// prevent infinite loops.
|
||||
$random = new Random();
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class);
|
||||
}
|
||||
for ($i = 0; $i <= 100; $i++) {
|
||||
$str = $random->name(1, TRUE);
|
||||
$names[$str] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests infinite loop prevention whilst generating random strings.
|
||||
*
|
||||
* @covers ::string
|
||||
*/
|
||||
public function testRandomStringException() {
|
||||
// There are fewer than 100 possibilities so an exception should occur to
|
||||
// prevent infinite loops.
|
||||
$random = new Random();
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\RuntimeException::class);
|
||||
}
|
||||
for ($i = 0; $i <= 100; $i++) {
|
||||
$str = $random->string(1, TRUE);
|
||||
$names[$str] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests random name generation if uniqueness is not enforced.
|
||||
*
|
||||
* @covers ::name
|
||||
*/
|
||||
public function testRandomNameNonUnique() {
|
||||
// There are fewer than 100 possibilities if we were forcing uniqueness so
|
||||
// exception would occur.
|
||||
$random = new Random();
|
||||
for ($i = 0; $i <= 100; $i++) {
|
||||
$random->name(1);
|
||||
}
|
||||
$this->assertTrue(TRUE, 'No exception thrown when uniqueness is not enforced.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests random string if uniqueness is not enforced.
|
||||
*
|
||||
* @covers ::string
|
||||
*/
|
||||
public function testRandomStringNonUnique() {
|
||||
// There are fewer than 100 possibilities if we were forcing uniqueness so
|
||||
// exception would occur.
|
||||
$random = new Random();
|
||||
for ($i = 0; $i <= 100; $i++) {
|
||||
$random->string(1);
|
||||
}
|
||||
$this->assertTrue(TRUE, 'No exception thrown when uniqueness is not enforced.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests random object generation to ensure the expected number of properties.
|
||||
*
|
||||
* @covers ::object
|
||||
*/
|
||||
public function testRandomObject() {
|
||||
// For values of 0 and 1 \Drupal\Component\Utility\Random::object() will
|
||||
// have different execution paths.
|
||||
$random = new Random();
|
||||
for ($i = 0; $i <= 1; $i++) {
|
||||
$obj = $random->object($i);
|
||||
$this->assertEquals($i, count(get_object_vars($obj)), 'Generated random object has expected number of properties');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests random string validation callbacks.
|
||||
*
|
||||
* @covers ::string
|
||||
*/
|
||||
public function testRandomStringValidator() {
|
||||
$random = new Random();
|
||||
$this->firstStringGenerated = '';
|
||||
$str = $random->string(1, TRUE, [$this, '_RandomStringValidate']);
|
||||
$this->assertNotEquals($this->firstStringGenerated, $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for random string validation.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Random::name()
|
||||
* @see \Drupal\Tests\Component\Utility\RandomTest::testRandomStringValidator()
|
||||
*
|
||||
* @param string $string
|
||||
* The random string to validate.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the random string is valid, FALSE if not.
|
||||
*/
|
||||
public function _RandomStringValidate($string) {
|
||||
// Return FALSE for the first generated string and any string that is the
|
||||
// same, as the test expects a different string to be returned.
|
||||
if (empty($this->firstStringGenerated) || $string == $this->firstStringGenerated) {
|
||||
$this->firstStringGenerated = $string;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Rectangle;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Rectangle
|
||||
* @group Image
|
||||
*/
|
||||
class RectangleTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests wrong rectangle width.
|
||||
*
|
||||
* @covers ::rotate
|
||||
*/
|
||||
public function testWrongWidth() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\InvalidArgumentException::class);
|
||||
}
|
||||
$rect = new Rectangle(-40, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests wrong rectangle height.
|
||||
*
|
||||
* @covers ::rotate
|
||||
*/
|
||||
public function testWrongHeight() {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\InvalidArgumentException::class);
|
||||
}
|
||||
$rect = new Rectangle(40, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting rectangle dimensions after a rotation operation.
|
||||
*
|
||||
* @param int $width
|
||||
* The width of the rectangle.
|
||||
* @param int $height
|
||||
* The height of the rectangle.
|
||||
* @param float $angle
|
||||
* The angle for rotation.
|
||||
* @param int $exp_width
|
||||
* The expected width of the rotated rectangle.
|
||||
* @param int $exp_height
|
||||
* The expected height of the rotated rectangle.
|
||||
*
|
||||
* @covers ::rotate
|
||||
* @covers ::getBoundingWidth
|
||||
* @covers ::getBoundingHeight
|
||||
*
|
||||
* @dataProvider providerPhp55RotateDimensions
|
||||
*/
|
||||
public function testRotateDimensions($width, $height, $angle, $exp_width, $exp_height) {
|
||||
$rect = new Rectangle($width, $height);
|
||||
$rect->rotate($angle);
|
||||
$this->assertEquals($exp_width, $rect->getBoundingWidth());
|
||||
$this->assertEquals($exp_height, $rect->getBoundingHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for image dimension rotation tests.
|
||||
*
|
||||
* This dataset sample was generated by running on PHP 5.5 the function below
|
||||
* - first, for all integer rotation angles (-360 to 360) on a rectangle
|
||||
* 40x20;
|
||||
* - second, for 500 random float rotation angle in the range -360 to 360 on
|
||||
* a rectangle 40x20;
|
||||
* - third, on 1000 rectangles of random WxH rotated to a random float angle
|
||||
* in the range -360 to 360
|
||||
* - fourth, on 2000 rectangles of random WxH rotated to a random integer
|
||||
* angle multiple of 30 degrees in the range -360 to 360 (which is the most
|
||||
* tricky case).
|
||||
* Using the GD toolkit operations gives us true data coming from the GD
|
||||
* library that can be used to match against the Rectangle class under test.
|
||||
* @code
|
||||
* protected function rotateResults($width, $height, $angle, &$new_width, &$new_height) {
|
||||
* $image = \Drupal::service('image.factory')->get(NULL, 'gd');
|
||||
* $image->createNew($width, $height);
|
||||
* $old_res = $image->getToolkit()->getResource();
|
||||
* $image->rotate($angle);
|
||||
* $new_width = $image->getWidth();
|
||||
* $new_height = $image->getHeight();
|
||||
* if (is_resource($old_res)) {
|
||||
* imagedestroy($old_res);
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @return array[]
|
||||
* A simple array of simple arrays, each having the following elements:
|
||||
* - original image width
|
||||
* - original image height
|
||||
* - rotation angle in degrees
|
||||
* - expected image width after rotation
|
||||
* - expected image height after rotation
|
||||
*
|
||||
* @see testRotateDimensions()
|
||||
*/
|
||||
public function providerPhp55RotateDimensions() {
|
||||
// The dataset is stored in a .json file because it is very large and causes
|
||||
// problems for PHPCS.
|
||||
return json_decode(file_get_contents(__DIR__ . '/fixtures/RectangleTest.json'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\Utility\SafeMarkupTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Render\HtmlEscapedText;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Render\MarkupTrait;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests marking strings as safe.
|
||||
*
|
||||
* @group Utility
|
||||
* @group legacy
|
||||
* @coversDefaultClass \Drupal\Component\Utility\SafeMarkup
|
||||
*/
|
||||
class SafeMarkupTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown() {
|
||||
parent::tearDown();
|
||||
|
||||
UrlHelper::setAllowedProtocols(['http', 'https']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SafeMarkup::isSafe() with different objects.
|
||||
*
|
||||
* @covers ::isSafe
|
||||
* @expectedDeprecation SafeMarkup::isSafe() is scheduled for removal in Drupal 9.0.0. Instead, you should just check if a variable is an instance of \Drupal\Component\Render\MarkupInterface. See https://www.drupal.org/node/2549395.
|
||||
*/
|
||||
public function testIsSafe() {
|
||||
$safe_string = $this->getMockBuilder('\Drupal\Component\Render\MarkupInterface')->getMock();
|
||||
$this->assertTrue(SafeMarkup::isSafe($safe_string));
|
||||
$string_object = new SafeMarkupTestString('test');
|
||||
$this->assertFalse(SafeMarkup::isSafe($string_object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SafeMarkup::checkPlain().
|
||||
*
|
||||
* @dataProvider providerCheckPlain
|
||||
* @covers ::checkPlain
|
||||
* @expectedDeprecation SafeMarkup::checkPlain() is scheduled for removal in Drupal 9.0.0. Rely on Twig's auto-escaping feature, or use the @link theme_render #plain_text @endlink key when constructing a render array that contains plain text in order to use the renderer's auto-escaping feature. If neither of these are possible, \Drupal\Component\Utility\Html::escape() can be used in places where explicit escaping is needed. See https://www.drupal.org/node/2549395.
|
||||
*
|
||||
* @param string $text
|
||||
* The text to provide to SafeMarkup::checkPlain().
|
||||
* @param string $expected
|
||||
* The expected output from the function.
|
||||
* @param string $message
|
||||
* The message to provide as output for the test.
|
||||
*/
|
||||
public function testCheckPlain($text, $expected, $message) {
|
||||
$result = SafeMarkup::checkPlain($text);
|
||||
$this->assertTrue($result instanceof HtmlEscapedText);
|
||||
$this->assertEquals($expected, $result, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function testHtmlEscapedText($text, $expected, $message) {
|
||||
$result = new HtmlEscapedText($text);
|
||||
$this->assertEquals($expected, $result, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testCheckPlain() and testEscapeString().
|
||||
*
|
||||
* @see testCheckPlain()
|
||||
*/
|
||||
public function providerCheckPlain() {
|
||||
// Checks that invalid multi-byte sequences are escaped.
|
||||
$tests[] = ["Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"'];
|
||||
$tests[] = ["\xc2\"", '<27>"', 'Escapes invalid sequence "\xc2\""'];
|
||||
$tests[] = ["Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"'];
|
||||
|
||||
// Checks that special characters are escaped.
|
||||
$tests[] = [SafeMarkupTestMarkup::create("<script>"), '<script>', 'Escapes <script> even inside an object that implements MarkupInterface.'];
|
||||
$tests[] = ["<script>", '<script>', 'Escapes <script>'];
|
||||
$tests[] = ['<>&"\'', '<>&"'', 'Escapes reserved HTML characters.'];
|
||||
$tests[] = [SafeMarkupTestMarkup::create('<>&"\''), '<>&"'', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.'];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string formatting with SafeMarkup::format().
|
||||
*
|
||||
* @dataProvider providerFormat
|
||||
* @covers ::format
|
||||
* @expectedDeprecation SafeMarkup::format() is scheduled for removal in Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup. See https://www.drupal.org/node/2549395.
|
||||
*
|
||||
* @param string $string
|
||||
* The string to run through SafeMarkup::format().
|
||||
* @param string[] $args
|
||||
* The arguments to pass into SafeMarkup::format().
|
||||
* @param string $expected
|
||||
* The expected result from calling the function.
|
||||
* @param string $message
|
||||
* The message to display as output to the test.
|
||||
* @param bool $expected_is_safe
|
||||
* 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, (string) $result, $message);
|
||||
$this->assertEquals($expected_is_safe, $result instanceof MarkupInterface, 'SafeMarkup::format correctly sets the result as safe or not safe.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFormat().
|
||||
*
|
||||
* @see testFormat()
|
||||
*/
|
||||
public function providerFormat() {
|
||||
$tests[] = ['Simple text', [], 'Simple text', 'SafeMarkup::format leaves simple text alone.', TRUE];
|
||||
$tests[] = ['Escaped text: @value', ['@value' => '<script>'], 'Escaped text: <script>', 'SafeMarkup::format replaces and escapes string.', TRUE];
|
||||
$tests[] = ['Escaped text: @value', ['@value' => SafeMarkupTestMarkup::create('<span>Safe HTML</span>')], 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE];
|
||||
$tests[] = ['Placeholder text: %value', ['%value' => '<script>'], 'Placeholder text: <em class="placeholder"><script></em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE];
|
||||
$tests[] = ['Placeholder text: %value', ['%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&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&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&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/#<'], 'Simple text <a href="http://example.com/#&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="<span>not a url</span>">MUUUH</a>', '', TRUE];
|
||||
|
||||
// Tests non-standard placeholders that will not replace.
|
||||
$tests['non-standard-placeholder'] = ['Hey hey', ['risky' => "<script>alert('foo');</script>"], 'Hey hey', '', TRUE];
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SafeMarkupTestString {
|
||||
|
||||
protected $string;
|
||||
|
||||
public function __construct($string) {
|
||||
$this->string = $string;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an object's __toString() method as returning markup.
|
||||
*/
|
||||
class SafeMarkupTestMarkup implements MarkupInterface {
|
||||
use MarkupTrait;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\SortArray;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the SortArray component.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\SortArray
|
||||
*/
|
||||
class SortArrayTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests SortArray::sortByWeightElement() input against expected output.
|
||||
*
|
||||
* @dataProvider providerSortByWeightElement
|
||||
* @covers ::sortByWeightElement
|
||||
* @covers ::sortByKeyInt
|
||||
*
|
||||
* @param array $a
|
||||
* The first input array for the SortArray::sortByWeightElement() method.
|
||||
* @param array $b
|
||||
* The second input array for the SortArray::sortByWeightElement().
|
||||
* @param int $expected
|
||||
* The expected output from calling the method.
|
||||
*/
|
||||
public function testSortByWeightElement($a, $b, $expected) {
|
||||
$result = SortArray::sortByWeightElement($a, $b);
|
||||
$this->assertBothNegativePositiveOrZero($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for SortArray::sortByWeightElement().
|
||||
*
|
||||
* @return array
|
||||
* An array of tests, matching the parameter inputs for
|
||||
* testSortByWeightElement.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByWeightElement()
|
||||
*/
|
||||
public function providerSortByWeightElement() {
|
||||
$tests = [];
|
||||
|
||||
// Weights set and equal.
|
||||
$tests[] = [
|
||||
['weight' => 1],
|
||||
['weight' => 1],
|
||||
0,
|
||||
];
|
||||
|
||||
// Weights set and $a is less (lighter) than $b.
|
||||
$tests[] = [
|
||||
['weight' => 1],
|
||||
['weight' => 2],
|
||||
-1,
|
||||
];
|
||||
|
||||
// Weights set and $a is greater (heavier) than $b.
|
||||
$tests[] = [
|
||||
['weight' => 2],
|
||||
['weight' => 1],
|
||||
1,
|
||||
];
|
||||
|
||||
// Weights not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
[],
|
||||
0,
|
||||
];
|
||||
|
||||
// Weights for $b not set.
|
||||
$tests[] = [
|
||||
['weight' => 1],
|
||||
[],
|
||||
1,
|
||||
];
|
||||
|
||||
// Weights for $a not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
['weight' => 1],
|
||||
-1,
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SortArray::sortByWeightProperty() input against expected output.
|
||||
*
|
||||
* @dataProvider providerSortByWeightProperty
|
||||
* @covers ::sortByWeightProperty
|
||||
* @covers ::sortByKeyInt
|
||||
*
|
||||
* @param array $a
|
||||
* The first input array for the SortArray::sortByWeightProperty() method.
|
||||
* @param array $b
|
||||
* The second input array for the SortArray::sortByWeightProperty().
|
||||
* @param int $expected
|
||||
* The expected output from calling the method.
|
||||
*/
|
||||
public function testSortByWeightProperty($a, $b, $expected) {
|
||||
$result = SortArray::sortByWeightProperty($a, $b);
|
||||
$this->assertBothNegativePositiveOrZero($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for SortArray::sortByWeightProperty().
|
||||
*
|
||||
* @return array
|
||||
* An array of tests, matching the parameter inputs for
|
||||
* testSortByWeightProperty.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByWeightProperty()
|
||||
*/
|
||||
public function providerSortByWeightProperty() {
|
||||
$tests = [];
|
||||
|
||||
// Weights set and equal.
|
||||
$tests[] = [
|
||||
['#weight' => 1],
|
||||
['#weight' => 1],
|
||||
0,
|
||||
];
|
||||
|
||||
// Weights set and $a is less (lighter) than $b.
|
||||
$tests[] = [
|
||||
['#weight' => 1],
|
||||
['#weight' => 2],
|
||||
-1,
|
||||
];
|
||||
|
||||
// Weights set and $a is greater (heavier) than $b.
|
||||
$tests[] = [
|
||||
['#weight' => 2],
|
||||
['#weight' => 1],
|
||||
1,
|
||||
];
|
||||
|
||||
// Weights not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
[],
|
||||
0,
|
||||
];
|
||||
|
||||
// Weights for $b not set.
|
||||
$tests[] = [
|
||||
['#weight' => 1],
|
||||
[],
|
||||
1,
|
||||
];
|
||||
|
||||
// Weights for $a not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
['#weight' => 1],
|
||||
-1,
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SortArray::sortByTitleElement() input against expected output.
|
||||
*
|
||||
* @dataProvider providerSortByTitleElement
|
||||
* @covers ::sortByTitleElement
|
||||
* @covers ::sortByKeyString
|
||||
*
|
||||
* @param array $a
|
||||
* The first input item for comparison.
|
||||
* @param array $b
|
||||
* The second item for comparison.
|
||||
* @param int $expected
|
||||
* The expected output from calling the method.
|
||||
*/
|
||||
public function testSortByTitleElement($a, $b, $expected) {
|
||||
$result = SortArray::sortByTitleElement($a, $b);
|
||||
$this->assertBothNegativePositiveOrZero($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for SortArray::sortByTitleElement().
|
||||
*
|
||||
* @return array
|
||||
* An array of tests, matching the parameter inputs for
|
||||
* testSortByTitleElement.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByTitleElement()
|
||||
*/
|
||||
public function providerSortByTitleElement() {
|
||||
$tests = [];
|
||||
|
||||
// Titles set and equal.
|
||||
$tests[] = [
|
||||
['title' => 'test'],
|
||||
['title' => 'test'],
|
||||
0,
|
||||
];
|
||||
|
||||
// Title $a not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
['title' => 'test'],
|
||||
-4,
|
||||
];
|
||||
|
||||
// Title $b not set.
|
||||
$tests[] = [
|
||||
['title' => 'test'],
|
||||
[],
|
||||
4,
|
||||
];
|
||||
|
||||
// Titles set but not equal.
|
||||
$tests[] = [
|
||||
['title' => 'test'],
|
||||
['title' => 'testing'],
|
||||
-1,
|
||||
];
|
||||
|
||||
// Titles set but not equal.
|
||||
$tests[] = [
|
||||
['title' => 'testing'],
|
||||
['title' => 'test'],
|
||||
1,
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SortArray::sortByTitleProperty() input against expected output.
|
||||
*
|
||||
* @dataProvider providerSortByTitleProperty
|
||||
* @covers ::sortByTitleProperty
|
||||
* @covers ::sortByKeyString
|
||||
*
|
||||
* @param array $a
|
||||
* The first input item for comparison.
|
||||
* @param array $b
|
||||
* The second item for comparison.
|
||||
* @param int $expected
|
||||
* The expected output from calling the method.
|
||||
*/
|
||||
public function testSortByTitleProperty($a, $b, $expected) {
|
||||
$result = SortArray::sortByTitleProperty($a, $b);
|
||||
$this->assertBothNegativePositiveOrZero($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for SortArray::sortByTitleProperty().
|
||||
*
|
||||
* @return array
|
||||
* An array of tests, matching the parameter inputs for
|
||||
* testSortByTitleProperty.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByTitleProperty()
|
||||
*/
|
||||
public function providerSortByTitleProperty() {
|
||||
$tests = [];
|
||||
|
||||
// Titles set and equal.
|
||||
$tests[] = [
|
||||
['#title' => 'test'],
|
||||
['#title' => 'test'],
|
||||
0,
|
||||
];
|
||||
|
||||
// Title $a not set.
|
||||
$tests[] = [
|
||||
[],
|
||||
['#title' => 'test'],
|
||||
-4,
|
||||
];
|
||||
|
||||
// Title $b not set.
|
||||
$tests[] = [
|
||||
['#title' => 'test'],
|
||||
[],
|
||||
4,
|
||||
];
|
||||
|
||||
// Titles set but not equal.
|
||||
$tests[] = [
|
||||
['#title' => 'test'],
|
||||
['#title' => 'testing'],
|
||||
-1,
|
||||
];
|
||||
|
||||
// Titles set but not equal.
|
||||
$tests[] = [
|
||||
['#title' => 'testing'],
|
||||
['#title' => 'test'],
|
||||
1,
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that numbers are either both negative, both positive or both zero.
|
||||
*
|
||||
* The exact values returned by comparison functions differ between PHP
|
||||
* versions and are considered an "implementation detail".
|
||||
*
|
||||
* @param int $expected
|
||||
* Expected comparison function return value.
|
||||
* @param int $result
|
||||
* Actual comparison function return value.
|
||||
*/
|
||||
protected function assertBothNegativePositiveOrZero($expected, $result) {
|
||||
$this->assertTrue(is_numeric($expected) && is_numeric($result), 'Parameters are numeric.');
|
||||
$this->assertTrue(($expected < 0 && $result < 0) || ($expected > 0 && $result > 0) || ($expected === 0 && $result === 0), 'Numbers are either both negative, both positive or both zero.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
/**
|
||||
* Used by SafeMarkupTest to test that a class with a __toString() method works.
|
||||
*/
|
||||
class TextWrapper {
|
||||
|
||||
/**
|
||||
* The text value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $text = '';
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Tests\Component\Utility\TextWrapper
|
||||
*
|
||||
* @param string $text
|
||||
*/
|
||||
public function __construct($text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Timer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the Timer system.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Timer
|
||||
*/
|
||||
class TimerTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests Timer::read() time accumulation accuracy across multiple restarts.
|
||||
*
|
||||
* @covers ::start
|
||||
* @covers ::stop
|
||||
* @covers ::read
|
||||
*/
|
||||
public function testTimer() {
|
||||
Timer::start('test');
|
||||
usleep(5000);
|
||||
$value = Timer::read('test');
|
||||
usleep(5000);
|
||||
$value2 = Timer::read('test');
|
||||
usleep(5000);
|
||||
$value3 = Timer::read('test');
|
||||
usleep(5000);
|
||||
$value4 = Timer::read('test');
|
||||
|
||||
// Although we sleep for 5 milliseconds, we should test that at least 4 ms
|
||||
// have past because usleep() is not reliable on Windows. See
|
||||
// http://php.net/manual/function.usleep.php for more information. The
|
||||
// purpose of the test to validate that the Timer class can measure elapsed
|
||||
// time not the granularity of usleep() on a particular OS.
|
||||
$this->assertGreaterThanOrEqual(4, $value, 'Timer failed to measure at least 4 milliseconds of sleeping while running.');
|
||||
|
||||
$this->assertGreaterThanOrEqual($value + 4, $value2, 'Timer failed to measure at least 8 milliseconds of sleeping while running.');
|
||||
|
||||
$this->assertGreaterThanOrEqual($value2 + 4, $value3, 'Timer failed to measure at least 12 milliseconds of sleeping while running.');
|
||||
|
||||
$this->assertGreaterThanOrEqual($value3 + 4, $value4, 'Timer failed to measure at least 16 milliseconds of sleeping while running.');
|
||||
|
||||
// Stop the timer.
|
||||
$value5 = Timer::stop('test');
|
||||
$this->assertGreaterThanOrEqual($value4, $value5['time'], 'Timer measured after stopping was not greater than last measurement.');
|
||||
|
||||
// Read again.
|
||||
$value6 = Timer::read('test');
|
||||
$this->assertEquals($value5['time'], $value6, 'Timer measured after stopping was not equal to the stopped time.');
|
||||
|
||||
// Restart.
|
||||
Timer::start('test');
|
||||
usleep(5000);
|
||||
$value7 = Timer::read('test');
|
||||
$this->assertGreaterThanOrEqual($value6 + 4, $value7, 'Timer failed to measure at least 16 milliseconds of sleeping while running.');
|
||||
|
||||
// Stop again.
|
||||
$value8 = Timer::stop('test');
|
||||
$value9 = Timer::read('test');
|
||||
$this->assertEquals($value8['time'], $value9, 'Timer measured after stopping not equal to stop time.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test unicode handling features implemented in Unicode component.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Unicode
|
||||
*/
|
||||
class UnicodeTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::setStatus() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In Drupal 9 there will be no way to set the status and in Drupal 8 this ability has been removed because mb_*() functions are supplied using Symfony's polyfill. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testSetStatus() {
|
||||
Unicode::setStatus(Unicode::STATUS_SINGLEBYTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte encoding and decoding.
|
||||
*
|
||||
* @dataProvider providerTestMimeHeader
|
||||
* @covers ::mimeHeaderEncode
|
||||
* @covers ::mimeHeaderDecode
|
||||
*/
|
||||
public function testMimeHeader($value, $encoded) {
|
||||
$this->assertEquals($encoded, Unicode::mimeHeaderEncode($value));
|
||||
$this->assertEquals($value, Unicode::mimeHeaderDecode($encoded));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testMimeHeader().
|
||||
*
|
||||
* @see testMimeHeader()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its encoded value.
|
||||
*/
|
||||
public function providerTestMimeHeader() {
|
||||
return [
|
||||
['tést.txt', '=?UTF-8?B?dMOpc3QudHh0?='],
|
||||
// Simple ASCII characters.
|
||||
['ASCII', 'ASCII'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte strtolower.
|
||||
*
|
||||
* @dataProvider providerStrtolower
|
||||
* @covers ::strtolower
|
||||
* @covers ::caseFlip
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::strtolower() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtolower() instead. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testStrtolower($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::strtolower($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testStrtolower().
|
||||
*
|
||||
* @see testStrtolower()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its lowercase version.
|
||||
*/
|
||||
public function providerStrtolower() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 'the quick brown'],
|
||||
['FrançAIS is ÜBER-åwesome', 'français is über-åwesome'],
|
||||
['ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', 'αβγδεζηθικλμνξοσὠ'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte strtoupper.
|
||||
*
|
||||
* @dataProvider providerStrtoupper
|
||||
* @covers ::strtoupper
|
||||
* @covers ::caseFlip
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::strtoupper() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtoupper() instead. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testStrtoupper($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::strtoupper($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testStrtoupper().
|
||||
*
|
||||
* @see testStrtoupper()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its uppercase version.
|
||||
*/
|
||||
public function providerStrtoupper() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 'THE QUICK BROWN'],
|
||||
['FrançAIS is ÜBER-åwesome', 'FRANÇAIS IS ÜBER-ÅWESOME'],
|
||||
['αβγδεζηθικλμνξοσὠ', 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte ucfirst.
|
||||
*
|
||||
* @dataProvider providerUcfirst
|
||||
* @covers ::ucfirst
|
||||
*/
|
||||
public function testUcfirst($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::ucfirst($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testUcfirst().
|
||||
*
|
||||
* @see testUcfirst()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its uppercase first version.
|
||||
*/
|
||||
public function providerUcfirst() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 'THe QUIcK bRoWn'],
|
||||
['françAIS', 'FrançAIS'],
|
||||
['über', 'Über'],
|
||||
['åwesome', 'Åwesome'],
|
||||
// A multibyte string.
|
||||
['σion', 'Σion'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte lcfirst.
|
||||
*
|
||||
* @dataProvider providerLcfirst
|
||||
* @covers ::lcfirst
|
||||
*/
|
||||
public function testLcfirst($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::lcfirst($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testLcfirst().
|
||||
*
|
||||
* @see testLcfirst()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its lowercase version.
|
||||
*/
|
||||
public function providerLcfirst() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 'tHe QUIcK bRoWn'],
|
||||
['FrançAIS is ÜBER-åwesome', 'françAIS is ÜBER-åwesome'],
|
||||
['Über', 'über'],
|
||||
['Åwesome', 'åwesome'],
|
||||
// Add a multibyte string.
|
||||
['ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', 'αΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte ucwords.
|
||||
*
|
||||
* @dataProvider providerUcwords
|
||||
* @covers ::ucwords
|
||||
*/
|
||||
public function testUcwords($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::ucwords($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testUcwords().
|
||||
*
|
||||
* @see testUcwords()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its capitalized version.
|
||||
*/
|
||||
public function providerUcwords() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 'THe QUIcK BRoWn'],
|
||||
['françAIS', 'FrançAIS'],
|
||||
['über', 'Über'],
|
||||
['åwesome', 'Åwesome'],
|
||||
// Make sure we don't mangle extra spaces.
|
||||
['frànçAIS is über-åwesome', 'FrànçAIS Is Über-Åwesome'],
|
||||
// Add a multibyte string.
|
||||
['σion', 'Σion'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte strlen.
|
||||
*
|
||||
* @dataProvider providerStrlen
|
||||
* @covers ::strlen
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::strlen() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strlen() instead. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testStrlen($text, $expected) {
|
||||
$this->assertEquals($expected, Unicode::strlen($text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testStrlen().
|
||||
*
|
||||
* @see testStrlen()
|
||||
*
|
||||
* @return array
|
||||
* An array containing a string and its length.
|
||||
*/
|
||||
public function providerStrlen() {
|
||||
return [
|
||||
['tHe QUIcK bRoWn', 15],
|
||||
['ÜBER-åwesome', 12],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', 15],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte substr.
|
||||
*
|
||||
* @dataProvider providerSubstr
|
||||
* @covers ::substr
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::substr() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_substr() instead. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testSubstr($text, $start, $length, $expected) {
|
||||
$this->assertEquals($expected, Unicode::substr($text, $start, $length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testSubstr().
|
||||
*
|
||||
* @see testSubstr()
|
||||
*
|
||||
* @return array
|
||||
* An array containing:
|
||||
* - The string to test.
|
||||
* - The start number to be processed by substr.
|
||||
* - The length number to be processed by substr.
|
||||
* - The expected string result.
|
||||
*/
|
||||
public function providerSubstr() {
|
||||
return [
|
||||
['frànçAIS is über-åwesome', 0, NULL, 'frànçAIS is über-åwesome'],
|
||||
['frànçAIS is über-åwesome', 0, 0, ''],
|
||||
['frànçAIS is über-åwesome', 0, 1, 'f'],
|
||||
['frànçAIS is über-åwesome', 0, 8, 'frànçAIS'],
|
||||
['frànçAIS is über-åwesome', 0, 23, 'frànçAIS is über-åwesom'],
|
||||
['frànçAIS is über-åwesome', 0, 24, 'frànçAIS is über-åwesome'],
|
||||
['frànçAIS is über-åwesome', 0, 25, 'frànçAIS is über-åwesome'],
|
||||
['frànçAIS is über-åwesome', 0, 100, 'frànçAIS is über-åwesome'],
|
||||
['frànçAIS is über-åwesome', 4, 4, 'çAIS'],
|
||||
['frànçAIS is über-åwesome', 1, 0, ''],
|
||||
['frànçAIS is über-åwesome', 100, 0, ''],
|
||||
['frànçAIS is über-åwesome', -4, 2, 'so'],
|
||||
['frànçAIS is über-åwesome', -4, 3, 'som'],
|
||||
['frànçAIS is über-åwesome', -4, 4, 'some'],
|
||||
['frànçAIS is über-åwesome', -4, 5, 'some'],
|
||||
['frànçAIS is über-åwesome', -7, 10, 'åwesome'],
|
||||
['frànçAIS is über-åwesome', 5, -10, 'AIS is üb'],
|
||||
['frànçAIS is über-åwesome', 0, -10, 'frànçAIS is üb'],
|
||||
['frànçAIS is über-åwesome', 0, -1, 'frànçAIS is über-åwesom'],
|
||||
['frànçAIS is über-åwesome', -7, -2, 'åweso'],
|
||||
['frànçAIS is über-åwesome', -7, -6, 'å'],
|
||||
['frànçAIS is über-åwesome', -7, -7, ''],
|
||||
['frànçAIS is über-åwesome', -7, -8, ''],
|
||||
['...', 0, 2, '..'],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', 1, 3, '呂波耳'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte truncate.
|
||||
*
|
||||
* @dataProvider providerTruncate
|
||||
* @covers ::truncate
|
||||
*/
|
||||
public function testTruncate($text, $max_length, $expected, $wordsafe = FALSE, $add_ellipsis = FALSE) {
|
||||
$this->assertEquals($expected, Unicode::truncate($text, $max_length, $wordsafe, $add_ellipsis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testTruncate().
|
||||
*
|
||||
* @see testTruncate()
|
||||
*
|
||||
* @return array
|
||||
* An array containing:
|
||||
* - The string to test.
|
||||
* - The max length to truncate this string to.
|
||||
* - The expected string result.
|
||||
* - (optional) Boolean for the $wordsafe flag. Defaults to FALSE.
|
||||
* - (optional) Boolean for the $add_ellipsis flag. Defaults to FALSE.
|
||||
*/
|
||||
public function providerTruncate() {
|
||||
$tests = [
|
||||
['frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome'],
|
||||
['frànçAIS is über-åwesome', 23, 'frànçAIS is über-åwesom'],
|
||||
['frànçAIS is über-åwesome', 17, 'frànçAIS is über-'],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', 6, '以呂波耳・ほ'],
|
||||
['frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome', FALSE, TRUE],
|
||||
['frànçAIS is über-åwesome', 23, 'frànçAIS is über-åweso…', FALSE, TRUE],
|
||||
['frànçAIS is über-åwesome', 17, 'frànçAIS is über…', FALSE, TRUE],
|
||||
['123', 1, '…', TRUE, TRUE],
|
||||
['123', 2, '1…', TRUE, TRUE],
|
||||
['123', 3, '123', TRUE, TRUE],
|
||||
['1234', 3, '12…', TRUE, TRUE],
|
||||
['1234567890', 10, '1234567890', TRUE, TRUE],
|
||||
['12345678901', 10, '123456789…', TRUE, TRUE],
|
||||
['12345678901', 11, '12345678901', TRUE, TRUE],
|
||||
['123456789012', 11, '1234567890…', TRUE, TRUE],
|
||||
['12345 7890', 10, '12345 7890', TRUE, TRUE],
|
||||
['12345 7890', 9, '12345…', TRUE, TRUE],
|
||||
['123 567 90', 10, '123 567 90', TRUE, TRUE],
|
||||
['123 567 901', 10, '123 567…', TRUE, TRUE],
|
||||
['Stop. Hammertime.', 17, 'Stop. Hammertime.', TRUE, TRUE],
|
||||
['Stop. Hammertime.', 16, 'Stop…', TRUE, TRUE],
|
||||
['frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome', TRUE, TRUE],
|
||||
['frànçAIS is über-åwesome', 23, 'frànçAIS is über…', TRUE, TRUE],
|
||||
['frànçAIS is über-åwesome', 17, 'frànçAIS is über…', TRUE, TRUE],
|
||||
['¿Dónde está el niño?', 20, '¿Dónde está el niño?', TRUE, TRUE],
|
||||
['¿Dónde está el niño?', 19, '¿Dónde está el…', TRUE, TRUE],
|
||||
['¿Dónde está el niño?', 13, '¿Dónde está…', TRUE, TRUE],
|
||||
['¿Dónde está el niño?', 10, '¿Dónde…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 17, 'Help! Help! Help!', TRUE, TRUE],
|
||||
['Help! Help! Help!', 16, 'Help! Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 15, 'Help! Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 14, 'Help! Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 13, 'Help! Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 12, 'Help! Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 11, 'Help! Help…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 10, 'Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 9, 'Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 8, 'Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 7, 'Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 6, 'Help!…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 5, 'Help…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 4, 'Hel…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 3, 'He…', TRUE, TRUE],
|
||||
['Help! Help! Help!', 2, 'H…', TRUE, TRUE],
|
||||
];
|
||||
|
||||
// Test truncate on text with multiple lines.
|
||||
$multi_line = <<<EOF
|
||||
This is a text that spans multiple lines.
|
||||
Line 2 goes here.
|
||||
EOF;
|
||||
$multi_line_wordsafe = <<<EOF
|
||||
This is a text that spans multiple lines.
|
||||
Line 2
|
||||
EOF;
|
||||
$multi_line_non_wordsafe = <<<EOF
|
||||
This is a text that spans multiple lines.
|
||||
Line 2 go
|
||||
EOF;
|
||||
$tests[] = [$multi_line, 51, $multi_line_wordsafe, TRUE];
|
||||
$tests[] = [$multi_line, 51, $multi_line_non_wordsafe, FALSE];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte truncate bytes.
|
||||
*
|
||||
* @dataProvider providerTestTruncateBytes
|
||||
* @covers ::truncateBytes
|
||||
*
|
||||
* @param string $text
|
||||
* The string to truncate.
|
||||
* @param int $max_length
|
||||
* The upper limit on the returned string length.
|
||||
* @param string $expected
|
||||
* The expected return from Unicode::truncateBytes().
|
||||
*/
|
||||
public function testTruncateBytes($text, $max_length, $expected) {
|
||||
$this->assertEquals($expected, Unicode::truncateBytes($text, $max_length), 'The string was not correctly truncated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testTruncateBytes().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters to
|
||||
* self::testTruncateBytes().
|
||||
*/
|
||||
public function providerTestTruncateBytes() {
|
||||
return [
|
||||
// String shorter than max length.
|
||||
['Short string', 42, 'Short string'],
|
||||
// Simple string longer than max length.
|
||||
['Longer string than previous.', 10, 'Longer str'],
|
||||
// Unicode.
|
||||
['以呂波耳・ほへとち。リヌルヲ。', 10, '以呂波'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UTF-8 validation.
|
||||
*
|
||||
* @dataProvider providerTestValidateUtf8
|
||||
* @covers ::validateUtf8
|
||||
*
|
||||
* @param string $text
|
||||
* The text to validate.
|
||||
* @param bool $expected
|
||||
* The expected return value from Unicode::validateUtf8().
|
||||
* @param string $message
|
||||
* The message to display on failure.
|
||||
*/
|
||||
public function testValidateUtf8($text, $expected, $message) {
|
||||
$this->assertEquals($expected, Unicode::validateUtf8($text), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testValidateUtf8().
|
||||
*
|
||||
* Invalid UTF-8 examples sourced from http://stackoverflow.com/a/11709412/109119.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters for
|
||||
* self::testValidateUtf8().
|
||||
*/
|
||||
public function providerTestValidateUtf8() {
|
||||
return [
|
||||
// Empty string.
|
||||
['', TRUE, 'An empty string did not validate.'],
|
||||
// Simple text string.
|
||||
['Simple text.', TRUE, 'A simple ASCII text string did not validate.'],
|
||||
// Invalid UTF-8, overlong 5 byte encoding.
|
||||
[chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80), FALSE, 'Invalid UTF-8 was validated.'],
|
||||
// High code-point without trailing characters.
|
||||
[chr(0xD0) . chr(0x01), FALSE, 'Invalid UTF-8 was validated.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UTF-8 conversion.
|
||||
*
|
||||
* @dataProvider providerTestConvertToUtf8
|
||||
* @covers ::convertToUtf8
|
||||
*
|
||||
* @param string $data
|
||||
* The data to be converted.
|
||||
* @param string $encoding
|
||||
* The encoding the data is in.
|
||||
* @param string|bool $expected
|
||||
* The expected result.
|
||||
*/
|
||||
public function testConvertToUtf8($data, $encoding, $expected) {
|
||||
$this->assertEquals($expected, Unicode::convertToUtf8($data, $encoding));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data to self::testConvertToUtf8().
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing the parameters to
|
||||
* self::testConvertUtf8(). }
|
||||
*/
|
||||
public function providerTestConvertToUtf8() {
|
||||
return [
|
||||
[chr(0x97), 'Windows-1252', '—'],
|
||||
[chr(0x99), 'Windows-1252', '™'],
|
||||
[chr(0x80), 'Windows-1252', '€'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multibyte strpos.
|
||||
*
|
||||
* @dataProvider providerStrpos
|
||||
* @covers ::strpos
|
||||
* @group legacy
|
||||
* @expectedDeprecation \Drupal\Component\Utility\Unicode::strpos() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strpos() instead. See https://www.drupal.org/node/2850048.
|
||||
*/
|
||||
public function testStrpos($haystack, $needle, $offset, $expected) {
|
||||
$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 [
|
||||
['frànçAIS is über-åwesome', 'frànçAIS is über-åwesome', 0, 0],
|
||||
['frànçAIS is über-åwesome', 'rànçAIS is über-åwesome', 0, 1],
|
||||
['frànçAIS is über-åwesome', 'not in string', 0, FALSE],
|
||||
['frànçAIS is über-åwesome', 'r', 0, 1],
|
||||
['frànçAIS is über-åwesome', 'nçAIS', 0, 3],
|
||||
['frànçAIS is über-åwesome', 'nçAIS', 2, 3],
|
||||
['frànçAIS is über-åwesome', 'nçAIS', 3, 3],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', '波耳', 0, 2],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', '波耳', 1, 2],
|
||||
['以呂波耳・ほへとち。リヌルヲ。', '波耳', 2, 2],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,613 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\UrlHelper
|
||||
*/
|
||||
class UrlHelperTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Provides test data for testBuildQuery().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestBuildQuery() {
|
||||
return [
|
||||
[['a' => ' &#//+%20@۞'], 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'],
|
||||
[[' &#//+%20@۞' => 'a'], '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'],
|
||||
[['a' => '1', 'b' => '2', 'c' => '3'], 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'],
|
||||
[['a' => ['b' => '2', 'c' => '3'], 'd' => 'foo'], 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'],
|
||||
[['foo' => NULL], 'foo', 'Simple parameters are properly added.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests query building.
|
||||
*
|
||||
* @dataProvider providerTestBuildQuery
|
||||
* @covers ::buildQuery
|
||||
*
|
||||
* @param array $query
|
||||
* The array of query parameters.
|
||||
* @param string $expected
|
||||
* The expected query string.
|
||||
* @param string $message
|
||||
* The assertion message.
|
||||
*/
|
||||
public function testBuildQuery($query, $expected, $message) {
|
||||
$this->assertEquals(UrlHelper::buildQuery($query), $expected, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testValidAbsolute().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestValidAbsoluteData() {
|
||||
$urls = [
|
||||
'example.com',
|
||||
'www.example.com',
|
||||
'ex-ample.com',
|
||||
'3xampl3.com',
|
||||
'example.com/parenthesis',
|
||||
'example.com/index.html#pagetop',
|
||||
'example.com:8080',
|
||||
'subdomain.example.com',
|
||||
'example.com/index.php/node',
|
||||
'example.com/index.php/node?param=false',
|
||||
'user@www.example.com',
|
||||
'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
|
||||
'127.0.0.1',
|
||||
'example.org?',
|
||||
'john%20doe:secret:foo@example.org/',
|
||||
'example.org/~,$\'*;',
|
||||
'caf%C3%A9.example.org',
|
||||
'[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
|
||||
];
|
||||
|
||||
return $this->dataEnhanceWithScheme($urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests valid absolute URLs.
|
||||
*
|
||||
* @dataProvider providerTestValidAbsoluteData
|
||||
* @covers ::isValid
|
||||
*
|
||||
* @param string $url
|
||||
* The url to test.
|
||||
* @param string $scheme
|
||||
* The scheme to test.
|
||||
*/
|
||||
public function testValidAbsolute($url, $scheme) {
|
||||
$test_url = $scheme . '://' . $url;
|
||||
$valid_url = UrlHelper::isValid($test_url, TRUE);
|
||||
$this->assertTrue($valid_url, $test_url . ' is a valid URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testInvalidAbsolute().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestInvalidAbsolute() {
|
||||
$data = [
|
||||
'',
|
||||
'ex!ample.com',
|
||||
'ex%ample.com',
|
||||
];
|
||||
return $this->dataEnhanceWithScheme($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid absolute URLs.
|
||||
*
|
||||
* @dataProvider providerTestInvalidAbsolute
|
||||
* @covers ::isValid
|
||||
*
|
||||
* @param string $url
|
||||
* The url to test.
|
||||
* @param string $scheme
|
||||
* The scheme to test.
|
||||
*/
|
||||
public function testInvalidAbsolute($url, $scheme) {
|
||||
$test_url = $scheme . '://' . $url;
|
||||
$valid_url = UrlHelper::isValid($test_url, TRUE);
|
||||
$this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testValidRelative().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestValidRelativeData() {
|
||||
$data = [
|
||||
'paren(the)sis',
|
||||
'index.html#pagetop',
|
||||
'index.php/node',
|
||||
'index.php/node?param=false',
|
||||
'login.php?do=login&style=%23#pagetop',
|
||||
];
|
||||
|
||||
return $this->dataEnhanceWithPrefix($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests valid relative URLs.
|
||||
*
|
||||
* @dataProvider providerTestValidRelativeData
|
||||
* @covers ::isValid
|
||||
*
|
||||
* @param string $url
|
||||
* The url to test.
|
||||
* @param string $prefix
|
||||
* The prefix to test.
|
||||
*/
|
||||
public function testValidRelative($url, $prefix) {
|
||||
$test_url = $prefix . $url;
|
||||
$valid_url = UrlHelper::isValid($test_url);
|
||||
$this->assertTrue($valid_url, $test_url . ' is a valid URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testInvalidRelative().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestInvalidRelativeData() {
|
||||
$data = [
|
||||
'ex^mple',
|
||||
'example<>',
|
||||
'ex%ample',
|
||||
];
|
||||
return $this->dataEnhanceWithPrefix($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid relative URLs.
|
||||
*
|
||||
* @dataProvider providerTestInvalidRelativeData
|
||||
* @covers ::isValid
|
||||
*
|
||||
* @param string $url
|
||||
* The url to test.
|
||||
* @param string $prefix
|
||||
* The prefix to test.
|
||||
*/
|
||||
public function testInvalidRelative($url, $prefix) {
|
||||
$test_url = $prefix . $url;
|
||||
$valid_url = UrlHelper::isValid($test_url);
|
||||
$this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests query filtering.
|
||||
*
|
||||
* @dataProvider providerTestFilterQueryParameters
|
||||
* @covers ::filterQueryParameters
|
||||
*
|
||||
* @param array $query
|
||||
* The array of query parameters.
|
||||
* @param array $exclude
|
||||
* A list of $query array keys to remove. Use "parent[child]" to exclude
|
||||
* nested items.
|
||||
* @param array $expected
|
||||
* An array containing query parameters.
|
||||
*/
|
||||
public function testFilterQueryParameters($query, $exclude, $expected) {
|
||||
$filtered = UrlHelper::filterQueryParameters($query, $exclude);
|
||||
$this->assertEquals($expected, $filtered, 'The query was not properly filtered.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data to self::testFilterQueryParameters().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestFilterQueryParameters() {
|
||||
return [
|
||||
// Test without an exclude filter.
|
||||
[
|
||||
'query' => ['a' => ['b' => 'c']],
|
||||
'exclude' => [],
|
||||
'expected' => ['a' => ['b' => 'c']],
|
||||
],
|
||||
// Exclude the 'b' element.
|
||||
[
|
||||
'query' => ['a' => ['b' => 'c', 'd' => 'e']],
|
||||
'exclude' => ['a[b]'],
|
||||
'expected' => ['a' => ['d' => 'e']],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests url parsing.
|
||||
*
|
||||
* @dataProvider providerTestParse
|
||||
* @covers ::parse
|
||||
*
|
||||
* @param string $url
|
||||
* URL to test.
|
||||
* @param array $expected
|
||||
* Associative array with expected parameters.
|
||||
*/
|
||||
public function testParse($url, $expected) {
|
||||
$parsed = UrlHelper::parse($url);
|
||||
$this->assertEquals($expected, $parsed, 'The URL was not properly parsed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testParse().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestParse() {
|
||||
return [
|
||||
[
|
||||
'http://www.example.com/my/path',
|
||||
[
|
||||
'path' => 'http://www.example.com/my/path',
|
||||
'query' => [],
|
||||
'fragment' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
'http://www.example.com/my/path?destination=home#footer',
|
||||
[
|
||||
'path' => 'http://www.example.com/my/path',
|
||||
'query' => [
|
||||
'destination' => 'home',
|
||||
],
|
||||
'fragment' => 'footer',
|
||||
],
|
||||
],
|
||||
'absolute fragment, no query' => [
|
||||
'http://www.example.com/my/path#footer',
|
||||
[
|
||||
'path' => 'http://www.example.com/my/path',
|
||||
'query' => [],
|
||||
'fragment' => 'footer',
|
||||
],
|
||||
],
|
||||
[
|
||||
'http://',
|
||||
[
|
||||
'path' => '',
|
||||
'query' => [],
|
||||
'fragment' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
'https://',
|
||||
[
|
||||
'path' => '',
|
||||
'query' => [],
|
||||
'fragment' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
'/my/path?destination=home#footer',
|
||||
[
|
||||
'path' => '/my/path',
|
||||
'query' => [
|
||||
'destination' => 'home',
|
||||
],
|
||||
'fragment' => 'footer',
|
||||
],
|
||||
],
|
||||
'relative fragment, no query' => [
|
||||
'/my/path#footer',
|
||||
[
|
||||
'path' => '/my/path',
|
||||
'query' => [],
|
||||
'fragment' => 'footer',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests path encoding.
|
||||
*
|
||||
* @dataProvider providerTestEncodePath
|
||||
* @covers ::encodePath
|
||||
*
|
||||
* @param string $path
|
||||
* A path to encode.
|
||||
* @param string $expected
|
||||
* The expected encoded path.
|
||||
*/
|
||||
public function testEncodePath($path, $expected) {
|
||||
$encoded = UrlHelper::encodePath($path);
|
||||
$this->assertEquals($expected, $encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testEncodePath().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestEncodePath() {
|
||||
return [
|
||||
['unencoded path with spaces', 'unencoded%20path%20with%20spaces'],
|
||||
['slashes/should/be/preserved', 'slashes/should/be/preserved'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests external versus internal paths.
|
||||
*
|
||||
* @dataProvider providerTestIsExternal
|
||||
* @covers ::isExternal
|
||||
*
|
||||
* @param string $path
|
||||
* URL or path to test.
|
||||
* @param bool $expected
|
||||
* Expected result.
|
||||
*/
|
||||
public function testIsExternal($path, $expected) {
|
||||
$isExternal = UrlHelper::isExternal($path);
|
||||
$this->assertEquals($expected, $isExternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testIsExternal().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestIsExternal() {
|
||||
return [
|
||||
['/internal/path', FALSE],
|
||||
['https://example.com/external/path', TRUE],
|
||||
['javascript://fake-external-path', FALSE],
|
||||
// External URL without an explicit protocol.
|
||||
['//www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
|
||||
// Internal URL starting with a slash.
|
||||
['/www.drupal.org', FALSE],
|
||||
// Simple external URLs.
|
||||
['http://example.com', TRUE],
|
||||
['https://example.com', TRUE],
|
||||
['http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
|
||||
['//drupal.org', TRUE],
|
||||
// Some browsers ignore or strip leading control characters.
|
||||
["\x00//www.example.com", TRUE],
|
||||
["\x08//www.example.com", TRUE],
|
||||
["\x1F//www.example.com", TRUE],
|
||||
["\n//www.example.com", TRUE],
|
||||
// JSON supports decoding directly from UTF-8 code points.
|
||||
[json_decode('"\u00AD"') . "//www.example.com", TRUE],
|
||||
[json_decode('"\u200E"') . "//www.example.com", TRUE],
|
||||
[json_decode('"\uE0020"') . "//www.example.com", TRUE],
|
||||
[json_decode('"\uE000"') . "//www.example.com", TRUE],
|
||||
// Backslashes should be normalized to forward.
|
||||
['\\\\example.com', TRUE],
|
||||
// Local URLs.
|
||||
['node', FALSE],
|
||||
['/system/ajax', FALSE],
|
||||
['?q=foo:bar', FALSE],
|
||||
['node/edit:me', FALSE],
|
||||
['/drupal.org', FALSE],
|
||||
['<front>', FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests bad protocol filtering and escaping.
|
||||
*
|
||||
* @dataProvider providerTestFilterBadProtocol
|
||||
* @covers ::setAllowedProtocols
|
||||
* @covers ::filterBadProtocol
|
||||
*
|
||||
* @param string $uri
|
||||
* Protocol URI.
|
||||
* @param string $expected
|
||||
* Expected escaped value.
|
||||
* @param array $protocols
|
||||
* Protocols to allow.
|
||||
*/
|
||||
public function testFilterBadProtocol($uri, $expected, $protocols) {
|
||||
UrlHelper::setAllowedProtocols($protocols);
|
||||
$this->assertEquals($expected, UrlHelper::filterBadProtocol($uri));
|
||||
// Multiple calls to UrlHelper::filterBadProtocol() do not cause double
|
||||
// escaping.
|
||||
$this->assertEquals($expected, UrlHelper::filterBadProtocol(UrlHelper::filterBadProtocol($uri)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testTestFilterBadProtocol().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestFilterBadProtocol() {
|
||||
return [
|
||||
['javascript://example.com?foo&bar', '//example.com?foo&bar', ['http', 'https']],
|
||||
// Test custom protocols.
|
||||
['http://example.com?foo&bar', '//example.com?foo&bar', ['https']],
|
||||
// Valid protocol.
|
||||
['http://example.com?foo&bar', 'http://example.com?foo&bar', ['https', 'http']],
|
||||
// Colon not part of the URL scheme.
|
||||
['/test:8888?foo&bar', '/test:8888?foo&bar', ['http']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests dangerous url protocol filtering.
|
||||
*
|
||||
* @dataProvider providerTestStripDangerousProtocols
|
||||
* @covers ::setAllowedProtocols
|
||||
* @covers ::stripDangerousProtocols
|
||||
*
|
||||
* @param string $uri
|
||||
* Protocol URI.
|
||||
* @param string $expected
|
||||
* Expected escaped value.
|
||||
* @param array $protocols
|
||||
* Protocols to allow.
|
||||
*/
|
||||
public function testStripDangerousProtocols($uri, $expected, $protocols) {
|
||||
UrlHelper::setAllowedProtocols($protocols);
|
||||
$stripped = UrlHelper::stripDangerousProtocols($uri);
|
||||
$this->assertEquals($expected, $stripped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for self::testStripDangerousProtocols().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function providerTestStripDangerousProtocols() {
|
||||
return [
|
||||
['javascript://example.com', '//example.com', ['http', 'https']],
|
||||
// Test custom protocols.
|
||||
['http://example.com', '//example.com', ['https']],
|
||||
// Valid protocol.
|
||||
['http://example.com', 'http://example.com', ['https', 'http']],
|
||||
// Colon not part of the URL scheme.
|
||||
['/test:8888', '/test:8888', ['http']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances test urls with schemes
|
||||
*
|
||||
* @param array $urls
|
||||
* The list of urls.
|
||||
*
|
||||
* @return array
|
||||
* A list of provider data with schemes.
|
||||
*/
|
||||
protected function dataEnhanceWithScheme(array $urls) {
|
||||
$url_schemes = ['http', 'https', 'ftp'];
|
||||
$data = [];
|
||||
foreach ($url_schemes as $scheme) {
|
||||
foreach ($urls as $url) {
|
||||
$data[] = [$url, $scheme];
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances test urls with prefixes.
|
||||
*
|
||||
* @param array $urls
|
||||
* The list of urls.
|
||||
*
|
||||
* @return array
|
||||
* A list of provider data with prefixes.
|
||||
*/
|
||||
protected function dataEnhanceWithPrefix(array $urls) {
|
||||
$prefixes = ['', '/'];
|
||||
$data = [];
|
||||
foreach ($prefixes as $prefix) {
|
||||
foreach ($urls as $url) {
|
||||
$data[] = [$url, $prefix];
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test detecting external urls that point to local resources.
|
||||
*
|
||||
* @param string $url
|
||||
* The external url to test.
|
||||
* @param string $base_url
|
||||
* The base url.
|
||||
* @param bool $expected
|
||||
* TRUE if an external URL points to this installation as determined by the
|
||||
* base url.
|
||||
*
|
||||
* @covers ::externalIsLocal
|
||||
* @dataProvider providerTestExternalIsLocal
|
||||
*/
|
||||
public function testExternalIsLocal($url, $base_url, $expected) {
|
||||
$this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for local external url detection.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocal()
|
||||
*/
|
||||
public function providerTestExternalIsLocal() {
|
||||
return [
|
||||
// Different mixes of trailing slash.
|
||||
['http://example.com', 'http://example.com', TRUE],
|
||||
['http://example.com/', 'http://example.com', TRUE],
|
||||
['http://example.com', 'http://example.com/', TRUE],
|
||||
['http://example.com/', 'http://example.com/', TRUE],
|
||||
// Sub directory of site.
|
||||
['http://example.com/foo', 'http://example.com/', TRUE],
|
||||
['http://example.com/foo/bar', 'http://example.com/foo', TRUE],
|
||||
['http://example.com/foo/bar', 'http://example.com/foo/', TRUE],
|
||||
// Different sub-domain.
|
||||
['http://example.com', 'http://www.example.com/', FALSE],
|
||||
['http://example.com/', 'http://www.example.com/', FALSE],
|
||||
['http://example.com/foo', 'http://www.example.com/', FALSE],
|
||||
// Different TLD.
|
||||
['http://example.com', 'http://example.ca', FALSE],
|
||||
['http://example.com', 'http://example.ca/', FALSE],
|
||||
['http://example.com/', 'http://example.ca/', FALSE],
|
||||
['http://example.com/foo', 'http://example.ca', FALSE],
|
||||
['http://example.com/foo', 'http://example.ca/', FALSE],
|
||||
// Different site path.
|
||||
['http://example.com/foo', 'http://example.com/bar', FALSE],
|
||||
['http://example.com', 'http://example.com/bar', FALSE],
|
||||
['http://example.com/bar', 'http://example.com/bar/', FALSE],
|
||||
// Ensure \ is normalised to / since some browsers do that.
|
||||
['http://www.example.ca\@example.com', 'http://example.com', FALSE],
|
||||
// Some browsers ignore or strip leading control characters.
|
||||
["\x00//www.example.ca", 'http://example.com', FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid url arguments.
|
||||
*
|
||||
* @param string $url
|
||||
* The url to test.
|
||||
* @param string $base_url
|
||||
* The base url.
|
||||
*
|
||||
* @covers ::externalIsLocal
|
||||
* @dataProvider providerTestExternalIsLocalInvalid
|
||||
*/
|
||||
public function testExternalIsLocalInvalid($url, $base_url) {
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
}
|
||||
else {
|
||||
$this->setExpectedException(\InvalidArgumentException::class);
|
||||
}
|
||||
UrlHelper::externalIsLocal($url, $base_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides invalid argument data for local external url detection.
|
||||
*
|
||||
* @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocalInvalid()
|
||||
*/
|
||||
public function providerTestExternalIsLocalInvalid() {
|
||||
return [
|
||||
['http://example.com/foo', ''],
|
||||
['http://example.com/foo', 'bar'],
|
||||
['http://example.com/foo', 'http://'],
|
||||
// Invalid destination urls.
|
||||
['', 'http://example.com/foo'],
|
||||
['bar', 'http://example.com/foo'],
|
||||
['/bar', 'http://example.com/foo'],
|
||||
['bar/', 'http://example.com/foo'],
|
||||
['http://', 'http://example.com/foo'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Component\Utility\UserAgent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests bytes size parsing helper methods.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\UserAgent
|
||||
*/
|
||||
class UserAgentTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Helper method to supply language codes to testGetBestMatchingLangcode().
|
||||
*
|
||||
* @return array
|
||||
* Language codes, ordered by priority.
|
||||
*/
|
||||
protected function getLanguages() {
|
||||
return [
|
||||
// In our test case, 'en' has priority over 'en-US'.
|
||||
'en',
|
||||
'en-US',
|
||||
// But 'fr-CA' has priority over 'fr'.
|
||||
'fr-CA',
|
||||
'fr',
|
||||
// 'es-MX' is alone.
|
||||
'es-MX',
|
||||
// 'pt' is alone.
|
||||
'pt',
|
||||
// Language codes with more then one dash are actually valid.
|
||||
// eh-oh-laa-laa is the official language code of the Teletubbies.
|
||||
'eh-oh-laa-laa',
|
||||
// Chinese languages.
|
||||
'zh-hans',
|
||||
'zh-hant',
|
||||
'zh-hant-tw',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to supply language mappings to testGetBestMatchingLangcode().
|
||||
*
|
||||
* @return array
|
||||
* Language mappings.
|
||||
*/
|
||||
protected function getMappings() {
|
||||
return [
|
||||
'no' => 'nb',
|
||||
'pt' => 'pt-pt',
|
||||
'zh' => 'zh-hans',
|
||||
'zh-tw' => 'zh-hant',
|
||||
'zh-hk' => 'zh-hant',
|
||||
'zh-mo' => 'zh-hant',
|
||||
'zh-cht' => 'zh-hant',
|
||||
'zh-cn' => 'zh-hans',
|
||||
'zh-sg' => 'zh-hans',
|
||||
'zh-chs' => 'zh-hans',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test matching language from user agent.
|
||||
*
|
||||
* @dataProvider providerTestGetBestMatchingLangcode
|
||||
* @covers ::getBestMatchingLangcode
|
||||
*/
|
||||
public function testGetBestMatchingLangcode($accept_language, $expected) {
|
||||
$result = UserAgent::getBestMatchingLangcode($accept_language, $this->getLanguages(), $this->getMappings());
|
||||
$this->assertSame($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetBestMatchingLangcode().
|
||||
*
|
||||
* @return array
|
||||
* - An accept-language string.
|
||||
* - Expected best matching language code.
|
||||
*/
|
||||
public function providerTestGetBestMatchingLangcode() {
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
return [
|
||||
// Equal qvalue for each language, choose the site preferred one.
|
||||
['en,en-US,fr-CA,fr,es-MX', 'en'],
|
||||
['en-US,en,fr-CA,fr,es-MX', 'en'],
|
||||
['fr,en', 'en'],
|
||||
['en,fr', 'en'],
|
||||
['en-US,fr', 'en-US'],
|
||||
['fr,en-US', 'en-US'],
|
||||
['fr,fr-CA', 'fr-CA'],
|
||||
['fr-CA,fr', 'fr-CA'],
|
||||
['fr', 'fr-CA'],
|
||||
['fr;q=1', 'fr-CA'],
|
||||
['fr,es-MX', 'fr-CA'],
|
||||
['fr,es', 'fr-CA'],
|
||||
['es,fr', 'fr-CA'],
|
||||
['es-MX,de', 'es-MX'],
|
||||
['de,es-MX', 'es-MX'],
|
||||
|
||||
// Different cases and whitespace.
|
||||
['en', 'en'],
|
||||
['En', 'en'],
|
||||
['EN', 'en'],
|
||||
[' en', 'en'],
|
||||
['en ', 'en'],
|
||||
['en, fr', 'en'],
|
||||
|
||||
// A less specific language from the browser matches a more specific one
|
||||
// from the website, and the other way around for compatibility with
|
||||
// some versions of Internet Explorer.
|
||||
['es', 'es-MX'],
|
||||
['es-MX', 'es-MX'],
|
||||
['pt', 'pt'],
|
||||
['pt-PT', 'pt'],
|
||||
['pt-PT;q=0.5,pt-BR;q=1,en;q=0.7', 'en'],
|
||||
['pt-PT;q=1,pt-BR;q=0.5,en;q=0.7', 'en'],
|
||||
['pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7', 'en'],
|
||||
['pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7', 'en'],
|
||||
|
||||
// Language code with several dashes are valid. The less specific language
|
||||
// from the browser matches the more specific one from the website.
|
||||
['eh-oh-laa-laa', 'eh-oh-laa-laa'],
|
||||
['eh-oh-laa', 'eh-oh-laa-laa'],
|
||||
['eh-oh', 'eh-oh-laa-laa'],
|
||||
['eh', 'eh-oh-laa-laa'],
|
||||
|
||||
// Different qvalues.
|
||||
['fr,en;q=0.5', 'fr-CA'],
|
||||
['fr,en;q=0.5,fr-CA;q=0.25', 'fr'],
|
||||
|
||||
// Silly wildcards are also valid.
|
||||
['*,fr-CA;q=0.5', 'en'],
|
||||
['*,en;q=0.25', 'fr-CA'],
|
||||
['en,en-US;q=0.5,fr;q=0.25', 'en'],
|
||||
['en-US,en;q=0.5,fr;q=0.25', 'en-US'],
|
||||
|
||||
// Unresolvable cases.
|
||||
['', FALSE],
|
||||
['de,pl', FALSE],
|
||||
['iecRswK4eh', FALSE],
|
||||
[$random->name(10, TRUE), FALSE],
|
||||
|
||||
// Chinese langcodes.
|
||||
['zh-cn, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hans'],
|
||||
['zh-tw, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'],
|
||||
['zh-hant, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'],
|
||||
['zh-hans, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hans'],
|
||||
// @todo: This is copied from RFC4647 but our regex skips the numbers so
|
||||
// they where removed. Our code should be updated so private1-private2 is
|
||||
// valid. http://tools.ietf.org/html/rfc4647#section-3.4
|
||||
['zh-hant-CN-x-private-private, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'],
|
||||
['zh-cn', 'zh-hans'],
|
||||
['zh-sg', 'zh-hans'],
|
||||
['zh-tw', 'zh-hant'],
|
||||
['zh-hk', 'zh-hant'],
|
||||
['zh-mo', 'zh-hant'],
|
||||
['zh-hans', 'zh-hans'],
|
||||
['zh-hant', 'zh-hant'],
|
||||
['zh-chs', 'zh-hans'],
|
||||
['zh-cht', 'zh-hant'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Component\Utility\VariableTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Variable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test variable export functionality in Variable component.
|
||||
*
|
||||
* @group Variable
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Variable
|
||||
*/
|
||||
class VariableTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Data provider for testExport().
|
||||
*
|
||||
* @return array
|
||||
* An array containing:
|
||||
* - The expected export string.
|
||||
* - The variable to export.
|
||||
*/
|
||||
public function providerTestExport() {
|
||||
return [
|
||||
// Array.
|
||||
[
|
||||
'array()',
|
||||
[],
|
||||
],
|
||||
[
|
||||
// non-associative.
|
||||
"array(\n 1,\n 2,\n 3,\n 4,\n)",
|
||||
[1, 2, 3, 4],
|
||||
],
|
||||
[
|
||||
// associative.
|
||||
"array(\n 'a' => 1,\n)",
|
||||
['a' => 1],
|
||||
],
|
||||
// Bool.
|
||||
[
|
||||
'TRUE',
|
||||
TRUE,
|
||||
],
|
||||
[
|
||||
'FALSE',
|
||||
FALSE,
|
||||
],
|
||||
// Strings.
|
||||
[
|
||||
"'string'",
|
||||
'string',
|
||||
],
|
||||
[
|
||||
'"\n\r\t"',
|
||||
"\n\r\t",
|
||||
],
|
||||
[
|
||||
// 2 backslashes. \\
|
||||
"'\\'",
|
||||
'\\',
|
||||
],
|
||||
[
|
||||
// Double-quote "
|
||||
"'\"'",
|
||||
"\"",
|
||||
],
|
||||
[
|
||||
// Single-quote '
|
||||
'"\'"',
|
||||
"'",
|
||||
],
|
||||
[
|
||||
// Quotes with $ symbols.
|
||||
'"\$settings[\'foo\']"',
|
||||
'$settings[\'foo\']',
|
||||
],
|
||||
// Object.
|
||||
[
|
||||
// A stdClass object.
|
||||
'(object) array()',
|
||||
new \stdClass(),
|
||||
],
|
||||
[
|
||||
// A not-stdClass object.
|
||||
"Drupal\Tests\Component\Utility\StubVariableTestClass::__set_state(array(\n))",
|
||||
new StubVariableTestClass(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exporting variables.
|
||||
*
|
||||
* @dataProvider providerTestExport
|
||||
* @covers ::export
|
||||
*
|
||||
* @param string $expected
|
||||
* The expected exported variable.
|
||||
* @param mixed $variable
|
||||
* The variable to be exported.
|
||||
*/
|
||||
public function testExport($expected, $variable) {
|
||||
$this->assertEquals($expected, Variable::export($variable));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op test class for VariableTest::testExport().
|
||||
*
|
||||
* @see Drupal\Tests\Component\Utility\VariableTest::testExport()
|
||||
* @see Drupal\Tests\Component\Utility\VariableTest::providerTestExport()
|
||||
*/
|
||||
class StubVariableTestClass {
|
||||
|
||||
}
|
||||
622
2017/web/core/tests/Drupal/Tests/Component/Utility/XssTest.php
Normal file
622
2017/web/core/tests/Drupal/Tests/Component/Utility/XssTest.php
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* XSS Filtering tests.
|
||||
*
|
||||
* @group Utility
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Utility\Xss
|
||||
*
|
||||
* Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
|
||||
*
|
||||
* Relevant CVEs:
|
||||
* - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
|
||||
* CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
|
||||
*/
|
||||
class XssTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$allowed_protocols = [
|
||||
'http',
|
||||
'https',
|
||||
'ftp',
|
||||
'news',
|
||||
'nntp',
|
||||
'telnet',
|
||||
'mailto',
|
||||
'irc',
|
||||
'ssh',
|
||||
'sftp',
|
||||
'webcal',
|
||||
'rtsp',
|
||||
];
|
||||
UrlHelper::setAllowedProtocols($allowed_protocols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests limiting allowed tags and XSS prevention.
|
||||
*
|
||||
* XSS tests assume that script is disallowed by default and src is allowed
|
||||
* by default, but on* and style attributes are disallowed.
|
||||
*
|
||||
* @param string $value
|
||||
* The value to filter.
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $message
|
||||
* The assertion message to display upon failure.
|
||||
* @param array $allowed_tags
|
||||
* (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter().
|
||||
*
|
||||
* @dataProvider providerTestFilterXssNormalized
|
||||
*/
|
||||
public function testFilterXssNormalized($value, $expected, $message, array $allowed_tags = NULL) {
|
||||
if ($allowed_tags === NULL) {
|
||||
$value = Xss::filter($value);
|
||||
}
|
||||
else {
|
||||
$value = Xss::filter($value, $allowed_tags);
|
||||
}
|
||||
$this->assertNormalized($value, $expected, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFilterXssNormalized().
|
||||
*
|
||||
* @see testFilterXssNormalized()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing strings:
|
||||
* - The value to filter.
|
||||
* - The value to expect after filtering.
|
||||
* - The assertion message.
|
||||
* - (optional) The allowed HTML HTML tags array that should be passed to
|
||||
* \Drupal\Component\Utility\Xss::filter().
|
||||
*/
|
||||
public function providerTestFilterXssNormalized() {
|
||||
return [
|
||||
[
|
||||
"Who's Online",
|
||||
"who's online",
|
||||
'HTML filter -- html entity number',
|
||||
],
|
||||
[
|
||||
"Who&#039;s Online",
|
||||
"who's online",
|
||||
'HTML filter -- encoded html entity number',
|
||||
],
|
||||
[
|
||||
"Who&amp;#039; Online",
|
||||
"who&#039; online",
|
||||
'HTML filter -- double encoded html entity number',
|
||||
],
|
||||
// Custom elements with dashes in the tag name.
|
||||
[
|
||||
"<test-element></test-element>",
|
||||
"<test-element></test-element>",
|
||||
'Custom element with dashes in tag name.',
|
||||
['test-element'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests limiting to allowed tags and XSS prevention.
|
||||
*
|
||||
* XSS tests assume that script is disallowed by default and src is allowed
|
||||
* by default, but on* and style attributes are disallowed.
|
||||
*
|
||||
* @param string $value
|
||||
* The value to filter.
|
||||
* @param string $expected
|
||||
* The string that is expected to be missing.
|
||||
* @param string $message
|
||||
* The assertion message to display upon failure.
|
||||
* @param array $allowed_tags
|
||||
* (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter().
|
||||
*
|
||||
* @dataProvider providerTestFilterXssNotNormalized
|
||||
*/
|
||||
public function testFilterXssNotNormalized($value, $expected, $message, array $allowed_tags = NULL) {
|
||||
if ($allowed_tags === NULL) {
|
||||
$value = Xss::filter($value);
|
||||
}
|
||||
else {
|
||||
$value = Xss::filter($value, $allowed_tags);
|
||||
}
|
||||
$this->assertNotNormalized($value, $expected, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFilterXssNotNormalized().
|
||||
*
|
||||
* @see testFilterXssNotNormalized()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing the following elements:
|
||||
* - The value to filter.
|
||||
* - The value to expect that's missing after filtering.
|
||||
* - The assertion message.
|
||||
* - (optional) The allowed HTML HTML tags array that should be passed to
|
||||
* \Drupal\Component\Utility\Xss::filter().
|
||||
*/
|
||||
public function providerTestFilterXssNotNormalized() {
|
||||
$cases = [
|
||||
// Tag stripping, different ways to work around removal of HTML tags.
|
||||
[
|
||||
'<script>alert(0)</script>',
|
||||
'script',
|
||||
'HTML tag stripping -- simple script without special characters.',
|
||||
],
|
||||
[
|
||||
'<script src="http://www.example.com" />',
|
||||
'script',
|
||||
'HTML tag stripping -- empty script with source.',
|
||||
],
|
||||
[
|
||||
'<ScRipt sRc=http://www.example.com/>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- varying case.',
|
||||
],
|
||||
[
|
||||
"<script\nsrc\n=\nhttp://www.example.com/\n>",
|
||||
'script',
|
||||
'HTML tag stripping evasion -- multiline tag.',
|
||||
],
|
||||
[
|
||||
'<script/a src=http://www.example.com/a.js></script>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- non whitespace character after tag name.',
|
||||
],
|
||||
[
|
||||
'<script/src=http://www.example.com/a.js></script>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- no space between tag and attribute.',
|
||||
],
|
||||
// Null between < and tag name works at least with IE6.
|
||||
[
|
||||
"<\0scr\0ipt>alert(0)</script>",
|
||||
'ipt',
|
||||
'HTML tag stripping evasion -- breaking HTML with nulls.',
|
||||
],
|
||||
[
|
||||
"<scrscriptipt src=http://www.example.com/a.js>",
|
||||
'script',
|
||||
'HTML tag stripping evasion -- filter just removing "script".',
|
||||
],
|
||||
[
|
||||
'<<script>alert(0);//<</script>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- double opening brackets.',
|
||||
],
|
||||
[
|
||||
'<script src=http://www.example.com/a.js?<b>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- no closing tag.',
|
||||
],
|
||||
// DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
|
||||
// work consistently.
|
||||
[
|
||||
'<script>>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- double closing tag.',
|
||||
],
|
||||
[
|
||||
'<script src=//www.example.com/.a>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- no scheme or ending slash.',
|
||||
],
|
||||
[
|
||||
'<script src=http://www.example.com/.a',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- no closing bracket.',
|
||||
],
|
||||
[
|
||||
'<script src=http://www.example.com/ <',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- opening instead of closing bracket.',
|
||||
],
|
||||
[
|
||||
'<nosuchtag attribute="newScriptInjectionVector">',
|
||||
'nosuchtag',
|
||||
'HTML tag stripping evasion -- unknown tag.',
|
||||
],
|
||||
[
|
||||
'<t:set attributeName="innerHTML" to="<script defer>alert(0)</script>">',
|
||||
't:set',
|
||||
'HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).',
|
||||
],
|
||||
[
|
||||
'<img """><script>alert(0)</script>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- a malformed image tag.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<blockquote><script>alert(0)</script></blockquote>',
|
||||
'script',
|
||||
'HTML tag stripping evasion -- script in a blockqoute.',
|
||||
['blockquote'],
|
||||
],
|
||||
[
|
||||
"<!--[if true]><script>alert(0)</script><![endif]-->",
|
||||
'script',
|
||||
'HTML tag stripping evasion -- script within a comment.',
|
||||
],
|
||||
// Dangerous attributes removal.
|
||||
[
|
||||
'<p onmouseover="http://www.example.com/">',
|
||||
'onmouseover',
|
||||
'HTML filter attributes removal -- events, no evasion.',
|
||||
['p'],
|
||||
],
|
||||
[
|
||||
'<li style="list-style-image: url(javascript:alert(0))">',
|
||||
'style',
|
||||
'HTML filter attributes removal -- style, no evasion.',
|
||||
['li'],
|
||||
],
|
||||
[
|
||||
'<img onerror =alert(0)>',
|
||||
'onerror',
|
||||
'HTML filter attributes removal evasion -- spaces before equals sign.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>',
|
||||
'onabort',
|
||||
'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img oNmediAError=alert(0)>',
|
||||
'onmediaerror',
|
||||
'HTML filter attributes removal evasion -- varying case.',
|
||||
['img'],
|
||||
],
|
||||
// Works at least with IE6.
|
||||
[
|
||||
"<img o\0nfocus\0=alert(0)>",
|
||||
'focus',
|
||||
'HTML filter attributes removal evasion -- breaking with nulls.',
|
||||
['img'],
|
||||
],
|
||||
// Only whitelisted scheme names allowed in attributes.
|
||||
[
|
||||
'<img src="javascript:alert(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing -- no evasion.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src=javascript:alert(0)>',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- no quotes.',
|
||||
['img'],
|
||||
],
|
||||
// A bit like CVE-2006-0070.
|
||||
[
|
||||
'<img src="javascript:confirm(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- no alert ;)',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src=`javascript:alert(0)`>',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- grave accents.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img dynsrc="javascript:alert(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing -- rare attribute.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<table background="javascript:alert(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing -- another tag.',
|
||||
['table'],
|
||||
],
|
||||
[
|
||||
'<base href="javascript:alert(0);//">',
|
||||
'javascript',
|
||||
'HTML scheme clearing -- one more attribute and tag.',
|
||||
['base'],
|
||||
],
|
||||
[
|
||||
'<img src="jaVaSCriPt:alert(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- varying case.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src=javascript:alert(0)>',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- UTF-8 decimal encoding.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src=javascript:alert(0)>',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- long UTF-8 encoding.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src=javascript:alert(0)>',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- UTF-8 hex encoding.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
"<img src=\"jav\tascript:alert(0)\">",
|
||||
'script',
|
||||
'HTML scheme clearing evasion -- an embedded tab.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src="jav	ascript:alert(0)">',
|
||||
'script',
|
||||
'HTML scheme clearing evasion -- an encoded, embedded tab.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src="jav
ascript:alert(0)">',
|
||||
'script',
|
||||
'HTML scheme clearing evasion -- an encoded, embedded newline.',
|
||||
['img'],
|
||||
],
|
||||
// With 
 this test would fail, but the entity gets turned into
|
||||
// &#xD;, so it's OK.
|
||||
[
|
||||
'<img src="jav
ascript:alert(0)">',
|
||||
'script',
|
||||
'HTML scheme clearing evasion -- an encoded, embedded carriage return.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
"<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">",
|
||||
'cript',
|
||||
'HTML scheme clearing evasion -- broken into many lines.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
"<img src=\"jav\0a\0\0cript:alert(0)\">",
|
||||
'cript',
|
||||
'HTML scheme clearing evasion -- embedded nulls.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src="vbscript:msgbox(0)">',
|
||||
'vbscript',
|
||||
'HTML scheme clearing evasion -- another scheme.',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<img src="nosuchscheme:notice(0)">',
|
||||
'nosuchscheme',
|
||||
'HTML scheme clearing evasion -- unknown scheme.',
|
||||
['img'],
|
||||
],
|
||||
// Netscape 4.x javascript entities.
|
||||
[
|
||||
'<br size="&{alert(0)}">',
|
||||
'alert',
|
||||
'Netscape 4.x javascript entities.',
|
||||
['br'],
|
||||
],
|
||||
// DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with
|
||||
// Internet Explorer 6.
|
||||
[
|
||||
"<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>",
|
||||
'style',
|
||||
'HTML filter -- invalid UTF-8.',
|
||||
['p'],
|
||||
],
|
||||
];
|
||||
// @fixme This dataset currently fails under 5.4 because of
|
||||
// https://www.drupal.org/node/1210798. Restore after its fixed.
|
||||
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
|
||||
$cases[] = [
|
||||
'<img src="  javascript:alert(0)">',
|
||||
'javascript',
|
||||
'HTML scheme clearing evasion -- spaces and metacharacters before scheme.',
|
||||
['img'],
|
||||
];
|
||||
}
|
||||
return $cases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that invalid multi-byte sequences are rejected.
|
||||
*
|
||||
* @param string $value
|
||||
* The value to filter.
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $message
|
||||
* The assertion message to display upon failure.
|
||||
*
|
||||
* @dataProvider providerTestInvalidMultiByte
|
||||
*/
|
||||
public function testInvalidMultiByte($value, $expected, $message) {
|
||||
$this->assertEquals(Xss::filter($value), $expected, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testInvalidMultiByte().
|
||||
*
|
||||
* @see testInvalidMultiByte()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing strings:
|
||||
* - The value to filter.
|
||||
* - The value to expect after filtering.
|
||||
* - The assertion message.
|
||||
*/
|
||||
public function providerTestInvalidMultiByte() {
|
||||
return [
|
||||
["Foo\xC0barbaz", '', 'Xss::filter() accepted invalid sequence "Foo\xC0barbaz"'],
|
||||
["Fooÿñ", "Fooÿñ", 'Xss::filter() rejects valid sequence Fooÿñ"'],
|
||||
["\xc0aaa", '', 'HTML filter -- overlong UTF-8 sequences.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that strings starting with a question sign are correctly processed.
|
||||
*/
|
||||
public function testQuestionSign() {
|
||||
$value = Xss::filter('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
|
||||
$this->assertTrue(stripos($value, '<?xml') === FALSE, 'HTML tag stripping evasion -- starting with a question sign (processing instructions).');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that strings in HTML attributes are correctly processed.
|
||||
*
|
||||
* @covers ::attributes
|
||||
* @dataProvider providerTestAttributes
|
||||
*/
|
||||
public function testAttribute($value, $expected, $message, $allowed_tags = NULL) {
|
||||
$value = Xss::filter($value, $allowed_tags);
|
||||
$this->assertEquals($expected, $value, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFilterXssAdminNotNormalized().
|
||||
*/
|
||||
public function providerTestAttributes() {
|
||||
return [
|
||||
[
|
||||
'<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">',
|
||||
'<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">',
|
||||
'Image tag with alt and title attribute',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>',
|
||||
'<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>',
|
||||
'Link tag with rel attribute',
|
||||
['a'],
|
||||
],
|
||||
[
|
||||
'<span property="dc:subject">Drupal 8: The best release ever.</span>',
|
||||
'<span property="dc:subject">Drupal 8: The best release ever.</span>',
|
||||
'Span tag with property attribute',
|
||||
['span'],
|
||||
],
|
||||
[
|
||||
'<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
|
||||
'<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
|
||||
'Image tag with data attribute',
|
||||
['img'],
|
||||
],
|
||||
[
|
||||
'<a data-a2a-url="foo"></a>',
|
||||
'<a data-a2a-url="foo"></a>',
|
||||
'Link tag with numeric data attribute',
|
||||
['a'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that \Drupal\Component\Utility\Xss::filterAdmin() correctly strips unallowed tags.
|
||||
*/
|
||||
public function testFilterXSSAdmin() {
|
||||
$value = Xss::filterAdmin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />');
|
||||
$this->assertEquals($value, '', 'Admin HTML filter -- should never allow some tags.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the loose, admin HTML filter.
|
||||
*
|
||||
* @param string $value
|
||||
* The value to filter.
|
||||
* @param string $expected
|
||||
* The expected result.
|
||||
* @param string $message
|
||||
* The assertion message to display upon failure.
|
||||
*
|
||||
* @dataProvider providerTestFilterXssAdminNotNormalized
|
||||
*/
|
||||
public function testFilterXssAdminNotNormalized($value, $expected, $message) {
|
||||
$this->assertNotNormalized(Xss::filterAdmin($value), $expected, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testFilterXssAdminNotNormalized().
|
||||
*
|
||||
* @see testFilterXssAdminNotNormalized()
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing strings:
|
||||
* - The value to filter.
|
||||
* - The value to expect after filtering.
|
||||
* - The assertion message.
|
||||
*/
|
||||
public function providerTestFilterXssAdminNotNormalized() {
|
||||
return [
|
||||
// DRUPAL-SA-2008-044
|
||||
['<object />', 'object', 'Admin HTML filter -- should not allow object tag.'],
|
||||
['<script />', 'script', 'Admin HTML filter -- should not allow script tag.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a text transformed to lowercase with HTML entities decoded does contain a given string.
|
||||
*
|
||||
* Otherwise fails the test with a given message, similar to all the
|
||||
* SimpleTest assert* functions.
|
||||
*
|
||||
* Note that this does not remove nulls, new lines and other characters that
|
||||
* could be used to obscure a tag or an attribute name.
|
||||
*
|
||||
* @param string $haystack
|
||||
* Text to look in.
|
||||
* @param string $needle
|
||||
* Lowercase, plain text to look for.
|
||||
* @param string $message
|
||||
* (optional) Message to display if failed. Defaults to an empty string.
|
||||
* @param string $group
|
||||
* (optional) The group this message belongs to. Defaults to 'Other'.
|
||||
*/
|
||||
protected function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
|
||||
$this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) !== FALSE, $message, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string.
|
||||
*
|
||||
* Otherwise fails the test with a given message, similar to all the
|
||||
* SimpleTest assert* functions.
|
||||
*
|
||||
* Note that this does not remove nulls, new lines, and other character that
|
||||
* could be used to obscure a tag or an attribute name.
|
||||
*
|
||||
* @param string $haystack
|
||||
* Text to look in.
|
||||
* @param string $needle
|
||||
* Lowercase, plain text to look for.
|
||||
* @param string $message
|
||||
* (optional) Message to display if failed. Defaults to an empty string.
|
||||
* @param string $group
|
||||
* (optional) The group this message belongs to. Defaults to 'Other'.
|
||||
*/
|
||||
protected function assertNotNormalized($haystack, $needle, $message = '', $group = 'Other') {
|
||||
$this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) === FALSE, $message, $group);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
97
2017/web/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php
Normal file
97
2017/web/core/tests/Drupal/Tests/Component/Uuid/UuidTest.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Uuid;
|
||||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Component\Uuid\Com;
|
||||
use Drupal\Component\Uuid\Pecl;
|
||||
use Drupal\Component\Uuid\Php;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the handling of Universally Unique Identifiers (UUIDs).
|
||||
*
|
||||
* @group Uuid
|
||||
*/
|
||||
class UuidTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests generating valid UUIDs.
|
||||
*
|
||||
* @dataProvider providerUuidInstances
|
||||
*/
|
||||
public function testGenerateUuid(UuidInterface $instance) {
|
||||
$this->assertTrue(Uuid::isValid($instance->generate()), sprintf('UUID generation for %s works.', get_class($instance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that generated UUIDs are unique.
|
||||
*
|
||||
* @dataProvider providerUuidInstances
|
||||
*/
|
||||
public function testUuidIsUnique(UuidInterface $instance) {
|
||||
$this->assertNotEquals($instance->generate(), $instance->generate(), sprintf('Same UUID was not generated twice with %s.', get_class($instance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for UUID instance tests.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerUuidInstances() {
|
||||
|
||||
$instances = [];
|
||||
$instances[][] = new Php();
|
||||
|
||||
// If valid PECL extensions exists add to list.
|
||||
if (function_exists('uuid_create') && !function_exists('uuid_make')) {
|
||||
$instances[][] = new Pecl();
|
||||
}
|
||||
|
||||
// If we are on Windows add the com implementation as well.
|
||||
if (function_exists('com_create_guid')) {
|
||||
$instances[][] = new Com();
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UUID validation.
|
||||
*
|
||||
* @param string $uuid
|
||||
* The uuid to check against.
|
||||
* @param bool $is_valid
|
||||
* Whether the uuid is valid or not.
|
||||
* @param string $message
|
||||
* The message to display on failure.
|
||||
*
|
||||
* @dataProvider providerTestValidation
|
||||
*/
|
||||
public function testValidation($uuid, $is_valid, $message) {
|
||||
$this->assertSame($is_valid, Uuid::isValid($uuid), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for UUID instance tests.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays containing
|
||||
* - The Uuid to check against.
|
||||
* - (bool) Whether or not the Uuid is valid.
|
||||
* - Failure message.
|
||||
*/
|
||||
public function providerTestValidation() {
|
||||
return [
|
||||
// These valid UUIDs.
|
||||
['6ba7b810-9dad-11d1-80b4-00c04fd430c8', TRUE, 'Basic FQDN UUID did not validate'],
|
||||
['00000000-0000-0000-0000-000000000000', TRUE, 'Minimum UUID did not validate'],
|
||||
['ffffffff-ffff-ffff-ffff-ffffffffffff', TRUE, 'Maximum UUID did not validate'],
|
||||
// These are invalid UUIDs.
|
||||
['0ab26e6b-f074-4e44-9da-601205fa0e976', FALSE, 'Invalid format was validated'],
|
||||
['0ab26e6b-f074-4e44-9daf-1205fa0e9761f', FALSE, 'Invalid length was validated'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
259
2017/web/core/tests/Drupal/Tests/ComposerIntegrationTest.php
Normal file
259
2017/web/core/tests/Drupal/Tests/ComposerIntegrationTest.php
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Composer\Semver\Semver;
|
||||
|
||||
/**
|
||||
* Tests Composer integration.
|
||||
*
|
||||
* @group Composer
|
||||
*/
|
||||
class ComposerIntegrationTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The minimum PHP version supported by Drupal.
|
||||
*
|
||||
* @see https://www.drupal.org/docs/8/system-requirements/web-server
|
||||
*
|
||||
* @todo Remove as part of https://www.drupal.org/node/2908079
|
||||
*/
|
||||
const MIN_PHP_VERSION = '5.5.9';
|
||||
|
||||
/**
|
||||
* Gets human-readable JSON error messages.
|
||||
*
|
||||
* @return string[]
|
||||
* Keys are JSON_ERROR_* constants.
|
||||
*/
|
||||
protected function getErrorMessages() {
|
||||
$messages = [
|
||||
0 => 'No errors found',
|
||||
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
|
||||
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
|
||||
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
|
||||
JSON_ERROR_SYNTAX => 'Syntax error',
|
||||
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
|
||||
];
|
||||
|
||||
if (version_compare(phpversion(), '5.5.0', '>=')) {
|
||||
$messages[JSON_ERROR_RECURSION] = 'One or more recursive references in the value to be encoded';
|
||||
$messages[JSON_ERROR_INF_OR_NAN] = 'One or more NAN or INF values in the value to be encoded';
|
||||
$messages[JSON_ERROR_UNSUPPORTED_TYPE] = 'A value of a type that cannot be encoded was given';
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the paths to the folders that contain the Composer integration.
|
||||
*
|
||||
* @return string[]
|
||||
* The paths.
|
||||
*/
|
||||
protected function getPaths() {
|
||||
return [
|
||||
$this->root,
|
||||
$this->root . '/core',
|
||||
$this->root . '/core/lib/Drupal/Component/Annotation',
|
||||
$this->root . '/core/lib/Drupal/Component/Assertion',
|
||||
$this->root . '/core/lib/Drupal/Component/Bridge',
|
||||
$this->root . '/core/lib/Drupal/Component/ClassFinder',
|
||||
$this->root . '/core/lib/Drupal/Component/Datetime',
|
||||
$this->root . '/core/lib/Drupal/Component/DependencyInjection',
|
||||
$this->root . '/core/lib/Drupal/Component/Diff',
|
||||
$this->root . '/core/lib/Drupal/Component/Discovery',
|
||||
$this->root . '/core/lib/Drupal/Component/EventDispatcher',
|
||||
$this->root . '/core/lib/Drupal/Component/FileCache',
|
||||
$this->root . '/core/lib/Drupal/Component/FileSystem',
|
||||
$this->root . '/core/lib/Drupal/Component/Gettext',
|
||||
$this->root . '/core/lib/Drupal/Component/Graph',
|
||||
$this->root . '/core/lib/Drupal/Component/HttpFoundation',
|
||||
$this->root . '/core/lib/Drupal/Component/PhpStorage',
|
||||
$this->root . '/core/lib/Drupal/Component/Plugin',
|
||||
$this->root . '/core/lib/Drupal/Component/ProxyBuilder',
|
||||
$this->root . '/core/lib/Drupal/Component/Render',
|
||||
$this->root . '/core/lib/Drupal/Component/Serialization',
|
||||
$this->root . '/core/lib/Drupal/Component/Transliteration',
|
||||
$this->root . '/core/lib/Drupal/Component/Utility',
|
||||
$this->root . '/core/lib/Drupal/Component/Uuid',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests composer.json.
|
||||
*/
|
||||
public function testComposerJson() {
|
||||
foreach ($this->getPaths() as $path) {
|
||||
$json = file_get_contents($path . '/composer.json');
|
||||
$result = json_decode($json);
|
||||
$this->assertNotNull($result, $this->getErrorMessages()[json_last_error()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests composer.lock content-hash.
|
||||
*/
|
||||
public function testComposerLockHash() {
|
||||
$content_hash = self::getContentHash(file_get_contents($this->root . '/composer.json'));
|
||||
$lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
|
||||
$this->assertSame($content_hash, $lock['content-hash']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests composer.json versions.
|
||||
*
|
||||
* @param string $path
|
||||
* Path to a composer.json to test.
|
||||
*
|
||||
* @dataProvider providerTestComposerJson
|
||||
*/
|
||||
public function testComposerTilde($path) {
|
||||
$content = json_decode(file_get_contents($path), TRUE);
|
||||
$composer_keys = array_intersect(['require', 'require-dev'], array_keys($content));
|
||||
if (empty($composer_keys)) {
|
||||
$this->markTestSkipped("$path has no keys to test");
|
||||
}
|
||||
foreach ($composer_keys as $composer_key) {
|
||||
foreach ($content[$composer_key] as $dependency => $version) {
|
||||
// We allow tildes if the dependency is a Symfony component.
|
||||
// @see https://www.drupal.org/node/2887000
|
||||
if (strpos($dependency, 'symfony/') === 0) {
|
||||
continue;
|
||||
}
|
||||
$this->assertFalse(strpos($version, '~'), "Dependency $dependency in $path contains a tilde, use a caret.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for all the composer.json provided by Drupal core.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestComposerJson() {
|
||||
$root = realpath(__DIR__ . '/../../../../');
|
||||
$tests = [[$root . '/composer.json']];
|
||||
$directory = new \RecursiveDirectoryIterator($root . '/core');
|
||||
$iterator = new \RecursiveIteratorIterator($directory);
|
||||
/** @var \SplFileInfo $file */
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->getFilename() === 'composer.json' && strpos($file->getPath(), 'core/modules/system/tests/fixtures/HtaccessTest') === FALSE) {
|
||||
$tests[] = [$file->getRealPath()];
|
||||
}
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests core's composer.json replace section.
|
||||
*
|
||||
* Verify that all core modules are also listed in the 'replace' section of
|
||||
* core's composer.json.
|
||||
*/
|
||||
public function testAllModulesReplaced() {
|
||||
// Assemble a path to core modules.
|
||||
$module_path = $this->root . '/core/modules';
|
||||
|
||||
// Grab the 'replace' section of the core composer.json file.
|
||||
$json = json_decode(file_get_contents($this->root . '/core/composer.json'));
|
||||
$composer_replace_packages = (array) $json->replace;
|
||||
|
||||
// Get a list of all the files in the module path.
|
||||
$folders = scandir($module_path);
|
||||
|
||||
// Make sure we only deal with directories that aren't . or ..
|
||||
$module_names = [];
|
||||
$discard = ['.', '..'];
|
||||
foreach ($folders as $file_name) {
|
||||
if ((!in_array($file_name, $discard)) && is_dir($module_path . '/' . $file_name)) {
|
||||
$module_names[] = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that each core module has a corresponding 'replace' in
|
||||
// composer.json.
|
||||
foreach ($module_names as $module_name) {
|
||||
$this->assertArrayHasKey(
|
||||
'drupal/' . $module_name,
|
||||
$composer_replace_packages,
|
||||
'Unable to find ' . $module_name . ' in replace list of composer.json'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests package requirements for the minimum supported PHP version by Drupal.
|
||||
*
|
||||
* @todo This can be removed when DrupalCI supports dependency regression
|
||||
* testing in https://www.drupal.org/node/2874198
|
||||
*/
|
||||
public function testMinPHPVersion() {
|
||||
// Check for lockfile in the application root. If the lockfile does not
|
||||
// exist, then skip this test.
|
||||
$lockfile = $this->root . '/composer.lock';
|
||||
if (!file_exists($lockfile)) {
|
||||
$this->markTestSkipped('/composer.lock is not available.');
|
||||
}
|
||||
|
||||
$lock = json_decode(file_get_contents($lockfile), TRUE);
|
||||
|
||||
// Check the PHP version for each installed non-development package. The
|
||||
// testing infrastructure uses the uses the development packages, and may
|
||||
// update them for particular environment configurations. In particular,
|
||||
// PHP 7.2+ require an updated version of phpunit, which is incompatible
|
||||
// with Drupal's minimum PHP requirement.
|
||||
foreach ($lock['packages'] as $package) {
|
||||
if (isset($package['require']['php'])) {
|
||||
$this->assertTrue(Semver::satisfies(static::MIN_PHP_VERSION, $package['require']['php']), $package['name'] . ' has a PHP dependency requirement of "' . $package['require']['php'] . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* The following method is copied from \Composer\Package\Locker.
|
||||
*
|
||||
* @see https://github.com/composer/composer
|
||||
*/
|
||||
/**
|
||||
* Returns the md5 hash of the sorted content of the composer file.
|
||||
*
|
||||
* @param string $composerFileContents The contents of the composer file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getContentHash($composerFileContents)
|
||||
{
|
||||
$content = json_decode($composerFileContents, true);
|
||||
|
||||
$relevantKeys = array(
|
||||
'name',
|
||||
'version',
|
||||
'require',
|
||||
'require-dev',
|
||||
'conflict',
|
||||
'replace',
|
||||
'provide',
|
||||
'minimum-stability',
|
||||
'prefer-stable',
|
||||
'repositories',
|
||||
'extra',
|
||||
);
|
||||
|
||||
$relevantContent = array();
|
||||
|
||||
foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
|
||||
$relevantContent[$key] = $content[$key];
|
||||
}
|
||||
if (isset($content['config']['platform'])) {
|
||||
$relevantContent['config']['platform'] = $content['config']['platform'];
|
||||
}
|
||||
|
||||
ksort($relevantContent);
|
||||
|
||||
return md5(json_encode($relevantContent));
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
}
|
||||
59
2017/web/core/tests/Drupal/Tests/ConfigTestTrait.php
Normal file
59
2017/web/core/tests/Drupal/Tests/ConfigTestTrait.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods to deal with config system objects in tests.
|
||||
*/
|
||||
trait ConfigTestTrait {
|
||||
|
||||
/**
|
||||
* Returns a ConfigImporter object to import test configuration.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The config importer object.
|
||||
*/
|
||||
protected function configImporter() {
|
||||
if (!$this->configImporter) {
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.sync'),
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer,
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$this->container->get('lock'),
|
||||
$this->container->get('config.typed'),
|
||||
$this->container->get('module_handler'),
|
||||
$this->container->get('module_installer'),
|
||||
$this->container->get('theme_handler'),
|
||||
$this->container->get('string_translation')
|
||||
);
|
||||
}
|
||||
// Always recalculate the changelist when called.
|
||||
return $this->configImporter->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies configuration objects from source storage to target storage.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The source config storage service.
|
||||
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The target config storage service.
|
||||
*/
|
||||
protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
$target_storage->deleteAll();
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$target_storage->write($name, $source_storage->read($name));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,573 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\Core\Access\AccessManagerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\Core\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessCheckInterface;
|
||||
use Drupal\Core\Access\AccessException;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\CheckProvider;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Access\AccessManager;
|
||||
use Drupal\Core\Access\DefaultAccessCheck;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\router_test\Access\DefinedTestAccessCheck;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Access\AccessManager
|
||||
* @group Access
|
||||
*/
|
||||
class AccessManagerTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The dependency injection container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* The collection of routes, which are tested.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $routeCollection;
|
||||
|
||||
/**
|
||||
* The access manager to test.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManager
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The parameter converter.
|
||||
*
|
||||
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $paramConverter;
|
||||
|
||||
/**
|
||||
* The mocked account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The access arguments resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $argumentsResolverFactory;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Access\CheckProvider
|
||||
*/
|
||||
protected $checkProvider;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container = new ContainerBuilder();
|
||||
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
|
||||
$this->container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($this->container);
|
||||
|
||||
$this->routeCollection = new RouteCollection();
|
||||
$this->routeCollection->add('test_route_1', new Route('/test-route-1'));
|
||||
$this->routeCollection->add('test_route_2', new Route('/test-route-2', [], ['_access' => 'TRUE']));
|
||||
$this->routeCollection->add('test_route_3', new Route('/test-route-3', [], ['_access' => 'FALSE']));
|
||||
$this->routeCollection->add('test_route_4', new Route('/test-route-4/{value}', [], ['_access' => 'TRUE']));
|
||||
|
||||
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
|
||||
$map = [];
|
||||
foreach ($this->routeCollection->all() as $name => $route) {
|
||||
$map[] = [$name, [], $route];
|
||||
}
|
||||
$map[] = ['test_route_4', ['value' => 'example'], $this->routeCollection->get('test_route_4')];
|
||||
$this->routeProvider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->will($this->returnValueMap($map));
|
||||
|
||||
$map = [];
|
||||
$map[] = ['test_route_1', [], '/test-route-1'];
|
||||
$map[] = ['test_route_2', [], '/test-route-2'];
|
||||
$map[] = ['test_route_3', [], '/test-route-3'];
|
||||
$map[] = ['test_route_4', ['value' => 'example'], '/test-route-4/example'];
|
||||
|
||||
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
|
||||
|
||||
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
|
||||
$this->currentUser = $this->getMock('Drupal\Core\Session\AccountInterface');
|
||||
$this->argumentsResolverFactory = $this->getMock('Drupal\Core\Access\AccessArgumentsResolverFactoryInterface');
|
||||
$this->checkProvider = new CheckProvider();
|
||||
$this->checkProvider->setContainer($this->container);
|
||||
|
||||
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Access\AccessManager::setChecks().
|
||||
*/
|
||||
public function testSetChecks() {
|
||||
// Check setChecks without any access checker defined yet.
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
|
||||
foreach ($this->routeCollection->all() as $route) {
|
||||
$this->assertNull($route->getOption('_access_checks'));
|
||||
}
|
||||
|
||||
$this->setupAccessChecker();
|
||||
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
|
||||
$this->assertEquals($this->routeCollection->get('test_route_1')->getOption('_access_checks'), NULL);
|
||||
$this->assertEquals($this->routeCollection->get('test_route_2')->getOption('_access_checks'), ['test_access_default']);
|
||||
$this->assertEquals($this->routeCollection->get('test_route_3')->getOption('_access_checks'), ['test_access_default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setChecks with a dynamic access checker.
|
||||
*/
|
||||
public function testSetChecksWithDynamicAccessChecker() {
|
||||
// Setup the access manager.
|
||||
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
|
||||
|
||||
// Setup the dynamic access checker.
|
||||
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
|
||||
$this->container->set('test_access', $access_check);
|
||||
$this->checkProvider->addCheckService('test_access', 'access');
|
||||
|
||||
$route = new Route('/test-path', [], ['_foo' => '1', '_bar' => '1']);
|
||||
$route2 = new Route('/test-path', [], ['_foo' => '1', '_bar' => '2']);
|
||||
$collection = new RouteCollection();
|
||||
$collection->add('test_route', $route);
|
||||
$collection->add('test_route2', $route2);
|
||||
|
||||
$access_check->expects($this->exactly(2))
|
||||
->method('applies')
|
||||
->with($this->isInstanceOf('Symfony\Component\Routing\Route'))
|
||||
->will($this->returnCallback(function (Route $route) {
|
||||
return $route->getRequirement('_bar') == 2;
|
||||
}));
|
||||
|
||||
$this->checkProvider->setChecks($collection);
|
||||
$this->assertEmpty($route->getOption('_access_checks'));
|
||||
$this->assertEquals(['test_access'], $route2->getOption('_access_checks'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Access\AccessManager::check().
|
||||
*/
|
||||
public function testCheck() {
|
||||
$route_matches = [];
|
||||
|
||||
// Construct route match objects.
|
||||
foreach ($this->routeCollection->all() as $route_name => $route) {
|
||||
$route_matches[$route_name] = new RouteMatch($route_name, $route, [], []);
|
||||
}
|
||||
|
||||
// Check route access without any access checker defined yet.
|
||||
foreach ($route_matches as $route_match) {
|
||||
$this->assertEquals(FALSE, $this->accessManager->check($route_match, $this->account));
|
||||
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_match, $this->account, NULL, TRUE));
|
||||
}
|
||||
|
||||
$this->setupAccessChecker();
|
||||
|
||||
// An access checker got setup, but the routes haven't been setup using
|
||||
// setChecks.
|
||||
foreach ($route_matches as $route_match) {
|
||||
$this->assertEquals(FALSE, $this->accessManager->check($route_match, $this->account));
|
||||
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_match, $this->account, NULL, TRUE));
|
||||
}
|
||||
|
||||
// Now applicable access checks have been saved on each route object.
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
$this->setupAccessArgumentsResolverFactory();
|
||||
|
||||
$this->assertEquals(FALSE, $this->accessManager->check($route_matches['test_route_1'], $this->account));
|
||||
$this->assertEquals(TRUE, $this->accessManager->check($route_matches['test_route_2'], $this->account));
|
||||
$this->assertEquals(FALSE, $this->accessManager->check($route_matches['test_route_3'], $this->account));
|
||||
$this->assertEquals(TRUE, $this->accessManager->check($route_matches['test_route_4'], $this->account));
|
||||
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_matches['test_route_1'], $this->account, NULL, TRUE));
|
||||
$this->assertEquals(AccessResult::allowed(), $this->accessManager->check($route_matches['test_route_2'], $this->account, NULL, TRUE));
|
||||
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->check($route_matches['test_route_3'], $this->account, NULL, TRUE));
|
||||
$this->assertEquals(AccessResult::allowed(), $this->accessManager->check($route_matches['test_route_4'], $this->account, NULL, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Access\AccessManager::check() with no account specified.
|
||||
*
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testCheckWithNullAccount() {
|
||||
$this->setupAccessChecker();
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
|
||||
$route = $this->routeCollection->get('test_route_2');
|
||||
$route_match = new RouteMatch('test_route_2', $route, [], []);
|
||||
|
||||
// Asserts that the current user is passed to the access arguments resolver
|
||||
// factory.
|
||||
$this->setupAccessArgumentsResolverFactory()
|
||||
->with($route_match, $this->currentUser, NULL);
|
||||
|
||||
$this->assertTrue($this->accessManager->check($route_match));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for the conjunction test.
|
||||
*
|
||||
* @return array
|
||||
* An array of data for check conjunctions.
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Access\AccessManagerTest::testCheckConjunctions()
|
||||
*/
|
||||
public function providerTestCheckConjunctions() {
|
||||
$access_allow = AccessResult::allowed();
|
||||
$access_deny = AccessResult::neutral();
|
||||
$access_kill = AccessResult::forbidden();
|
||||
|
||||
$access_configurations = [];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_4',
|
||||
'condition_one' => 'TRUE',
|
||||
'condition_two' => 'FALSE',
|
||||
'expected' => $access_kill,
|
||||
];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_5',
|
||||
'condition_one' => 'TRUE',
|
||||
'condition_two' => 'NULL',
|
||||
'expected' => $access_deny,
|
||||
];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_6',
|
||||
'condition_one' => 'FALSE',
|
||||
'condition_two' => 'NULL',
|
||||
'expected' => $access_kill,
|
||||
];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_7',
|
||||
'condition_one' => 'TRUE',
|
||||
'condition_two' => 'TRUE',
|
||||
'expected' => $access_allow,
|
||||
];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_8',
|
||||
'condition_one' => 'FALSE',
|
||||
'condition_two' => 'FALSE',
|
||||
'expected' => $access_kill,
|
||||
];
|
||||
$access_configurations[] = [
|
||||
'name' => 'test_route_9',
|
||||
'condition_one' => 'NULL',
|
||||
'condition_two' => 'NULL',
|
||||
'expected' => $access_deny,
|
||||
];
|
||||
|
||||
return $access_configurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test \Drupal\Core\Access\AccessManager::check() with conjunctions.
|
||||
*
|
||||
* @dataProvider providerTestCheckConjunctions
|
||||
*/
|
||||
public function testCheckConjunctions($name, $condition_one, $condition_two, $expected_access) {
|
||||
$this->setupAccessChecker();
|
||||
$access_check = new DefinedTestAccessCheck();
|
||||
$this->container->register('test_access_defined', $access_check);
|
||||
$this->checkProvider->addCheckService('test_access_defined', 'access', ['_test_access']);
|
||||
|
||||
$route_collection = new RouteCollection();
|
||||
// Setup a test route for each access configuration.
|
||||
$requirements = [
|
||||
'_access' => $condition_one,
|
||||
'_test_access' => $condition_two,
|
||||
];
|
||||
$route = new Route($name, [], $requirements);
|
||||
$route_collection->add($name, $route);
|
||||
|
||||
$this->checkProvider->setChecks($route_collection);
|
||||
$this->setupAccessArgumentsResolverFactory();
|
||||
|
||||
$route_match = new RouteMatch($name, $route, [], []);
|
||||
$this->assertEquals($expected_access->isAllowed(), $this->accessManager->check($route_match, $this->account));
|
||||
$this->assertEquals($expected_access, $this->accessManager->check($route_match, $this->account, NULL, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the checkNamedRoute method.
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessManager::checkNamedRoute()
|
||||
*/
|
||||
public function testCheckNamedRoute() {
|
||||
$this->setupAccessChecker();
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
$this->setupAccessArgumentsResolverFactory();
|
||||
|
||||
$this->paramConverter->expects($this->at(0))
|
||||
->method('convert')
|
||||
->with([RouteObjectInterface::ROUTE_NAME => 'test_route_2', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_2')])
|
||||
->will($this->returnValue([]));
|
||||
$this->paramConverter->expects($this->at(1))
|
||||
->method('convert')
|
||||
->with([RouteObjectInterface::ROUTE_NAME => 'test_route_2', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_2')])
|
||||
->will($this->returnValue([]));
|
||||
|
||||
$this->paramConverter->expects($this->at(2))
|
||||
->method('convert')
|
||||
->with(['value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_4', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_4')])
|
||||
->will($this->returnValue(['value' => 'example']));
|
||||
$this->paramConverter->expects($this->at(3))
|
||||
->method('convert')
|
||||
->with(['value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_4', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_4')])
|
||||
->will($this->returnValue(['value' => 'example']));
|
||||
|
||||
// Tests the access with routes with parameters without given request.
|
||||
$this->assertEquals(TRUE, $this->accessManager->checkNamedRoute('test_route_2', [], $this->account));
|
||||
$this->assertEquals(AccessResult::allowed(), $this->accessManager->checkNamedRoute('test_route_2', [], $this->account, TRUE));
|
||||
$this->assertEquals(TRUE, $this->accessManager->checkNamedRoute('test_route_4', ['value' => 'example'], $this->account));
|
||||
$this->assertEquals(AccessResult::allowed(), $this->accessManager->checkNamedRoute('test_route_4', ['value' => 'example'], $this->account, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the checkNamedRoute with upcasted values.
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessManager::checkNamedRoute()
|
||||
*/
|
||||
public function testCheckNamedRouteWithUpcastedValues() {
|
||||
$this->routeCollection = new RouteCollection();
|
||||
$route = new Route('/test-route-1/{value}', [], ['_test_access' => 'TRUE']);
|
||||
$this->routeCollection->add('test_route_1', $route);
|
||||
|
||||
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
|
||||
$this->routeProvider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->with('test_route_1', ['value' => 'example'])
|
||||
->will($this->returnValue($route));
|
||||
|
||||
$map = [];
|
||||
$map[] = ['test_route_1', ['value' => 'example'], '/test-route-1/example'];
|
||||
|
||||
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
|
||||
$this->paramConverter->expects($this->atLeastOnce())
|
||||
->method('convert')
|
||||
->with(['value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_1', RouteObjectInterface::ROUTE_OBJECT => $route])
|
||||
->will($this->returnValue(['value' => 'upcasted_value']));
|
||||
|
||||
$this->setupAccessArgumentsResolverFactory($this->exactly(2))
|
||||
->with($this->callback(function ($route_match) {
|
||||
return $route_match->getParameters()->get('value') == 'upcasted_value';
|
||||
}));
|
||||
|
||||
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
|
||||
|
||||
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
|
||||
$access_check->expects($this->atLeastOnce())
|
||||
->method('applies')
|
||||
->will($this->returnValue(TRUE));
|
||||
$access_check->expects($this->atLeastOnce())
|
||||
->method('access')
|
||||
->will($this->returnValue(AccessResult::forbidden()));
|
||||
|
||||
$this->container->set('test_access', $access_check);
|
||||
|
||||
$this->checkProvider->addCheckService('test_access', 'access');
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
|
||||
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', ['value' => 'example'], $this->account));
|
||||
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->checkNamedRoute('test_route_1', ['value' => 'example'], $this->account, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the checkNamedRoute with default values.
|
||||
*
|
||||
* @covers ::checkNamedRoute
|
||||
*/
|
||||
public function testCheckNamedRouteWithDefaultValue() {
|
||||
$this->routeCollection = new RouteCollection();
|
||||
$route = new Route('/test-route-1/{value}', ['value' => 'example'], ['_test_access' => 'TRUE']);
|
||||
$this->routeCollection->add('test_route_1', $route);
|
||||
|
||||
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
|
||||
$this->routeProvider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->with('test_route_1', [])
|
||||
->will($this->returnValue($route));
|
||||
|
||||
$map = [];
|
||||
$map[] = ['test_route_1', ['value' => 'example'], '/test-route-1/example'];
|
||||
|
||||
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
|
||||
$this->paramConverter->expects($this->atLeastOnce())
|
||||
->method('convert')
|
||||
->with(['value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_1', RouteObjectInterface::ROUTE_OBJECT => $route])
|
||||
->will($this->returnValue(['value' => 'upcasted_value']));
|
||||
|
||||
$this->setupAccessArgumentsResolverFactory($this->exactly(2))
|
||||
->with($this->callback(function ($route_match) {
|
||||
return $route_match->getParameters()->get('value') == 'upcasted_value';
|
||||
}));
|
||||
|
||||
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
|
||||
|
||||
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
|
||||
$access_check->expects($this->atLeastOnce())
|
||||
->method('applies')
|
||||
->will($this->returnValue(TRUE));
|
||||
$access_check->expects($this->atLeastOnce())
|
||||
->method('access')
|
||||
->will($this->returnValue(AccessResult::forbidden()));
|
||||
|
||||
$this->container->set('test_access', $access_check);
|
||||
|
||||
$this->checkProvider->addCheckService('test_access', 'access');
|
||||
$this->checkProvider->setChecks($this->routeCollection);
|
||||
|
||||
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', [], $this->account));
|
||||
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->checkNamedRoute('test_route_1', [], $this->account, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests checkNamedRoute given an invalid/non existing route name.
|
||||
*/
|
||||
public function testCheckNamedRouteWithNonExistingRoute() {
|
||||
$this->routeProvider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->will($this->throwException(new RouteNotFoundException()));
|
||||
|
||||
$this->setupAccessChecker();
|
||||
|
||||
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', [], $this->account), 'A non existing route lead to access.');
|
||||
$this->assertEquals(AccessResult::forbidden()->addCacheTags(['config:core.extension']), $this->accessManager->checkNamedRoute('test_route_1', [], $this->account, TRUE), 'A non existing route lead to access.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an access checker throws an exception for not allowed values.
|
||||
*
|
||||
* @dataProvider providerCheckException
|
||||
*/
|
||||
public function testCheckException($return_value) {
|
||||
$route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
|
||||
|
||||
// Setup a test route for each access configuration.
|
||||
$requirements = [
|
||||
'_test_incorrect_value' => 'TRUE',
|
||||
];
|
||||
$options = [
|
||||
'_access_checks' => [
|
||||
'test_incorrect_value',
|
||||
],
|
||||
];
|
||||
$route = new Route('', [], $requirements, $options);
|
||||
|
||||
$route_provider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->will($this->returnValue($route));
|
||||
|
||||
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
|
||||
$this->paramConverter->expects($this->any())
|
||||
->method('convert')
|
||||
->will($this->returnValue([]));
|
||||
|
||||
$this->setupAccessArgumentsResolverFactory();
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
// Register a service that will return an incorrect value.
|
||||
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
|
||||
$access_check->expects($this->any())
|
||||
->method('access')
|
||||
->will($this->returnValue($return_value));
|
||||
$container->set('test_incorrect_value', $access_check);
|
||||
|
||||
$access_manager = new AccessManager($route_provider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
|
||||
$this->checkProvider->setContainer($container);
|
||||
$this->checkProvider->addCheckService('test_incorrect_value', 'access');
|
||||
|
||||
$this->setExpectedException(AccessException::class);
|
||||
$access_manager->checkNamedRoute('test_incorrect_value', [], $this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testCheckException.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerCheckException() {
|
||||
return [
|
||||
[[1]],
|
||||
['string'],
|
||||
[0],
|
||||
[1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a default access check service to the container and the access manager.
|
||||
*/
|
||||
protected function setupAccessChecker() {
|
||||
$access_check = new DefaultAccessCheck();
|
||||
$this->container->register('test_access_default', $access_check);
|
||||
$this->checkProvider->addCheckService('test_access_default', 'access', ['_access']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default expectations to the access arguments resolver factory.
|
||||
*/
|
||||
protected function setupAccessArgumentsResolverFactory($constraint = NULL) {
|
||||
if (!isset($constraint)) {
|
||||
$constraint = $this->any();
|
||||
}
|
||||
return $this->argumentsResolverFactory->expects($constraint)
|
||||
->method('getArgumentsResolver')
|
||||
->will($this->returnCallback(function ($route_match, $account) {
|
||||
$resolver = $this->getMock('Drupal\Component\Utility\ArgumentsResolverInterface');
|
||||
$resolver->expects($this->any())
|
||||
->method('getArguments')
|
||||
->will($this->returnCallback(function ($callable) use ($route_match) {
|
||||
return [$route_match->getRouteObject()];
|
||||
}));
|
||||
return $resolver;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an interface with a defined access() method for mocking.
|
||||
*/
|
||||
interface TestAccessCheckInterface extends AccessCheckInterface {
|
||||
|
||||
public function access();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\Core\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResultForbidden;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Access\AccessResultForbidden
|
||||
* @group Access
|
||||
*/
|
||||
class AccessResultForbiddenTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests the construction of an AccessResultForbidden object.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getReason
|
||||
*/
|
||||
public function testConstruction() {
|
||||
|
||||
$a = new AccessResultForbidden();
|
||||
$this->assertEquals(NULL, $a->getReason());
|
||||
|
||||
$reason = $this->getRandomGenerator()->string();
|
||||
$b = new AccessResultForbidden($reason);
|
||||
$this->assertEquals($reason, $b->getReason());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setReason()
|
||||
*
|
||||
* @covers ::setReason
|
||||
*/
|
||||
public function testSetReason() {
|
||||
$a = new AccessResultForbidden();
|
||||
|
||||
$reason = $this->getRandomGenerator()->string();
|
||||
$return = $a->setReason($reason);
|
||||
|
||||
$this->assertSame($reason, $a->getReason());
|
||||
$this->assertSame($a, $return);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue