first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* The Base URI to use for links to the output files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htmlOutputBaseUrl;
|
||||
|
||||
/**
|
||||
* 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 = $this->htmlOutputBaseUrl . '/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);
|
||||
$this->htmlOutputBaseUrl = getenv('BROWSERTEST_OUTPUT_BASE_URL') ?: $GLOBALS['base_url'];
|
||||
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. Checks !is_dir() twice around mkdir() because a
|
||||
// concurrent test might have made the directory and caused mkdir() to
|
||||
// fail. In this case we can still use the directory even though we failed
|
||||
// to make it.
|
||||
if (!is_dir($this->htmlOutputDirectory) && !@mkdir($this->htmlOutputDirectory, 0775, TRUE) && !is_dir($this->htmlOutputDirectory)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create directory: %s', $this->htmlOutputDirectory));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,762 @@
|
||||
<?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 Drupal\TestTools\Comparator\MarkupInterfaceComparator;
|
||||
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';
|
||||
|
||||
/**
|
||||
* The theme to install as the default for testing.
|
||||
*
|
||||
* Defaults to the install profile's default theme, if it specifies any.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultTheme;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// Get the response body as a string. Any errors are silenced as
|
||||
// tests should not fail if there is a problem. On PHP 7.4
|
||||
// \Drupal\Tests\migrate\Functional\process\DownloadFunctionalTest
|
||||
// fails without the usage of a silence operator.
|
||||
$body = @(string) $response->getBody();
|
||||
// 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.
|
||||
$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() {
|
||||
parent::setUp();
|
||||
|
||||
// Allow tests to compare MarkupInterface objects via assertEquals().
|
||||
$this->registerComparator(new MarkupInterfaceComparator());
|
||||
|
||||
$this->setupBaseUrl();
|
||||
|
||||
// Install Drupal test site.
|
||||
$this->prepareEnvironment();
|
||||
$this->installDrupal();
|
||||
|
||||
// Setup Mink.
|
||||
$this->initMink();
|
||||
|
||||
// Set up the browser test output file.
|
||||
$this->initBrowserOutputFile();
|
||||
|
||||
// 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
|
||||
*/
|
||||
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.
|
||||
\Drupal::service('file_system')->deleteRecursive($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->installDefaultThemeFromClassProperty($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 in drupal:8.8.0 and is removed from drupal:9.0.0.
|
||||
* Use $this->getSession()->getResponseHeaders() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3067207
|
||||
*/
|
||||
protected function drupalGetHeaders() {
|
||||
@trigger_error('Drupal\Tests\BrowserTestBase::drupalGetHeaders() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use $this->getSession()->getResponseHeaders() instead. See https://www.drupal.org/node/3067207', E_USER_DEPRECATED);
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 we match PHPUnit's TestCase::runTest, then the previously processed
|
||||
// caller entry is where our test method sits.
|
||||
if (isset($last_caller) && isset($caller['function']) && $caller['function'] === 'PHPUnit\Framework\TestCase->runTest()') {
|
||||
// Return the last caller since that has to be the test class.
|
||||
$caller = $last_caller;
|
||||
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;
|
||||
}
|
||||
|
||||
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?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
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
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,101 @@
|
||||
<?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
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
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,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Doctrine\DocParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Doctrine\DocParser
|
||||
*
|
||||
* @group Annotation
|
||||
*/
|
||||
class DocParserIgnoredClassesTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Ensure annotations can be ignored when namespaces are present.
|
||||
*
|
||||
* Drupal's DocParser should never use class_exists() on an ignored
|
||||
* annotation, including cases where namespaces are set.
|
||||
*/
|
||||
public function testIgnoredAnnotationSkippedBeforeReflection() {
|
||||
$annotation = 'neverReflectThis';
|
||||
$parser = new DocParser();
|
||||
$parser->setIgnoredAnnotationNames([$annotation => TRUE]);
|
||||
$parser->addNamespace('\\Arbitrary\\Namespace');
|
||||
|
||||
// Register our class loader which will fail if the parser tries to
|
||||
// autoload disallowed annotations.
|
||||
$autoloader = function ($class_name) use ($annotation) {
|
||||
$name_array = explode('\\', $class_name);
|
||||
$name = array_pop($name_array);
|
||||
if ($name == $annotation) {
|
||||
$this->fail('Attempted to autoload an ignored annotation: ' . $name);
|
||||
}
|
||||
};
|
||||
spl_autoload_register($autoloader, TRUE, TRUE);
|
||||
// Perform the parse.
|
||||
$this->assertEmpty($parser->parse('@neverReflectThis'));
|
||||
// Clean up after ourselves.
|
||||
spl_autoload_unregister($autoloader);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class AnnotWithDefaultValue
|
||||
{
|
||||
/** @var string */
|
||||
public $foo = 'bar';
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class Autoload
|
||||
{
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class Route
|
||||
{
|
||||
/** @var string @Required */
|
||||
public $pattern;
|
||||
public $name;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class Secure
|
||||
{
|
||||
private $roles;
|
||||
|
||||
public function __construct(array $values)
|
||||
{
|
||||
if (is_string($values['value'])) {
|
||||
$values['value'] = array($values['value']);
|
||||
}
|
||||
|
||||
$this->roles = $values['value'];
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class Template
|
||||
{
|
||||
private $name;
|
||||
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$this->name = isset($values['value']) ? $values['value'] : null;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class Version
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationEnum
|
||||
{
|
||||
const ONE = 'ONE';
|
||||
const TWO = 'TWO';
|
||||
const THREE = 'THREE';
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*
|
||||
* @Enum({"ONE","TWO","THREE"})
|
||||
*/
|
||||
public $value;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationEnumInvalid
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*
|
||||
* @Enum({1, 2, "foo", "bar", {"foo":"bar"}})
|
||||
*/
|
||||
public $value;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationEnumLiteral as SelfEnum;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationEnumLiteral
|
||||
{
|
||||
const ONE = 1;
|
||||
const TWO = 2;
|
||||
const THREE = 3;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*
|
||||
* @Enum(
|
||||
* value = {
|
||||
* 1,
|
||||
* 2,
|
||||
* 3,
|
||||
* },
|
||||
* literal = {
|
||||
* 1 : "AnnotationEnumLiteral::ONE",
|
||||
* 2 : "AnnotationEnumLiteral::TWO",
|
||||
* 3 : "AnnotationEnumLiteral::THREE",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $value;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationEnumLiteralInvalid
|
||||
{
|
||||
const ONE = 1;
|
||||
const TWO = 2;
|
||||
const THREE = 3;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*
|
||||
* @Enum(
|
||||
* value = {
|
||||
* 1,
|
||||
* 2
|
||||
* },
|
||||
* literal = {
|
||||
* 1 : "AnnotationEnumLiteral::ONE",
|
||||
* 2 : "AnnotationEnumLiteral::TWO",
|
||||
* 3 : "AnnotationEnumLiteral::THREE"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $value;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
class AnnotationTargetAll
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({ "ANNOTATION" })
|
||||
*/
|
||||
final class AnnotationTargetAnnotation
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class AnnotationTargetClass
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({ "METHOD", "PROPERTY" })
|
||||
*/
|
||||
final class AnnotationTargetPropertyMethod
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @Attributes({
|
||||
@Attribute("mixed", type = "mixed"),
|
||||
@Attribute("boolean", type = "boolean"),
|
||||
@Attribute("bool", type = "bool"),
|
||||
@Attribute("float", type = "float"),
|
||||
@Attribute("string", type = "string"),
|
||||
@Attribute("integer", type = "integer"),
|
||||
@Attribute("array", type = "array"),
|
||||
@Attribute("arrayOfIntegers", type = "array<integer>"),
|
||||
@Attribute("arrayOfStrings", type = "string[]"),
|
||||
@Attribute("annotation", type = "Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll"),
|
||||
@Attribute("arrayOfAnnotations", type = "array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>"),
|
||||
})
|
||||
*/
|
||||
final class AnnotationWithAttributes
|
||||
{
|
||||
|
||||
public final function __construct(array $data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
private $mixed;
|
||||
private $boolean;
|
||||
private $bool;
|
||||
private $float;
|
||||
private $string;
|
||||
private $integer;
|
||||
private $array;
|
||||
private $annotation;
|
||||
private $arrayOfIntegers;
|
||||
private $arrayOfStrings;
|
||||
private $arrayOfAnnotations;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMixed()
|
||||
{
|
||||
return $this->mixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getBoolean()
|
||||
{
|
||||
return $this->boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getBool()
|
||||
{
|
||||
return $this->bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getFloat()
|
||||
{
|
||||
return $this->float;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getString()
|
||||
{
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
public function getInteger()
|
||||
{
|
||||
return $this->integer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getArray()
|
||||
{
|
||||
return $this->array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll
|
||||
*/
|
||||
public function getAnnotation()
|
||||
{
|
||||
return $this->annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getArrayOfStrings()
|
||||
{
|
||||
return $this->arrayOfIntegers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<integer>
|
||||
*/
|
||||
public function getArrayOfIntegers()
|
||||
{
|
||||
return $this->arrayOfIntegers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>
|
||||
*/
|
||||
public function getArrayOfAnnotations()
|
||||
{
|
||||
return $this->arrayOfAnnotations;
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationWithConstants
|
||||
{
|
||||
|
||||
const INTEGER = 1;
|
||||
const FLOAT = 1.2;
|
||||
const STRING = '1.2.3';
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $value;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @Attributes({
|
||||
@Attribute("value", required = true , type = "string"),
|
||||
@Attribute("annot", required = true , type = "Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation"),
|
||||
})
|
||||
*/
|
||||
final class AnnotationWithRequiredAttributes
|
||||
{
|
||||
|
||||
public final function __construct(array $data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
|
||||
*/
|
||||
private $annot;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
|
||||
*/
|
||||
public function getAnnot()
|
||||
{
|
||||
return $this->annot;
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationWithRequiredAttributesWithoutContructor
|
||||
{
|
||||
|
||||
/**
|
||||
* @Required
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @Required
|
||||
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
|
||||
*/
|
||||
public $annot;
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target(@)
|
||||
*/
|
||||
final class AnnotationWithTargetSyntaxError
|
||||
{
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
final class AnnotationWithVarType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $mixed;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $boolean;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bool;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
public $float;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $string;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $integer;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $array;
|
||||
|
||||
/**
|
||||
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll
|
||||
*/
|
||||
public $annotation;
|
||||
|
||||
/**
|
||||
* @var array<integer>
|
||||
*/
|
||||
public $arrayOfIntegers;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $arrayOfStrings;
|
||||
|
||||
/**
|
||||
* @var array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>
|
||||
*/
|
||||
public $arrayOfAnnotations;
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
class ClassWithConstants
|
||||
{
|
||||
const SOME_VALUE = 'ClassWithConstants.SOME_VALUE';
|
||||
const SOME_KEY = 'ClassWithConstants.SOME_KEY';
|
||||
const OTHER_KEY_ = 'ClassWithConstants.OTHER_KEY_';
|
||||
const OTHER_KEY_2 = 'ClassWithConstants.OTHER_KEY_2';
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetPropertyMethod;
|
||||
|
||||
/**
|
||||
* @AnnotationTargetPropertyMethod("Some data")
|
||||
*/
|
||||
class ClassWithInvalidAnnotationTargetAtClass
|
||||
{
|
||||
|
||||
/**
|
||||
* @AnnotationTargetPropertyMethod("Bar")
|
||||
*/
|
||||
public $foo;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
|
||||
|
||||
/**
|
||||
* @AnnotationTargetClass("Some data")
|
||||
*/
|
||||
class ClassWithInvalidAnnotationTargetAtMethod
|
||||
{
|
||||
|
||||
/**
|
||||
* @AnnotationTargetClass("functionName")
|
||||
*/
|
||||
public function functionName($param)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation;
|
||||
|
||||
/**
|
||||
* @AnnotationTargetClass("Some data")
|
||||
*/
|
||||
class ClassWithInvalidAnnotationTargetAtProperty
|
||||
{
|
||||
|
||||
/**
|
||||
* @AnnotationTargetClass("Bar")
|
||||
*/
|
||||
public $foo;
|
||||
|
||||
|
||||
/**
|
||||
* @AnnotationTargetAnnotation("Foo")
|
||||
*/
|
||||
public $bar;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll;
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetPropertyMethod;
|
||||
|
||||
/**
|
||||
* @AnnotationTargetClass("Some data")
|
||||
*/
|
||||
class ClassWithValidAnnotationTarget
|
||||
{
|
||||
|
||||
/**
|
||||
* @AnnotationTargetPropertyMethod("Some data")
|
||||
*/
|
||||
public $foo;
|
||||
|
||||
|
||||
/**
|
||||
* @AnnotationTargetAll("Some data",name="Some name")
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @AnnotationTargetPropertyMethod("Some data",name="Some name")
|
||||
*/
|
||||
public function someFunction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @AnnotationTargetAll(@AnnotationTargetAnnotation)
|
||||
*/
|
||||
public $nested;
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
interface IntefaceWithConstants
|
||||
{
|
||||
|
||||
const SOME_VALUE = 'IntefaceWithConstants.SOME_VALUE';
|
||||
const SOME_KEY = 'IntefaceWithConstants.SOME_KEY';
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
// Some class named Entity in the global namespace
|
||||
/**
|
||||
* This class is a near-copy of
|
||||
* tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php, which is
|
||||
* part of the Doctrine project: <http://www.doctrine-project.org>. It was
|
||||
* copied from version 1.2.7.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Entity
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket;
|
||||
|
||||
use Drupal\Component\Annotation\Doctrine\DocParser;
|
||||
use Drupal\Component\Annotation\Doctrine\SimpleAnnotationReader;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* This class is a near-copy of
|
||||
* \Doctrine\Tests\Common\Annotations\Ticket\DCOM58Test, which is part of the
|
||||
* Doctrine project: <http://www.doctrine-project.org>. It was copied from
|
||||
* version 1.2.7.
|
||||
*
|
||||
* @group DCOM58
|
||||
*
|
||||
* Run this test in a separate process as it includes code that might have side
|
||||
* effects.
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class DCOM58Test extends TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
// Some class named Entity in the global namespace.
|
||||
include __DIR__ .'/DCOM58Entity.php';
|
||||
}
|
||||
|
||||
public function testIssueGlobalNamespace()
|
||||
{
|
||||
$docblock = "@Entity";
|
||||
$parser = new DocParser();
|
||||
$parser->setImports(array(
|
||||
"__NAMESPACE__" =>"Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping"
|
||||
));
|
||||
|
||||
$annots = $parser->parse($docblock);
|
||||
|
||||
$this->assertCount(1, $annots);
|
||||
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
|
||||
}
|
||||
|
||||
public function testIssueNamespaces()
|
||||
{
|
||||
$docblock = "@Entity";
|
||||
$parser = new DocParser();
|
||||
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM");
|
||||
|
||||
$annots = $parser->parse($docblock);
|
||||
|
||||
$this->assertCount(1, $annots);
|
||||
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Entity", $annots[0]);
|
||||
}
|
||||
|
||||
public function testIssueMultipleNamespaces()
|
||||
{
|
||||
$docblock = "@Entity";
|
||||
$parser = new DocParser();
|
||||
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping");
|
||||
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM");
|
||||
|
||||
$annots = $parser->parse($docblock);
|
||||
|
||||
$this->assertCount(1, $annots);
|
||||
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
|
||||
}
|
||||
|
||||
public function testIssueWithNamespacesOrImports()
|
||||
{
|
||||
$docblock = "@Entity";
|
||||
$parser = new DocParser();
|
||||
$annots = $parser->parse($docblock);
|
||||
|
||||
$this->assertCount(1, $annots);
|
||||
$this->assertInstanceOf("Entity", $annots[0]);
|
||||
$this->assertCount(1, $annots);
|
||||
}
|
||||
|
||||
|
||||
public function testIssueSimpleAnnotationReader()
|
||||
{
|
||||
$reader = new SimpleAnnotationReader();
|
||||
$reader->addNamespace('Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping');
|
||||
$annots = $reader->getClassAnnotations(new \ReflectionClass(__NAMESPACE__."\MappedClass"));
|
||||
|
||||
$this->assertCount(1, $annots);
|
||||
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class MappedClass
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping;
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class Entity
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM;
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class Entity
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
All files within this directory where copied from the Doctrine project: <http://www.doctrine-project.org>
|
||||
They were copied from version 1.2.7.
|
||||
|
||||
Original copyright:
|
||||
|
||||
Copyright (c) 2006-2013 Doctrine Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
+16
@@ -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 {}
|
||||
+2
@@ -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'));
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -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,156 @@
|
||||
<?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 Laminas\Feed\Reader\Extension\Atom\Entry;
|
||||
use Laminas\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() {
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Drupal\Tests\Component\Bridge\ZfExtensionManagerSfContainerTest must implement Laminas\Feed\Reader\ExtensionManagerInterface or Laminas\Feed\Writer\ExtensionManagerInterface');
|
||||
$bridge = new ZfExtensionManagerSfContainer();
|
||||
$bridge->setStandalone(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGetContainerException() {
|
||||
$this->expectException(ServiceNotFoundException::class);
|
||||
$this->expectExceptionMessage('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,894 @@
|
||||
<?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) {
|
||||
$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');
|
||||
$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) {
|
||||
$this->expectException($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) {
|
||||
$this->expectException($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
|
||||
// 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
|
||||
// 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.
|
||||
$this->expectException(\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() {
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$this->expectExceptionMessage('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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getRequestTime
|
||||
*/
|
||||
public function testGetRequestTimeNoRequest() {
|
||||
$expected = 12345678;
|
||||
unset($_SERVER['REQUEST_TIME']);
|
||||
$this->assertEquals($expected, $this->time->getRequestTime());
|
||||
$_SERVER['REQUEST_TIME'] = 23456789;
|
||||
$this->assertEquals(23456789, $this->time->getRequestTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getRequestMicroTime
|
||||
*/
|
||||
public function testGetRequestMicroTimeNoRequest() {
|
||||
$expected = 1234567.89;
|
||||
unset($_SERVER['REQUEST_TIME_FLOAT']);
|
||||
$this->assertEquals($expected, $this->time->getRequestMicroTime());
|
||||
$_SERVER['REQUEST_TIME_FLOAT'] = 2345678.90;
|
||||
$this->assertEquals(2345678.90, $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
+716
@@ -0,0 +1,716 @@
|
||||
<?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);
|
||||
$this->expectException(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);
|
||||
$this->expectException(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);
|
||||
$this->expectException(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);
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->dumper->getArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that service arguments with escaped percents are correctly dumped.
|
||||
*
|
||||
* @dataProvider percentsEscapeProvider
|
||||
*/
|
||||
public function testPercentsEscape($expected, $argument) {
|
||||
$this->containerBuilder->getDefinitions()->willReturn([
|
||||
'test' => new Definition('\stdClass', [$argument]),
|
||||
]);
|
||||
|
||||
$dump = $this->dumper->getArray();
|
||||
|
||||
$this->assertEquals($this->serializeDefinition([
|
||||
'class' => '\stdClass',
|
||||
'arguments' => $this->getCollection([
|
||||
$this->getRaw($expected),
|
||||
]),
|
||||
'arguments_count' => 1,
|
||||
]), $dump['services']['test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPercentsEscape().
|
||||
*
|
||||
* @return array[]
|
||||
* Returns data-set elements with:
|
||||
* - expected final value.
|
||||
* - escaped value in service definition.
|
||||
*/
|
||||
public function percentsEscapeProvider() {
|
||||
return [
|
||||
['%foo%', '%%foo%%'],
|
||||
['foo%bar%', 'foo%%bar%%'],
|
||||
['%foo%bar', '%%foo%%bar'],
|
||||
['%', '%'],
|
||||
['%', '%%'],
|
||||
['%%', '%%%'],
|
||||
['%%', '%%%%'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a raw value definition.
|
||||
*/
|
||||
protected function getRaw($value) {
|
||||
return (object) [
|
||||
'type' => 'raw',
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -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,33 @@
|
||||
<?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() {
|
||||
$this->expectException(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,163 @@
|
||||
<?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() {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('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() {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('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,91 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Discovery;
|
||||
|
||||
use Drupal\Component\Discovery\YamlDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use org\bovigo\vfs\vfsStreamDirectory;
|
||||
use org\bovigo\vfs\vfsStreamWrapper;
|
||||
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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if filename is output for a broken YAML file.
|
||||
*/
|
||||
public function testForBrokenYml() {
|
||||
vfsStreamWrapper::register();
|
||||
$root = new vfsStreamDirectory('modules');
|
||||
vfsStreamWrapper::setRoot($root);
|
||||
$url = vfsStream::url('modules');
|
||||
|
||||
mkdir($url . '/test_broken');
|
||||
file_put_contents($url . '/test_broken/test_broken.test.yml', "broken:\n:");
|
||||
|
||||
$this->expectException(InvalidDataTypeException::class);
|
||||
$this->expectExceptionMessage('vfs://modules/test_broken/test_broken.test.yml');
|
||||
|
||||
$directories = ['test_broken' => $url . '/test_broken'];
|
||||
$discovery = new YamlDiscovery('test', $directories);
|
||||
$discovery->findAll();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component;
|
||||
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
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 (AssertionFailedError $e) {
|
||||
$pass = FALSE;
|
||||
}
|
||||
$this->assertEquals($expected_pass, $pass, $expected_pass ?
|
||||
'Test caused a false positive' :
|
||||
'Test failed to detect Core usage');
|
||||
}
|
||||
|
||||
}
|
||||
+602
@@ -0,0 +1,602 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\EventDispatcher;
|
||||
|
||||
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Unit tests for the ContainerAwareEventDispatcher.
|
||||
*
|
||||
* NOTE: Most of this code is a literal copy of Symfony 3.4's
|
||||
* Symfony\Component\EventDispatcher\Tests\AbstractEventDispatcherTest.
|
||||
*
|
||||
* This file does NOT follow Drupal coding standards, so as to simplify future
|
||||
* synchronizations.
|
||||
*
|
||||
* @group EventDispatcher
|
||||
*/
|
||||
class ContainerAwareEventDispatcherTest extends TestCase {
|
||||
|
||||
/* Some pseudo events */
|
||||
const PREFOO = 'pre.foo';
|
||||
const POSTFOO = 'post.foo';
|
||||
const PREBAR = 'pre.bar';
|
||||
const POSTBAR = 'post.bar';
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcher
|
||||
*/
|
||||
private $dispatcher;
|
||||
private $listener;
|
||||
|
||||
protected function setUp() {
|
||||
$this->dispatcher = $this->createEventDispatcher();
|
||||
$this->listener = new TestEventListener();
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$this->dispatcher = NULL;
|
||||
$this->listener = NULL;
|
||||
}
|
||||
|
||||
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 = [new TestEventListener(), 'preFoo'];
|
||||
$listeners = [
|
||||
'test_event' => [
|
||||
0 => [
|
||||
['callable' => $firstListener],
|
||||
['callable' => $secondListener],
|
||||
['callable' => $thirdListener],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$actualListeners = $dispatcher->getListeners();
|
||||
|
||||
$expectedListeners = [
|
||||
'test_event' => [
|
||||
$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 = [new TestEventListener(), 'preFoo'];
|
||||
$listeners = [
|
||||
'test_event' => [
|
||||
0 => [
|
||||
['callable' => $firstListener],
|
||||
['callable' => $secondListener],
|
||||
['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', TestEventListener::class);
|
||||
|
||||
$listeners = [
|
||||
'test_event' => [
|
||||
0 => [
|
||||
['service' => ['listener_service', 'preFoo']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$actualListeners = $dispatcher->getListeners();
|
||||
|
||||
$listenerService = $container->get('listener_service');
|
||||
$expectedListeners = [
|
||||
'test_event' => [
|
||||
[$listenerService, 'preFoo'],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame($expectedListeners, $actualListeners);
|
||||
}
|
||||
|
||||
public function testDispatchWithServices() {
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('listener_service', TestEventListener::class);
|
||||
|
||||
$listeners = [
|
||||
'test_event' => [
|
||||
0 => [
|
||||
['service' => ['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', TestEventListener::class);
|
||||
$container->register('other_listener_service', TestEventListener::class);
|
||||
|
||||
$listeners = [
|
||||
'test_event' => [
|
||||
0 => [
|
||||
['service' => ['listener_service', 'preFoo']],
|
||||
['service' => ['other_listener_service', 'preFoo']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
|
||||
$listenerService = $container->get('listener_service');
|
||||
$dispatcher->removeListener('test_event', [$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 = [
|
||||
'test_event' => [
|
||||
5 => [
|
||||
['service' => ['listener_service', 'preFoo']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
|
||||
$listenerService = $container->get('listener_service');
|
||||
$actualPriority = $dispatcher->getListenerPriority('test_event', [$listenerService, 'preFoo']);
|
||||
|
||||
$this->assertSame(5, $actualPriority);
|
||||
}
|
||||
|
||||
public function testInitialState() {
|
||||
$this->assertEquals([], $this->dispatcher->getListeners());
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::POSTFOO));
|
||||
}
|
||||
|
||||
public function testAddListener() {
|
||||
$this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
|
||||
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
|
||||
$this->assertTrue($this->dispatcher->hasListeners());
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
|
||||
$this->assertCount(1, $this->dispatcher->getListeners(self::PREFOO));
|
||||
$this->assertCount(1, $this->dispatcher->getListeners(self::POSTFOO));
|
||||
$this->assertCount(2, $this->dispatcher->getListeners());
|
||||
}
|
||||
|
||||
public function testGetListenersSortsByPriority() {
|
||||
$listener1 = new TestEventListener();
|
||||
$listener2 = new TestEventListener();
|
||||
$listener3 = new TestEventListener();
|
||||
$listener1->name = '1';
|
||||
$listener2->name = '2';
|
||||
$listener3->name = '3';
|
||||
|
||||
$this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10);
|
||||
$this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10);
|
||||
$this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']);
|
||||
|
||||
$expected = [
|
||||
[$listener2, 'preFoo'],
|
||||
[$listener3, 'preFoo'],
|
||||
[$listener1, 'preFoo'],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
|
||||
}
|
||||
|
||||
public function testGetAllListenersSortsByPriority() {
|
||||
$listener1 = new TestEventListener();
|
||||
$listener2 = new TestEventListener();
|
||||
$listener3 = new TestEventListener();
|
||||
$listener4 = new TestEventListener();
|
||||
$listener5 = new TestEventListener();
|
||||
$listener6 = new TestEventListener();
|
||||
|
||||
$this->dispatcher->addListener('pre.foo', $listener1, -10);
|
||||
$this->dispatcher->addListener('pre.foo', $listener2);
|
||||
$this->dispatcher->addListener('pre.foo', $listener3, 10);
|
||||
$this->dispatcher->addListener('post.foo', $listener4, -10);
|
||||
$this->dispatcher->addListener('post.foo', $listener5);
|
||||
$this->dispatcher->addListener('post.foo', $listener6, 10);
|
||||
|
||||
$expected = [
|
||||
'pre.foo' => [$listener3, $listener2, $listener1],
|
||||
'post.foo' => [$listener6, $listener5, $listener4],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $this->dispatcher->getListeners());
|
||||
}
|
||||
|
||||
public function testGetListenerPriority() {
|
||||
$listener1 = new TestEventListener();
|
||||
$listener2 = new TestEventListener();
|
||||
|
||||
$this->dispatcher->addListener('pre.foo', $listener1, -10);
|
||||
$this->dispatcher->addListener('pre.foo', $listener2);
|
||||
|
||||
$this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
|
||||
$this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
|
||||
$this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
|
||||
$this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {
|
||||
}));
|
||||
}
|
||||
|
||||
public function testDispatch() {
|
||||
$this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
|
||||
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
|
||||
$this->dispatcher->dispatch(self::PREFOO);
|
||||
$this->assertTrue($this->listener->preFooInvoked);
|
||||
$this->assertFalse($this->listener->postFooInvoked);
|
||||
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch('noevent'));
|
||||
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(self::PREFOO));
|
||||
$event = new Event();
|
||||
$return = $this->dispatcher->dispatch(self::PREFOO, $event);
|
||||
$this->assertSame($event, $return);
|
||||
}
|
||||
|
||||
public function testDispatchForClosure() {
|
||||
$invoked = 0;
|
||||
$listener = function () use (&$invoked) {
|
||||
++$invoked;
|
||||
};
|
||||
$this->dispatcher->addListener('pre.foo', $listener);
|
||||
$this->dispatcher->addListener('post.foo', $listener);
|
||||
$this->dispatcher->dispatch(self::PREFOO);
|
||||
$this->assertEquals(1, $invoked);
|
||||
}
|
||||
|
||||
public function testStopEventPropagation() {
|
||||
$otherListener = new TestEventListener();
|
||||
|
||||
// postFoo() stops the propagation, so only one listener should
|
||||
// be executed
|
||||
// Manually set priority to enforce $this->listener to be called first
|
||||
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo'], 10);
|
||||
$this->dispatcher->addListener('post.foo', [$otherListener, 'postFoo']);
|
||||
$this->dispatcher->dispatch(self::POSTFOO);
|
||||
$this->assertTrue($this->listener->postFooInvoked);
|
||||
$this->assertFalse($otherListener->postFooInvoked);
|
||||
}
|
||||
|
||||
public function testDispatchByPriority() {
|
||||
$invoked = [];
|
||||
$listener1 = function () use (&$invoked) {
|
||||
$invoked[] = '1';
|
||||
};
|
||||
$listener2 = function () use (&$invoked) {
|
||||
$invoked[] = '2';
|
||||
};
|
||||
$listener3 = function () use (&$invoked) {
|
||||
$invoked[] = '3';
|
||||
};
|
||||
$this->dispatcher->addListener('pre.foo', $listener1, -10);
|
||||
$this->dispatcher->addListener('pre.foo', $listener2);
|
||||
$this->dispatcher->addListener('pre.foo', $listener3, 10);
|
||||
$this->dispatcher->dispatch(self::PREFOO);
|
||||
$this->assertEquals(['3', '2', '1'], $invoked);
|
||||
}
|
||||
|
||||
public function testRemoveListener() {
|
||||
$this->dispatcher->addListener('pre.bar', $this->listener);
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREBAR));
|
||||
$this->dispatcher->removeListener('pre.bar', $this->listener);
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::PREBAR));
|
||||
$this->dispatcher->removeListener('notExists', $this->listener);
|
||||
}
|
||||
|
||||
public function testAddSubscriber() {
|
||||
$eventSubscriber = new TestEventSubscriber();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
|
||||
}
|
||||
|
||||
public function testAddSubscriberWithPriorities() {
|
||||
$eventSubscriber = new TestEventSubscriber();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
|
||||
$eventSubscriber = new TestEventSubscriberWithPriorities();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
|
||||
$listeners = $this->dispatcher->getListeners('pre.foo');
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertCount(2, $listeners);
|
||||
$this->assertInstanceOf(TestEventSubscriberWithPriorities::class, $listeners[0][0]);
|
||||
}
|
||||
|
||||
public function testAddSubscriberWithMultipleListeners() {
|
||||
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
|
||||
$listeners = $this->dispatcher->getListeners('pre.foo');
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertCount(2, $listeners);
|
||||
$this->assertEquals('preFoo2', $listeners[0][1]);
|
||||
}
|
||||
|
||||
public function testRemoveSubscriber() {
|
||||
$eventSubscriber = new TestEventSubscriber();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
|
||||
$this->dispatcher->removeSubscriber($eventSubscriber);
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::POSTFOO));
|
||||
}
|
||||
|
||||
public function testRemoveSubscriberWithPriorities() {
|
||||
$eventSubscriber = new TestEventSubscriberWithPriorities();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->dispatcher->removeSubscriber($eventSubscriber);
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
|
||||
}
|
||||
|
||||
public function testRemoveSubscriberWithMultipleListeners() {
|
||||
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
|
||||
$this->dispatcher->addSubscriber($eventSubscriber);
|
||||
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
|
||||
$this->assertCount(2, $this->dispatcher->getListeners(self::PREFOO));
|
||||
$this->dispatcher->removeSubscriber($eventSubscriber);
|
||||
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
|
||||
}
|
||||
|
||||
public function testEventReceivesTheDispatcherInstanceAsArgument() {
|
||||
$listener = new TestWithDispatcher();
|
||||
$this->dispatcher->addListener('test', [$listener, 'foo']);
|
||||
$this->assertNull($listener->name);
|
||||
$this->assertNull($listener->dispatcher);
|
||||
$this->dispatcher->dispatch('test');
|
||||
$this->assertEquals('test', $listener->name);
|
||||
$this->assertSame($this->dispatcher, $listener->dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://bugs.php.net/bug.php?id=62976
|
||||
*
|
||||
* This bug affects:
|
||||
* - The PHP 5.3 branch for versions < 5.3.18
|
||||
* - The PHP 5.4 branch for versions < 5.4.8
|
||||
* - The PHP 5.5 branch is not affected
|
||||
*/
|
||||
public function testWorkaroundForPhpBug62976() {
|
||||
$dispatcher = $this->createEventDispatcher();
|
||||
$dispatcher->addListener('bug.62976', new CallableClass());
|
||||
$dispatcher->removeListener('bug.62976', function () {
|
||||
|
||||
});
|
||||
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
|
||||
}
|
||||
|
||||
public function testHasListenersWhenAddedCallbackListenerIsRemoved() {
|
||||
$listener = function () {
|
||||
|
||||
};
|
||||
$this->dispatcher->addListener('foo', $listener);
|
||||
$this->dispatcher->removeListener('foo', $listener);
|
||||
$this->assertFalse($this->dispatcher->hasListeners());
|
||||
}
|
||||
|
||||
public function testGetListenersWhenAddedCallbackListenerIsRemoved() {
|
||||
$listener = function () {
|
||||
|
||||
};
|
||||
$this->dispatcher->addListener('foo', $listener);
|
||||
$this->dispatcher->removeListener('foo', $listener);
|
||||
$this->assertSame([], $this->dispatcher->getListeners());
|
||||
}
|
||||
|
||||
public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() {
|
||||
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||
$this->assertFalse($this->dispatcher->hasListeners());
|
||||
}
|
||||
|
||||
public function testHasListenersIsLazy() {
|
||||
$called = 0;
|
||||
$listener = [
|
||||
function () use (&$called) {
|
||||
++$called;
|
||||
},
|
||||
'onFoo',
|
||||
];
|
||||
$this->dispatcher->addListener('foo', $listener);
|
||||
$this->assertTrue($this->dispatcher->hasListeners());
|
||||
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||
$this->assertSame(0, $called);
|
||||
}
|
||||
|
||||
public function testDispatchLazyListener() {
|
||||
$called = 0;
|
||||
$factory = function () use (&$called) {
|
||||
++$called;
|
||||
|
||||
return new TestWithDispatcher();
|
||||
};
|
||||
$this->dispatcher->addListener('foo', [$factory, 'foo']);
|
||||
$this->assertSame(0, $called);
|
||||
$this->dispatcher->dispatch('foo', new Event());
|
||||
$this->dispatcher->dispatch('foo', new Event());
|
||||
$this->assertSame(1, $called);
|
||||
}
|
||||
|
||||
public function testRemoveFindsLazyListeners() {
|
||||
$test = new TestWithDispatcher();
|
||||
$factory = function () use ($test) {
|
||||
return $test;
|
||||
};
|
||||
|
||||
$this->dispatcher->addListener('foo', [$factory, 'foo']);
|
||||
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||
$this->dispatcher->removeListener('foo', [$test, 'foo']);
|
||||
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||
|
||||
$this->dispatcher->addListener('foo', [$test, 'foo']);
|
||||
$this->assertTrue($this->dispatcher->hasListeners('foo'));
|
||||
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
|
||||
$this->assertFalse($this->dispatcher->hasListeners('foo'));
|
||||
}
|
||||
|
||||
public function testPriorityFindsLazyListeners() {
|
||||
$test = new TestWithDispatcher();
|
||||
$factory = function () use ($test) {
|
||||
return $test;
|
||||
};
|
||||
|
||||
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
|
||||
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo']));
|
||||
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
|
||||
|
||||
$this->dispatcher->addListener('foo', [$test, 'foo'], 5);
|
||||
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo']));
|
||||
}
|
||||
|
||||
public function testGetLazyListeners() {
|
||||
$test = new TestWithDispatcher();
|
||||
$factory = function () use ($test) {
|
||||
return $test;
|
||||
};
|
||||
|
||||
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
|
||||
$this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo'));
|
||||
|
||||
$this->dispatcher->removeListener('foo', [$test, 'foo']);
|
||||
$this->dispatcher->addListener('bar', [$factory, 'foo'], 3);
|
||||
$this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CallableClass {
|
||||
|
||||
public function __invoke() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestEventListener {
|
||||
|
||||
public $preFooInvoked = FALSE;
|
||||
public $postFooInvoked = FALSE;
|
||||
|
||||
/**
|
||||
* Listener methods
|
||||
*/
|
||||
public function preFoo(Event $e) {
|
||||
$this->preFooInvoked = TRUE;
|
||||
}
|
||||
|
||||
public function postFoo(Event $e) {
|
||||
$this->postFooInvoked = TRUE;
|
||||
|
||||
$e->stopPropagation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestWithDispatcher {
|
||||
|
||||
public $name;
|
||||
public $dispatcher;
|
||||
|
||||
public function foo(Event $e, $name, $dispatcher) {
|
||||
$this->name = $name;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
public static function getSubscribedEvents() {
|
||||
return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestEventSubscriberWithPriorities implements EventSubscriberInterface {
|
||||
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
'pre.foo' => ['preFoo', 10],
|
||||
'post.foo' => ['postFoo'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface {
|
||||
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
'pre.foo' => [
|
||||
['preFoo1'],
|
||||
['preFoo2', 10],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?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);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('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,96 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\FileSecurity;
|
||||
|
||||
use Drupal\Component\FileSecurity\FileSecurity;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests the file security component.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\FileSecurity\FileSecurity
|
||||
* @group FileSecurity
|
||||
*/
|
||||
class FileSecurityTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessPrivate() {
|
||||
vfsStream::setup('root');
|
||||
FileSecurity::writeHtaccess(vfsStream::url('root'));
|
||||
$htaccess_file = vfsStream::url('root') . '/.htaccess';
|
||||
$this->assertFileExists($htaccess_file);
|
||||
$this->assertEquals('0444', substr(sprintf('%o', fileperms($htaccess_file)), -4));
|
||||
$htaccess_contents = file_get_contents($htaccess_file);
|
||||
$this->assertContains("Require all denied", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessPublic() {
|
||||
vfsStream::setup('root');
|
||||
$this->assertTrue(FileSecurity::writeHtaccess(vfsStream::url('root'), FALSE));
|
||||
$htaccess_file = vfsStream::url('root') . '/.htaccess';
|
||||
$this->assertFileExists($htaccess_file);
|
||||
$this->assertEquals('0444', substr(sprintf('%o', fileperms($htaccess_file)), -4));
|
||||
$htaccess_contents = file_get_contents($htaccess_file);
|
||||
$this->assertNotContains("Require all denied", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessForceOverwrite() {
|
||||
vfsStream::setup('root');
|
||||
$htaccess_file = vfsStream::url('root') . '/.htaccess';
|
||||
file_put_contents($htaccess_file, "foo");
|
||||
$this->assertTrue(FileSecurity::writeHtaccess(vfsStream::url('root'), TRUE, TRUE));
|
||||
$htaccess_contents = file_get_contents($htaccess_file);
|
||||
$this->assertContains("Require all denied", $htaccess_contents);
|
||||
$this->assertNotContains("foo", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessFailure() {
|
||||
vfsStream::setup('root');
|
||||
$this->assertFalse(FileSecurity::writeHtaccess(vfsStream::url('root') . '/foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeWebConfig
|
||||
*/
|
||||
public function testWriteWebConfig() {
|
||||
vfsStream::setup('root');
|
||||
$this->assertTrue(FileSecurity::writeWebConfig(vfsStream::url('root')));
|
||||
$web_config_file = vfsStream::url('root') . '/web.config';
|
||||
$this->assertFileExists($web_config_file);
|
||||
$this->assertEquals('0444', substr(sprintf('%o', fileperms($web_config_file)), -4));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeWebConfig
|
||||
*/
|
||||
public function testWriteWebConfigForceOverwrite() {
|
||||
vfsStream::setup('root');
|
||||
$web_config_file = vfsStream::url('root') . '/web.config';
|
||||
file_put_contents($web_config_file, "foo");
|
||||
$this->assertTrue(FileSecurity::writeWebConfig(vfsStream::url('root'), TRUE));
|
||||
$this->assertFileExists($web_config_file);
|
||||
$this->assertEquals('0444', substr(sprintf('%o', fileperms($web_config_file)), -4));
|
||||
$this->assertNotContains("foo", $web_config_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeWebConfig
|
||||
*/
|
||||
public function testWriteWebConfigFailure() {
|
||||
vfsStream::setup('root');
|
||||
$this->assertFalse(FileSecurity::writeWebConfig(vfsStream::url('root') . '/foo'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,103 @@
|
||||
<?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() {
|
||||
$this->expectException(\Exception::class, 'No URI set.');
|
||||
|
||||
$this->poWriter->getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeItem
|
||||
* @dataProvider providerWriteData
|
||||
*/
|
||||
public function testWriteItem($poContent, $expected, $long) {
|
||||
if ($long) {
|
||||
$this->expectException(\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() {
|
||||
$this->expectException(\Exception::class, 'Cannot close stream that is not open.');
|
||||
|
||||
$this->poWriter->close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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->assertCount(1, 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', 0, '/', NULL, FALSE, TRUE, FALSE, NULL));
|
||||
|
||||
// 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,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests FileStorage deprecations.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage
|
||||
* @group legacy
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class FileStorageDeprecationTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @expectedDeprecation htaccessLines() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Component\FileSecurity\FileSecurity::htaccessLines() instead. See https://www.drupal.org/node/3075098
|
||||
*/
|
||||
public function testHtAccessLines() {
|
||||
$this->assertNotEmpty(FileStorage::htaccessLines());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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->assertDirectoryExists($this->directory . '/test');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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->assertDirectoryExists($this->directory . '/test');
|
||||
|
||||
$this->assertTrue($php->deleteAll(), 'Delete all reported success');
|
||||
$this->assertFalse($php->load($name));
|
||||
$this->assertDirectoryNotExists($this->directory . '/test');
|
||||
|
||||
// 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';";
|
||||
$this->expectException(Warning::class);
|
||||
$this->expectExceptionMessage('mkdir(): Permission Denied');
|
||||
$storage->save('subdirectory/foo.php', $code);
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -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,134 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\FileSecurity\FileSecurity;
|
||||
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.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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->assertFileExists($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'), FileSecurity::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,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests ConfigurablePluginInterface deprecation.
|
||||
*
|
||||
* @group legacy
|
||||
* @group plugin
|
||||
*/
|
||||
class ConfigurablePluginInterfaceTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests the deprecation error is thrown.
|
||||
*
|
||||
* @expectedDeprecation Drupal\Component\Plugin\ConfigurablePluginInterface is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. You should implement ConfigurableInterface and/or DependentPluginInterface directly as needed. If you implement ConfigurableInterface you may choose to implement ConfigurablePluginInterface in Drupal 8 as well for maximum compatibility, however this must be removed prior to Drupal 9. See https://www.drupal.org/node/2946161
|
||||
*/
|
||||
public function testDeprecation() {
|
||||
new ConfigurablePluginInterfaceTestClass([], '', []);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Class to trigger deprecation error.
|
||||
*/
|
||||
class ConfigurablePluginInterfaceTestClass extends PluginBase implements ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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) {
|
||||
$this->expectException('Drupal\Component\Plugin\Exception\ContextException');
|
||||
$this->expectExceptionMessage(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,151 @@
|
||||
<?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() {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('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();
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('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() {
|
||||
$this->expectException(PluginException::class);
|
||||
$this->expectExceptionMessage('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);
|
||||
$this->expectException(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() {
|
||||
$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.');
|
||||
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);
|
||||
$this->expectException(PluginException::class);
|
||||
DefaultFactory::getPluginClass('corn', $plugin_definition, VegetableInterface::class);
|
||||
}
|
||||
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use org\bovigo\vfs\vfsStreamDirectory;
|
||||
use org\bovigo\vfs\vfsStreamWrapper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
|
||||
*
|
||||
* @group Annotation
|
||||
* @group Plugin
|
||||
*/
|
||||
class AnnotatedClassDiscoveryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* All the Drupal documentation standards tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public function provideBadAnnotations() {
|
||||
return [
|
||||
['addtogroup'],
|
||||
['code'],
|
||||
['defgroup'],
|
||||
['deprecated'],
|
||||
['endcode'],
|
||||
['endlink'],
|
||||
['file'],
|
||||
['ingroup'],
|
||||
['group'],
|
||||
['link'],
|
||||
['mainpage'],
|
||||
['param'],
|
||||
['ref'],
|
||||
['return'],
|
||||
['section'],
|
||||
['see'],
|
||||
['subsection'],
|
||||
['throws'],
|
||||
['todo'],
|
||||
['var'],
|
||||
['{'],
|
||||
['}'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure AnnotatedClassDiscovery never tries to autoload bad annotations.
|
||||
*
|
||||
* @dataProvider provideBadAnnotations
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
public function testAutoloadBadAnnotations($annotation) {
|
||||
// Set up a class file in vfsStream.
|
||||
vfsStreamWrapper::register();
|
||||
$root = new vfsStreamDirectory('root');
|
||||
vfsStreamWrapper::setRoot($root);
|
||||
|
||||
FileCacheFactory::setPrefix(__CLASS__);
|
||||
|
||||
// Make a directory for discovery.
|
||||
$url = vfsStream::url('root');
|
||||
mkdir($url . '/DrupalTest');
|
||||
|
||||
// Create a class docblock with our annotation.
|
||||
$php_file = "<?php\nnamespace DrupalTest;\n/**\n";
|
||||
$php_file .= " * @$annotation\n";
|
||||
$php_file .= " */\nclass TestClass {}";
|
||||
file_put_contents($url . '/DrupalTest/TestClass.php', $php_file);
|
||||
|
||||
// Create an AnnotatedClassDiscovery object referencing the virtual file.
|
||||
$discovery = new AnnotatedClassDiscovery(
|
||||
['\\DrupalTest\\TestClass' => [vfsStream::url('root/DrupalTest')]], '\\DrupalTest\\Component\\Annotation\\'
|
||||
);
|
||||
|
||||
// Register our class loader which will fail if the annotation reader tries
|
||||
// to autoload disallowed annotations.
|
||||
$class_loader = function ($class_name) use ($annotation) {
|
||||
$name_array = explode('\\', $class_name);
|
||||
$name = array_pop($name_array);
|
||||
if ($name == $annotation) {
|
||||
$this->fail('Attempted to autoload a non-plugin annotation: ' . $name);
|
||||
}
|
||||
};
|
||||
spl_autoload_register($class_loader, TRUE, TRUE);
|
||||
// Now try to get plugin definitions.
|
||||
$definitions = $discovery->getDefinitions();
|
||||
// Unregister to clean up.
|
||||
spl_autoload_unregister($class_loader);
|
||||
// Assert that no annotations were loaded.
|
||||
$this->assertEmpty($definitions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,150 @@
|
||||
<?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.
|
||||
$this->expectException(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.
|
||||
$this->expectException(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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
<?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) {
|
||||
$this->expectException('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,214 @@
|
||||
<?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') {
|
||||
$this->expectException('\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,129 @@
|
||||
<?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.
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$this->expectExceptionMessage(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 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�barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"'];
|
||||
$tests[] = ["\xc2\"", '�"', '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());
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user