Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,124 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Bridge\ZfExtensionManagerSfContainerTest.
*/
namespace Drupal\Tests\Component\Bridge;
use Drupal\Component\Bridge\ZfExtensionManagerSfContainer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @coversDefaultClass \Drupal\Component\Bridge\ZfExtensionManagerSfContainer
* @group Bridge
*/
class ZfExtensionManagerSfContainerTest extends UnitTestCase {
/**
* @covers ::setContainer
* @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'));
}
/**
* @covers ::setContainer
* @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'));
}
/**
* @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 array(
array(
'foobar',
'foobar',
),
array(
'foo-bar',
'foobar',
),
array(
'foo_bar',
'foobar',
),
array(
'foo bar',
'foobar',
),
array(
'foo\\bar',
'foobar',
),
array(
'foo/bar',
'foobar',
),
// There is also a strtolower in canonicalizeName.
array(
'Foo/bAr',
'foobar',
),
array(
'foo/-_\\ bar',
'foobar',
),
);
}
}

View file

@ -0,0 +1,536 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Datetime\DateTimePlusTest.
*/
namespace Drupal\Tests\Component\Datetime;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Datetime\DateTimePlus;
/**
* @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus
* @group Datetime
*/
class DateTimePlusTest extends UnitTestCase {
/**
* 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 creating dates from invalid array input.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
*
* @dataProvider providerTestInvalidDateArrays
* @expectedException \Exception
*/
public function testInvalidDateArrays($input, $timezone) {
$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 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.
*
* @dataProvider providerTestInvalidDates
* @expectedException \Exception
*/
public function testInvalidDates($input, $timezone, $format, $message) {
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() {
return array(
// String input.
// Create date object from datetime string.
array('2009-03-07 10:30', 'America/Chicago', '2009-03-07T10:30:00-06:00'),
// Same during daylight savings time.
array('2009-06-07 10:30', 'America/Chicago', '2009-06-07T10:30:00-05:00'),
// Create date object from date string.
array('2009-03-07', 'America/Chicago', '2009-03-07T00:00:00-06:00'),
// Same during daylight savings time.
array('2009-06-07', 'America/Chicago', '2009-06-07T00:00:00-05:00'),
// Create date object from date string.
array('2009-03-07 10:30', 'Australia/Canberra', '2009-03-07T10:30:00+11:00'),
// Same during daylight savings time.
array('2009-06-07 10:30', 'Australia/Canberra', '2009-06-07T10:30:00+10:00'),
);
}
/**
* 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() {
return array(
// Array input.
// Create date object from date array, date only.
array(array('year' => 2010, 'month' => 2, 'day' => 28), 'America/Chicago', '2010-02-28T00:00:00-06:00'),
// Create date object from date array with hour.
array(array('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.
array(array('year' => 2010, 'month' => 2, 'day' => 28), 'Europe/Berlin', '2010-02-28T00:00:00+01:00'),
// Create date object from date array with hour.
array(array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10), 'Europe/Berlin', '2010-02-28T10:00:00+01:00'),
);
}
/**
* 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 array(
// Create a year-only date.
array('2009', NULL, 'Y', 'Y', '2009'),
// Create a month and year-only date.
array('2009-10', NULL, 'Y-m', 'Y-m', '2009-10'),
// Create a time-only date.
array('T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'),
// Create a time-only date.
array('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 array(
// Test for invalid month names when we are using a short version
// of the month.
array('23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors."),
// Test for invalid hour.
array('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."),
// Test for invalid day.
array('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."),
// Test for invalid month.
array('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."),
// Test for invalid year.
array('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."),
);
}
/**
* 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 array(
// One year larger than the documented upper limit of checkdate().
array(array('year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0), 'America/Chicago'),
// One year smaller than the documented lower limit of checkdate().
array(array('year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0), 'America/Chicago'),
// Test for invalid month from date array.
array(array('year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0), 'America/Chicago'),
// Test for invalid hour from date array.
array(array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0), 'America/Chicago'),
// Test for invalid minute from date array.
array(array('year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0), 'America/Chicago'),
// Regression test for https://www.drupal.org/node/2084455.
array(array('hour' => 59, 'minute' => 1,'second' => 1), 'America/Chicago'),
);
}
/**
* 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 array(
// Create a date object with an unspecified timezone, which should
// end up using the system timezone.
array($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.
array($date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'),
// Create a date object with a timezone object.
array($date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'),
// Create a date object with another date object.
array(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 array(
// Create date object from a unix timestamp and display it in
// local time.
array(
'input' => 0,
'initial' => array(
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
),
'transform' => array(
'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.
array(
'input' => 0,
'initial' => array(
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
),
'transform' => array(
'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 array(
// Create date object from datetime string in UTC, and convert
// it to a local date.
array(
'input' => '1970-01-01 00:00:00',
'initial' => array(
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
),
'transform' => array(
'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.
array(
'input' => '1969-12-31 16:00:00',
'initial' => array(
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
),
'transform' => array(
'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.
array(
'input' => '1969-12-31 16:00:00',
'initial' => array(
'timezone' => 'Europe/Warsaw',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00+01:00',
'expected_timezone' => 'Europe/Warsaw',
'expected_offset' => '+3600',
),
'transform' => array(
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1969-12-31T15:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
),
),
);
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Discovery\YamlDiscoveryTest.
*/
namespace Drupal\Tests\Component\Discovery;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Discovery\YamlDiscovery;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamWrapper;
use org\bovigo\vfs\vfsStreamDirectory;
/**
* YamlDiscovery component unit tests.
*
* @group Discovery
*/
class YamlDiscoveryTest extends UnitTestCase {
/**
* 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 = array(
'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 (array('test_1', 'test_2', 'test_3') as $key) {
$this->assertArrayHasKey('name', $data[$key]);
$this->assertEquals($data[$key]['name'], 'test');
}
$this->assertSame(array(), $data['test_4']);
}
}

View file

@ -0,0 +1,135 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\DrupalComponentTest.
*/
namespace Drupal\Tests\Component;
use Drupal\Tests\UnitTestCase;
use org\bovigo\vfs\vfsStream;
/**
* General tests for \Drupal\Component that can't go anywhere else.
*
* @group Component
*/
class DrupalComponentTest extends UnitTestCase {
/**
* 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);
}
}
/**
* 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 = array();
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);
if (preg_match_all('/^.*Drupal\\\Core.*$/m', $contents, $matches)) {
foreach ($matches[0] as $line) {
if ((strpos($line, '@see ') === FALSE)) {
$this->fail(
"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 array(
array(
TRUE,
'@see \\Drupal\\Core\\Something',
),
array(
FALSE,
'\\Drupal\\Core\\Something',
),
array(
FALSE,
"@see \\Drupal\\Core\\Something\n" .
'\\Drupal\\Core\\Something',
),
array(
FALSE,
"\\Drupal\\Core\\Something\n" .
'@see \\Drupal\\Core\\Something',
),
);
}
/**
* @covers \Drupal\Tests\Component\DrupalComponentTest::assertNoCoreUsage
* @dataProvider providerAssertNoCoreUseage
*/
public function testAssertNoCoreUseage($expected_pass, $file_data) {
// Set up a virtual file to read.
$vfs_root = vfsStream::setup('root');
vfsStream::newFile('Test.php')->at($vfs_root)->setContent($file_data);
$file_uri = vfsStream::url('root/Test.php');
try {
$pass = true;
$this->assertNoCoreUsage($file_uri);
}
catch (\PHPUnit_Framework_AssertionFailedError $e) {
$pass = false;
}
$this->assertEquals($expected_pass, $pass, $expected_pass ?
'Test caused a false positive' :
'Test failed to detect Core usage');
}
}

View file

@ -0,0 +1,180 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\EventDispatcher\ContainerAwareEventDispatcherTest.
*/
namespace Drupal\Tests\Component\EventDispatcher;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Tests\AbstractEventDispatcherTest;
use Symfony\Component\EventDispatcher\Tests\CallableClass;
use Symfony\Component\EventDispatcher\Tests\TestEventListener;
/**
* Unit tests for the ContainerAwareEventDispatcher.
*
* NOTE: 98% of this code is a literal copy of Symfony's emerging
* CompiledEventDispatcherTest.
*
* This file does NOT follow Drupal coding standards, so as to simplify future
* synchronizations.
*
* @see https://github.com/symfony/symfony/pull/12521
*
* @group Symfony
*/
class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
{
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->getMock('Symfony\Component\DependencyInjection\IntrospectableContainerInterface');
$container->expects($this->never())->method($this->anything());
$firstListener = new CallableClass();
$secondListener = function () {};
$thirdListener = array(new TestEventListener(), 'preFoo');
$listeners = array(
'test_event' => array(
0 => array(
array('callable' => $firstListener),
array('callable' => $secondListener),
array('callable' => $thirdListener),
),
),
);
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$actualListeners = $dispatcher->getListeners();
$expectedListeners = array(
'test_event' => array(
$firstListener,
$secondListener,
$thirdListener,
),
);
$this->assertSame($expectedListeners, $actualListeners);
}
public function testDispatchWithCallables()
{
// When passing in callables exclusively as listeners into the event
// dispatcher constructor, the event dispatcher must not attempt to
// resolve any services.
$container = $this->getMock('Symfony\Component\DependencyInjection\IntrospectableContainerInterface');
$container->expects($this->never())->method($this->anything());
$firstListener = new CallableClass();
$secondListener = function () {};
$thirdListener = array(new TestEventListener(), 'preFoo');
$listeners = array(
'test_event' => array(
0 => array(
array('callable' => $firstListener),
array('callable' => $secondListener),
array('callable' => $thirdListener),
),
),
);
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$dispatcher->dispatch('test_event');
$this->assertTrue($thirdListener[0]->preFooInvoked);
}
public function testGetListenersWithServices()
{
$container = new ContainerBuilder();
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
$listeners = array(
'test_event' => array(
0 => array(
array('service' => array('listener_service', 'preFoo')),
),
),
);
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$actualListeners = $dispatcher->getListeners();
$listenerService = $container->get('listener_service');
$expectedListeners = array(
'test_event' => array(
array($listenerService, 'preFoo'),
),
);
$this->assertSame($expectedListeners, $actualListeners);
}
public function testDispatchWithServices()
{
$container = new ContainerBuilder();
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
$listeners = array(
'test_event' => array(
0 => array(
array('service' => array('listener_service', 'preFoo')),
),
),
);
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$dispatcher->dispatch('test_event');
$listenerService = $container->get('listener_service');
$this->assertTrue($listenerService->preFooInvoked);
}
public function testRemoveService()
{
$container = new ContainerBuilder();
$container->register('listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
$container->register('other_listener_service', 'Symfony\Component\EventDispatcher\Tests\TestEventListener');
$listeners = array(
'test_event' => array(
0 => array(
array('service' => array('listener_service', 'preFoo')),
array('service' => array('other_listener_service', 'preFoo')),
),
),
);
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$listenerService = $container->get('listener_service');
$dispatcher->removeListener('test_event', array($listenerService, 'preFoo'));
// Ensure that other service was not initialized during removal of the
// listener service.
$this->assertFalse($container->initialized('other_listener_service'));
$dispatcher->dispatch('test_event');
$this->assertFalse($listenerService->preFooInvoked);
$otherService = $container->get('other_listener_service');
$this->assertTrue($otherService->preFooInvoked);
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\FileCache\FileCacheFactoryTest.
*/
namespace Drupal\Tests\Component\FileCache;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\FileCache\FileCacheFactory
* @group FileCache
*/
class FileCacheFactoryTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$settings = [
'collection' => 'test-23',
'cache_backend_class' => '\Drupal\Tests\Component\FileCache\StaticFileCacheBackend',
'cache_backend_configuration' => [
'bin' => 'dog',
],
];
$configuration = FileCacheFactory::getConfiguration();
if (!$configuration) {
$configuration = [];
}
$configuration += [ 'test_foo_settings' => $settings ];
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
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Required prefix configuration is missing
*/
public function testGetNoPrefix() {
FileCacheFactory::setPrefix(NULL);
FileCacheFactory::get('test_foo_settings', []);
}
/**
* @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() {
$prefix = $this->randomMachineName();
FileCacheFactory::setPrefix($prefix);
$this->assertEquals($prefix, FileCacheFactory::getPrefix());
}
}

View file

@ -0,0 +1,147 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\FileCache\FileCacheTest.
*/
namespace Drupal\Tests\Component\FileCache;
use Drupal\Component\FileCache\FileCache;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\FileCache\FileCache
* @group FileCache
*/
class FileCacheTest extends UnitTestCase {
/**
* 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);
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\FileCache\StaticFileCacheBackend.
*/
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 = [];
}
}

View file

@ -0,0 +1,375 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Gettext\PoHeaderTest.
*/
namespace Drupal\Tests\Component\Gettext;
use Drupal\Component\Gettext\PoHeader;
use Drupal\Tests\UnitTestCase;
/**
* Unit tests for the Gettext PO file header handling features.
*
* @see Drupal\Component\Gettext\PoHeader.
*
* @group Gettext
*/
class PoHeaderTest extends UnitTestCase {
/**
* 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 array(
array(
'nplurals=1; plural=0;',
array('default' => 0),
),
array(
'nplurals=2; plural=(n > 1);',
array(0 => 0, 1 => 0, 'default' => 1),
),
array(
'nplurals=2; plural=(n!=1);',
array(1 => 0, 'default' => 1),
),
array(
'nplurals=2; plural=(((n==1)||((n%10)==1))?(0):1);',
array(
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,
),
),
array(
'nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
array(
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,
),
),
array(
'nplurals=3; plural=((n==1)?(0):(((n>=2)&&(n<=4))?(1):2));',
array(
1 => 0,
2 => 1,
3 => 1,
4 => 1,
'default' => 2,
),
),
array(
'nplurals=3; plural=((n==1)?(0):(((n==0)||(((n%100)>0)&&((n%100)<20)))?(1):2));',
array(
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,
),
),
array(
'nplurals=3; plural=((n==1)?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
array(
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,
),),
array(
'nplurals=4; plural=(((n==1)||(n==11))?(0):(((n==2)||(n==12))?(1):(((n>2)&&(n<20))?(2):3)));',
array(
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,
),
),
array(
'nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));',
array(
1 => 0,
2 => 1,
3 => 2,
4 => 2,
101 => 0,
102 => 1,
103 => 2,
104 => 2,
'default' => 3,
),
),
array(
'nplurals=5; plural=((n==1)?(0):((n==2)?(1):((n<7)?(2):((n<11)?(3):4))));',
array(
0 => 2,
1 => 0,
2 => 1,
3 => 2,
4 => 2,
5 => 2,
6 => 2,
7 => 3,
8 => 3,
9 => 3,
10 => 3,
'default' => 4,
),
),
array(
'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)))));',
array(
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,
),
),
);
}
}

View file

@ -0,0 +1,196 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Graph\GraphTest.
*/
namespace Drupal\Tests\Component\Graph;
use Drupal\Component\Graph\Graph;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Graph\Graph
* @group Graph
*/
class GraphTest extends UnitTestCase {
/**
* 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(array(
1 => array(2),
2 => array(3, 4),
3 => array(),
4 => array(3),
5 => array(6),
7 => array(4, 5),
8 => array(9),
9 => array(),
));
$graph_object = new Graph($graph);
$graph = $graph_object->searchAndSort();
$expected_paths = array(
1 => array(2, 3, 4),
2 => array(3, 4),
3 => array(),
4 => array(3),
5 => array(6),
7 => array(4, 3, 5, 6),
8 => array(9),
9 => array(),
);
$this->assertPaths($graph, $expected_paths);
$expected_reverse_paths = array(
1 => array(),
2 => array(1),
3 => array(2, 1, 4, 7),
4 => array(2, 1, 7),
5 => array(7),
7 => array(),
8 => array(),
9 => array(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 = array(
array(1, 2, 3, 4, 5, 7),
array(8, 9),
);
$this->assertComponents($graph, $expected_components);
$expected_weights = array(
array(1, 2, 3),
array(2, 4, 3),
array(7, 4, 3),
array(7, 5),
array(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 = array();
foreach ($graph as $vertex => $edges) {
// Create vertex even if it hasn't any edges.
$normalized_graph[$vertex] = array();
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'] : array();
$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'] : array();
$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 = array();
foreach ($component as $vertex) {
$result_components[] = $graph[$vertex]['component'];
unset($unassigned_vertices[$vertex]);
}
$this->assertEquals(1, count(array_unique($result_components)), sprintf('Expected one unique component for vertices %s, got %s', $this->displayArray($component), $this->displayArray($result_components)));
}
$this->assertEquals(array(), $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)';
}
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\FileStorageReadOnlyTest.
*/
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\FileStorage;
use Drupal\Component\PhpStorage\FileReadOnlyStorage;
/**
* @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 = array(
'directory' => $this->directory,
'bin' => 'test',
);
$this->readonlyStorage = array(
'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() {
$php = new FileStorage($this->standardSettings);
$name = $this->randomMachineName() . '/' . $this->randomMachineName() . '.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($success, TRUE);
$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($php_read->exists($name), TRUE);
// Saving and deleting should always fail.
$this->assertFalse($php_read->save($name, $code));
$this->assertFalse($php_read->delete($name));
}
/**
* @covers ::writeable
*/
public function testWriteable() {
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
$this->assertFalse($php_read->writeable());
}
/**
* @covers ::deleteAll
*/
public function testDeleteAll() {
$php = new FileStorage($this->standardSettings);
$name = $this->randomMachineName() . '/' . $this->randomMachineName() . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write our the file so we can test deleting.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$this->assertTrue($php->save($name, $code));
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
$this->assertFalse($php_read->deleteAll());
// Make sure directory exists prior to removal.
$this->assertTrue(file_exists($this->directory . '/test'), 'File storage directory does not exist.');
}
}

View file

@ -0,0 +1,91 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\FileStorageTest.
*/
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\FileStorage;
/**
* @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 = array(
'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() {
// Write out some files.
$php = new FileStorage($this->standardSettings);
$name = $this->randomMachineName() . '/' . $this->randomMachineName() . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write out a PHP file and ensure it's successfully loaded.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$this->assertTrue($php->save($name, $code), 'Saved php file');
$php->load($name);
$this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
// Make sure directory exists prior to removal.
$this->assertTrue(file_exists($this->directory . '/test'), 'File storage directory does not exist.');
$this->assertTrue($php->deleteAll(), 'Delete all reported success');
$this->assertFalse($php->load($name));
$this->assertFalse(file_exists($this->directory . '/test'), 'File storage directory does not exist after call to deleteAll()');
// Should still return TRUE if directory has already been deleted.
$this->assertTrue($php->deleteAll(), 'Delete all succeeds with nothing to delete');
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\MTimeProtectedFastFileStorageTest.
*/
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 = array(TRUE, FALSE);
/**
* The PHP storage class to test.
*/
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage';
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\MTimeProtectedFileStorageBase.
*/
namespace Drupal\Tests\Component\PhpStorage;
/**
* Base test class for MTime protected storage.
*/
abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase {
/**
* The PHP storage class to test.
*
* This should be overridden by extending classes.
*/
protected $storageClass;
/**
* The secret string to use for file creation.
*
* @var string
*/
protected $secret;
/**
* Test settings to pass to storage instances.
*
* @var array
*/
protected $settings;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->secret = $this->randomMachineName();
$this->settings = array(
'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 . '/' . hash_hmac('sha256', $name, $this->secret . $directory_mtime) . '.php';
// Ensure the file exists and that it and the containing directory have
// minimal permissions. fileperms() can return high bits unrelated to
// permissions, so mask with 0777.
$this->assertTrue(file_exists($expected_filename));
$this->assertSame(fileperms($expected_filename) & 0777, 0444);
$this->assertSame(fileperms($expected_directory) & 0777, 0777);
// Ensure the root directory for the bin has a .htaccess file denying web
// access.
$this->assertSame(file_get_contents($expected_root_directory . '/.htaccess'), call_user_func(array($this->storageClass, 'htaccessLines')));
// Ensure that if the file is replaced with an untrusted one (due to another
// script's file upload vulnerability), it does not get loaded. Since mtime
// granularity is 1 second, we cannot prevent an attack that happens within
// a second of the initial save().
sleep(1);
for ($i = 0; $i < 2; $i++) {
$php = new $this->storageClass($this->settings);
$GLOBALS['hacked'] = FALSE;
$untrusted_code = "<?php\n" . '$GLOBALS["hacked"] = TRUE;';
chmod($expected_directory, 0700);
chmod($expected_filename, 0700);
if ($i) {
// Now try to write the file in such a way that the directory mtime
// changes and invalidates the hash.
file_put_contents($expected_filename . '.tmp', $untrusted_code);
rename($expected_filename . '.tmp', $expected_filename);
}
else {
// On the first try do not change the directory mtime but the filemtime
// is now larger than the directory mtime.
file_put_contents($expected_filename, $untrusted_code);
}
chmod($expected_filename, 0400);
chmod($expected_directory, 0100);
$this->assertSame(file_get_contents($expected_filename), $untrusted_code);
$this->assertSame($php->exists($name), $this->expected[$i]);
$this->assertSame($php->load($name), $this->expected[$i]);
$this->assertSame($GLOBALS['hacked'], $this->expected[$i]);
}
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\MTimeProtectedFileStorageTest.
*/
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 = array(FALSE, FALSE);
/**
* The PHP storage class to test.
*/
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\PhpStorage\PhpStorageTestBase.
*/
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Tests\UnitTestCase;
use org\bovigo\vfs\vfsStream;
/**
* Base test for PHP storages.
*/
abstract class PhpStorageTestBase extends UnitTestCase {
/**
* 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) {
$name = $this->randomMachineName() . '/' . $this->randomMachineName() . '.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');
// 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');
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\Context\ContextTest.
*/
namespace Drupal\Tests\Component\Plugin\Context;
use Drupal\Component\Plugin\Context\Context;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\Context\Context
* @group Plugin
*/
class ContextTest extends UnitTestCase {
/**
* 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(array('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(array('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->setExpectedException(
'Drupal\Component\Plugin\Exception\ContextException',
sprintf("The %s context is required and not present.", $data_type)
);
}
// Exercise getContextValue().
$this->assertEquals($context_value, $mock_context->getContextValue());
}
}
/**
* @covers ::getContextValue
*/
public function testDefaultValue() {
$mock_definition = $this->getMockBuilder('Drupal\Component\Plugin\Context\ContextDefinitionInterface')
->setMethods(array('getDefaultValue'))
->getMockForAbstractClass();
$mock_definition->expects($this->once())
->method('getDefaultValue')
->willReturn('test');
$context = new Context($mock_definition);
$this->assertEquals('test', $context->getContextValue());
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\DefaultFactoryTest.
*/
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\Factory\DefaultFactory
* @group Plugin
*/
class DefaultFactoryTest extends UnitTestCase {
/**
* Tests getPluginClass() with a valid plugin.
*/
public function testGetPluginClassWithValidPlugin() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry';
$class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class]);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a missing class definition.
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage The plugin (cherry) did not specify an instance class.
*/
public function testGetPluginClassWithMissingClass() {
DefaultFactory::getPluginClass('cherry', []);
}
/**
* Tests getPluginClass() with a not existing class definition.
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage Plugin (kiwifruit) instance class "\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit" does not exist.
*/
public function testGetPluginClassWithNotExistingClass() {
DefaultFactory::getPluginClass('kiwifruit', ['class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit']);
}
/**
* Tests getPluginClass() with a required interface.
*/
public function testGetPluginClassWithInterface() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry';
$class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a required interface but no implementation.
*
* @expectedException \Drupal\Component\Plugin\Exception\PluginException
* @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface.
*/
public function testGetPluginClassWithInterfaceAndInvalidClass() {
$plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale';
DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class, 'provider' => 'core'], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\Discovery\DiscoveryCachedTraitTest.
*/
namespace Drupal\Tests\Component\Plugin\Discovery;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait
* @uses Drupal\Component\Plugin\Discovery\DiscoveryTrait
* @group Plugin
*/
class DiscoveryCachedTraitTest extends UnitTestCase {
/**
* 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 array(
['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)
);
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\Discovery\DiscoveryTraitTest.
*/
namespace Drupal\Tests\Component\Plugin\Discovery;
use Drupal\Tests\UnitTestCase;
/**
* @group Plugin
* @coversDefaultClass Drupal\Component\Plugin\Discovery\DiscoveryTrait
*/
class DiscoveryTraitTest extends UnitTestCase {
/**
* 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 array(
['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 array(
[FALSE, ['plugin_name' => 'definition'], 'bad_plugin_name'],
);
}
/**
* @covers ::doGetDefinition
* @expectedException Drupal\Component\Plugin\Exception\PluginNotFoundException
* @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->assertSame(
$expected,
$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
* @expectedException Drupal\Component\Plugin\Exception\PluginNotFoundException
* @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->assertSame(
$expected,
$trait->getDefinition($plugin_id, TRUE)
);
}
/**
* Data provider for testHasDefinition().
*
* @return array
* - Expected TRUE or FALSE.
* - Plugin ID to look for.
*/
public function providerHasDefinition() {
return array(
[TRUE, 'valid'],
[FALSE, 'not_valid'],
);
}
/**
* @covers ::hasDefinition
* @dataProvider providerHasDefinition
*/
public function testHasDefinition($expected, $plugin_id) {
$trait = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryTrait')
->setMethods(array('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(array(
['valid', FALSE, TRUE],
['not_valid', FALSE, FALSE],
)));
// Call hasDefinition().
$this->assertSame(
$expected,
$trait->hasDefinition($plugin_id)
);
}
}

View file

@ -0,0 +1,234 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\Discovery\StaticDiscoveryDecoratorTest.
*/
namespace Drupal\Tests\Component\Plugin\Discovery;
use Drupal\Tests\UnitTestCase;
/**
* @group Plugin
* @coversDefaultClass Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator
*/
class StaticDiscoveryDecoratorTest extends UnitTestCase {
/**
* 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 mock
* Mocked object with expectation of registerDefinitionsCallback() being
* called once.
*/
public function getRegisterDefinitionsCallback() {
$mock_callable = $this->getMockBuilder('\stdClass')
->setMethods(array('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(array('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,
array($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, array());
// Mock a decorated object.
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods(array('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->setExpectedException('Drupal\Component\Plugin\Exception\PluginNotFoundException');
}
// Exercise getDefinition(). It calls parent::getDefinition().
$this->assertEquals(
$expected,
$mock_decorator->getDefinition($base_plugin_id, $exception_on_invalid)
);
}
/**
* Data provider for testGetDefinitions().
*
* @return array
* - bool Whether the test mock has a callback.
* - array Plugin definitions.
*/
public function providerGetDefinitions() {
return [
[TRUE, ['definition' => 'is_fake']],
[FALSE, ['definition' => 'array_of_stuff']],
];
}
/**
* @covers ::getDefinitions
* @dataProvider providerGetDefinitions
*/
public function testGetDefinitions($has_register_definitions, $definitions) {
// Mock our StaticDiscoveryDecorator.
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
->disableOriginalConstructor()
->setMethods(array('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,
array($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, array());
// Mock a decorated object.
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods(array('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->assertArrayEquals(
$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(array($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->assertArrayEquals(
$args,
\call_user_func_array(array($mock_decorated, $method), $args)
);
}
}

View file

@ -0,0 +1,215 @@
<?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 Drupal\Tests\UnitTestCase;
/**
* @group Plugin
* @coversDefaultClass Drupal\Component\Plugin\Factory\ReflectionFactory
*/
class ReflectionFactoryTest extends UnitTestCase {
/**
* 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(array('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->setExpectedException('\ReflectionException');
}
// Finally invoke getInstanceArguments() on our mocked factory.
$ref = new \ReflectionClass($reflector_name);
$result = $get_instance_arguments_ref->invoke(
$reflection_factory, $ref, $plugin_id, $plugin_definition, $configuration);
$this->assertEquals($expected, $result);
}
}
/**
* Override ReflectionFactory because ::createInstance() calls a static method.
*
* We have to override getPluginClass so that we can stub out its return value.
*/
class StubReflectionFactory extends ReflectionFactory {
/**
* {@inheritdoc}
*/
public static function getPluginClass($plugin_id, $plugin_definition = NULL, $required_interface = NULL) {
// Return the class name from the plugin definition.
return $plugin_definition[$plugin_id]['class'];
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsPluginId {
public function __construct($plugin_id) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsMany {
public function __construct(
$configuration, $plugin_definition, $plugin_id, $foo = 'default_value', $what_am_i_doing_here = 'what_default'
) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsConfigArrayKey {
public function __construct($config_name) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsAllNull {
public function __construct($charismatic, $demure, $delightful, $electrostatic) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsNoConstructor {
}

View file

@ -0,0 +1,112 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\PluginBaseTest.
*/
namespace Drupal\Tests\Component\Plugin;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\PluginBase
* @group Plugin
*/
class PluginBaseTest extends UnitTestCase {
/**
* @dataProvider providerTestGetPluginId
* @covers ::getPluginId
*/
public function testGetPluginId($plugin_id, $expected) {
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', array(
array(),
$plugin_id,
array(),
));
$this->assertEquals($expected, $plugin_base->getPluginId());
}
/**
* Returns test data for testGetPluginId().
*
* @return array
*/
public function providerTestGetPluginId() {
return array(
array('base_id', 'base_id'),
array('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', array(
array(),
$plugin_id,
array(),
));
$this->assertEquals($expected, $plugin_base->getBaseId());
}
/**
* Returns test data for testGetBaseId().
*
* @return array
*/
public function providerTestGetBaseId() {
return array(
array('base_id', 'base_id'),
array('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', array(
array(),
$plugin_id,
array(),
));
$this->assertEquals($expected, $plugin_base->getDerivativeId());
}
/**
* Returns test data for testGetDerivativeId().
*
* @return array
*/
public function providerTestGetDerivativeId() {
return array(
array('base_id', NULL),
array('base_id:derivative', 'derivative'),
);
}
/**
* @covers ::getPluginDefinition
*/
public function testGetPluginDefinition() {
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', array(
array(),
'plugin_id',
array('value', array('key' => 'value')),
));
$this->assertEquals(array('value', array('key' => 'value')), $plugin_base->getPluginDefinition());
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\PluginManagerBaseTest.
*/
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\PluginManagerBase
* @group Plugin
*/
class PluginManagerBaseTest extends UnitTestCase {
/**
* 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 array(
'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(array('createInstance'))
->getMockForAbstractClass();
$mock_factory->expects($this->exactly($expects_count))
->method('createInstance')
->willReturnCallback(array($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 = array('config' => 'something');
$result = $manager->createInstance('valid', $configuration_array);
$this->assertEquals('valid', $result['plugin_id']);
$this->assertArrayEquals($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 = 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->assertArrayEquals($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->assertArrayEquals($configuration_array, $fallback_result['configuration']);
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\StubFallbackPluginManager.
*/
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 = array()) {
// Minimally implement getFallbackPluginId so that we can test it.
return $plugin_id . '_fallback';
}
}

View file

@ -0,0 +1,374 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\ProxyBuilder\ProxyBuilderTest.
*/
namespace Drupal\Tests\Component\ProxyBuilder;
use Drupal\Component\ProxyBuilder\ProxyBuilder;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\ProxyBuilder\ProxyBuilder
* @group proxy_builder
*/
class ProxyBuilderTest extends UnitTestCase {
/**
* 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_Component_ProxyBuilder_TestServiceNoMethod_Proxy', $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'
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'
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'
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'
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'
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'
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'
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 = '') {
$proxy_class = $this->proxyBuilder->buildProxyClassName($class);
$expected_string = <<<'EOS'
/**
* Provides a proxy class for \{{ class }}.
*
* @see \Drupal\Component\ProxyBuilder
*/
class {{ proxy_class }}{{ interface_string }}
{
/**
* @var string
*/
protected $serviceId;
/**
* @var \{{ class }}
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $serviceId)
{
$this->container = $container;
$this->serviceId = $serviceId;
}
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$method_name = 'get' . Container::camelize($this->serviceId) . 'Service';
$this->service = $this->container->$method_name(false);
}
return $this->service;
}
{{ expected_methods_body }}
}
EOS;
$expected_string = str_replace('{{ proxy_class }}', $proxy_class, $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 = array()) {
}
}
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 {
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\ProxyBuilder\ProxyDumperTest.
*/
namespace Drupal\Tests\Component\ProxyBuilder;
use Drupal\Component\ProxyBuilder\ProxyDumper;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\Definition;
/**
* @coversDefaultClass \Drupal\Component\ProxyBuilder\ProxyDumper
* @group proxy_builder
*/
class ProxyDumperTest extends UnitTestCase {
/**
* The mocked proxy builder.
*
* @var \Drupal\Component\ProxyBuilder\ProxyBuilder|\PHPUnit_Framework_MockObject_MockObject
*/
protected $proxyBuilder;
/**
* The tested proxy dumper.
*
* @var \Drupal\Component\ProxyBuilder\ProxyDumper
*/
protected $proxyDumper;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->proxyBuilder = $this->getMockBuilder('Drupal\Component\ProxyBuilder\ProxyBuilder')
->disableOriginalConstructor()
->setMethods(['build'])
->getMock();
$this->proxyDumper = new ProxyDumper($this->proxyBuilder);
}
/**
* @dataProvider providerTestIsProxyCandidate
* @covers ::isProxyCandidate
*/
public function testIsProxyCandidate(Definition $definition, $expected) {
$this->assertSame($expected, $this->proxyDumper->isProxyCandidate($definition));
}
public function providerTestIsProxyCandidate() {
// Not lazy service.
$data = [];
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestService');
$data[] = [$definition, FALSE];
// Not existing service.
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestNotExistingService');
$definition->setLazy(TRUE);
$data[] = [$definition, FALSE];
// Existing and lazy service.
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestService');
$definition->setLazy(TRUE);
$data[] = [$definition, TRUE];
return $data;
}
public function testGetProxyFactoryCode() {
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestService');
$definition->setLazy(TRUE);
$result = $this->proxyDumper->getProxyFactoryCode($definition, 'test_service');
$expected = <<<'EOS'
if ($lazyLoad) {
return $this->services['test_service'] = new Drupal_Tests_Component_ProxyBuilder_TestService_Proxy($this, 'test_service');
}
EOS;
$this->assertEquals($expected, $result);
}
/**
* @covers ::getProxyCode
*/
public function testGetProxyCode() {
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestService');
$definition->setLazy(TRUE);
$class = 'class Drupal_Tests_Component_ProxyBuilder_TestService_Proxy {}';
$this->proxyBuilder->expects($this->once())
->method('build')
->with('Drupal\Tests\Component\ProxyBuilder\TestService')
->willReturn($class);
$result = $this->proxyDumper->getProxyCode($definition);
$this->assertEquals($class, $result);
}
/**
* @covers ::getProxyCode
*/
public function testGetProxyCodeWithSameClassMultipleTimes() {
$definition = new Definition('Drupal\Tests\Component\ProxyBuilder\TestService');
$definition->setLazy(TRUE);
$class = 'class Drupal_Tests_Component_ProxyBuilder_TestService_Proxy {}';
$this->proxyBuilder->expects($this->once())
->method('build')
->with('Drupal\Tests\Component\ProxyBuilder\TestService')
->willReturn($class);
$result = $this->proxyDumper->getProxyCode($definition);
$this->assertEquals($class, $result);
$result = $this->proxyDumper->getProxyCode($definition);
$this->assertEquals('', $result);
}
}
class TestService {
}

View file

@ -0,0 +1,122 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Serialization\JsonTest.
*/
namespace Drupal\Tests\Component\Serialization;
use Drupal\Component\Serialization\Json;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Serialization\Json
* @group Serialization
*/
class JsonTest extends UnitTestCase {
/**
* A test string with the full ASCII table.
*
* @var string
*/
protected $string;
/**
* An array of unsafe html characters which has to be encoded.
*
* @var array
*/
protected $htmlUnsafe;
/**
* An array of unsafe html characters which are already escaped.
*
* @var array
*/
protected $htmlUnsafeEscaped;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Setup a string with the full ASCII table.
// @todo: Add tests for non-ASCII characters and Unicode.
$this->string = '';
for ($i = 1; $i < 128; $i++) {
$this->string .= chr($i);
}
// Characters that must be escaped.
// We check for unescaped " separately.
$this->htmlUnsafe = array('<', '>', '\'', '&');
// The following are the encoded forms of: < > ' & "
$this->htmlUnsafeEscaped = array('\u003C', '\u003E', '\u0027', '\u0026', '\u0022');
}
/**
* Tests encoding for every ASCII character.
*/
public function testEncodingAscii() {
// Verify there aren't character encoding problems with the source string.
$this->assertSame(strlen($this->string), 127, 'A string with the full ASCII table has the correct length.');
foreach ($this->htmlUnsafe as $char) {
$this->assertTrue(strpos($this->string, $char) > 0, sprintf('A string with the full ASCII table includes %s.', $char));
}
}
/**
* Tests encoding length.
*/
public function testEncodingLength() {
// Verify that JSON encoding produces a string with all of the characters.
$json = Json::encode($this->string);
$this->assertTrue(strlen($json) > strlen($this->string), 'A JSON encoded string is larger than the source string.');
}
/**
* Tests end and start of the encoded string.
*/
public function testEncodingStartEnd() {
$json = Json::encode($this->string);
// The first and last characters should be ", and no others.
$this->assertTrue($json[0] == '"', 'A JSON encoded string begins with ".');
$this->assertTrue($json[strlen($json) - 1] == '"', 'A JSON encoded string ends with ".');
$this->assertTrue(substr_count($json, '"') == 2, 'A JSON encoded string contains exactly two ".');
}
/**
* Tests converting PHP variables to JSON strings and back.
*/
public function testReversibility() {
$json = Json::encode($this->string);
// Verify that encoding/decoding is reversible.
$json_decoded = Json::decode($json);
$this->assertSame($this->string, $json_decoded, 'Encoding a string to JSON and decoding back results in the original string.');
}
/**
* Test the reversibility of structured data
*/
public function testStructuredReversibility() {
// Verify reversibility for structured data. Also verify that necessary
// characters are escaped.
$source = array(TRUE, FALSE, 0, 1, '0', '1', $this->string, array('key1' => $this->string, 'key2' => array('nested' => TRUE)));
$json = Json::encode($source);
foreach ($this->htmlUnsafe as $char) {
$this->assertTrue(strpos($json, $char) === FALSE, sprintf('A JSON encoded string does not contain %s.', $char));
}
// Verify that JSON encoding escapes the HTML unsafe characters
foreach ($this->htmlUnsafeEscaped as $char) {
$this->assertTrue(strpos($json, $char) > 0, sprintf('A JSON encoded string contains %s.', $char));
}
$json_decoded = Json::decode($json);
$this->assertNotSame($source, $json, 'An array encoded in JSON is identical to the source.');
$this->assertSame($source, $json_decoded, 'Encoding structured data to JSON and decoding back not results in the original data.');
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Serialization\YamlTest.
*/
namespace Drupal\Tests\Component\Serialization;
use Drupal\Component\Serialization\Yaml;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Serialization\Yaml
* @group Serialization
*/
class YamlTest extends UnitTestCase {
/**
* @covers ::decode
*/
public function testDecode() {
// Test that files without line break endings are properly interpreted.
$yaml = 'foo: bar';
$expected = array(
'foo' => 'bar',
);
$this->assertSame($expected, Yaml::decode($yaml));
$yaml .= "\n";
$this->assertSame($expected, Yaml::decode($yaml));
$yaml .= "\n";
$this->assertSame($expected, Yaml::decode($yaml));
$yaml = "{}\n";
$expected = array();
$this->assertSame($expected, Yaml::decode($yaml));
$yaml = '';
$this->assertNULL(Yaml::decode($yaml));
$yaml .= "\n";
$this->assertNULL(Yaml::decode($yaml));
$yaml .= "\n";
$this->assertNULL(Yaml::decode($yaml));
}
/**
* @covers ::encode
*/
public function testEncode() {
$decoded = array(
'foo' => 'bar',
);
$this->assertSame('foo: bar' . "\n", Yaml::encode($decoded));
}
/**
* @covers ::getFileExtension
*/
public function testGetFileExtension() {
$this->assertEquals('yml', Yaml::getFileExtension());
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Transliteration\PhpTransliterationTest.
*/
namespace Drupal\Tests\Component\Transliteration;
use Drupal\Component\Transliteration\PhpTransliteration;
use Drupal\Component\Utility\Random;
use Drupal\Tests\UnitTestCase;
use org\bovigo\vfs\vfsStream;
/**
* Tests Transliteration component functionality.
*
* @group Transliteration
*
* @coversDefaultClass \Drupal\Component\Transliteration\PhpTransliteration
*/
class PhpTransliterationTest extends UnitTestCase {
/**
* Tests the PhpTransliteration::removeDiacritics() function.
*
* @param string $original
* The language code to test.
* @param string $expected
* The expected return from PhpTransliteration::removeDiacritics().
*
* @dataProvider providerTestPhpTransliterationRemoveDiacritics
*/
public function testRemoveDiacritics($original, $expected) {
$transliterator_class = new PhpTransliteration();
$result = $transliterator_class->removeDiacritics($original);
$this->assertEquals($expected, $result);
}
/**
* Provides data for self::testRemoveDiacritics().
*
* @return array
* An array of arrays, each containing the parameters for
* self::testRemoveDiacritics().
*/
public function providerTestPhpTransliterationRemoveDiacritics() {
return array(
// Test all characters in the Unicode range 0x00bf to 0x017f.
array('ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ', 'AAAAAAÆCEEEEIIII'),
array('ÐÑÒÓÔÕÖרÙÚÛÜÝÞß', 'ÐNOOOOO×OUUUUYÞß'),
array('àáâãäåæçèéêëìíîï', 'aaaaaaæceeeeiiii'),
array('ðñòóôõö÷øùúûüýþÿ', 'ðnooooo÷ouuuuyþy'),
array('ĀāĂ㥹ĆćĈĉĊċČčĎď', 'AaAaAaCcCcCcCcDd'),
array('ĐđĒēĔĕĖėĘęĚěĜĝĞğ', 'DdEeEeEeEeEeGgGg'),
array('ĠġĢģĤĥĦħĨĩĪīĬĭĮį', 'GgGgHhHhIiIiIiIi'),
array('İıIJijĴĵĶķĸĹĺĻļĽľĿ', 'IiIJijJjKkĸLlLlLlL'),
array('ŀŁłŃńŅņŇňʼnŊŋŌōŎŏ', 'lLlNnNnNnʼnŊŋOoOo'),
array('ŐőŒœŔŕŖŗŘřŚśŜŝŞş', 'OoŒœRrRrRrSsSsSs'),
array('ŠšŢţŤťŦŧŨũŪūŬŭŮů', 'SsTtTtTtUuUuUuUu'),
array('ŰűŲųŴŵŶŷŸŹźŻżŽž', 'UuUuWwYyYZzZzZz'),
// Test all characters in the Unicode range 0x01CD to 0x024F.
array('ǍǎǏ', 'AaI'),
array('ǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟ', 'iOoUuUuUuUuUuǝAa'),
array('ǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯ', 'AaǢǣGgGgKkOoOoǮǯ'),
array('ǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿ', 'jDZDzdzGgǶǷNnAaǼǽOo'),
array('ȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏ', 'AaAaEeEeIiIiOoOo'),
array('ȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟ', 'RrRrUuUuSsTtȜȝHh'),
array('ȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯ', 'ȠȡȢȣZzAaEeOoOoOo'),
array('ȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿ', 'OoYylntjȸȹACcLTs'),
array('ɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ', 'zɁɂBUɅEeJjQqRrYy'),
);
}
/**
* Tests the PhpTransliteration class.
*
* @param string $langcode
* The language code to test.
* @param string $original
* The original string.
* @param string $expected
* The expected return from PhpTransliteration::transliterate().
* @param string $unknown_character
* (optional) The character to substitute for characters in $string without
* transliterated equivalents. Defaults to '?'.
* @param int $max_length
* (optional) If provided, return at most this many characters, ensuring
* that the transliteration does not split in the middle of an input
* character's transliteration.
*
* @dataProvider providerTestPhpTransliteration
*/
public function testPhpTransliteration($langcode, $original, $expected, $unknown_character = '?', $max_length = NULL) {
$transliterator_class = new PhpTransliteration();
$actual = $transliterator_class->transliterate($original, $langcode, $unknown_character, $max_length);
$this->assertSame($expected, $actual);
}
/**
* Provides data for self::testPhpTransliteration().
*
* @return array
* An array of arrays, each containing the parameters for
* self::testPhpTransliteration().
*/
public function providerTestPhpTransliteration() {
$random_generator = new Random();
$random = $random_generator->string(10);
// Make some strings with two, three, and four-byte characters for testing.
// Note that the 3-byte character is overridden by the 'kg' language.
$two_byte = 'Ä Ö Ü Å Ø äöüåøhello';
// This is a Cyrrillic character that looks something like a u. See
// http://www.unicode.org/charts/PDF/U0400.pdf
$three_byte = html_entity_decode('&#x446;', ENT_NOQUOTES, 'UTF-8');
// This is a Canadian Aboriginal character like a triangle. See
// http://www.unicode.org/charts/PDF/U1400.pdf
$four_byte = html_entity_decode('&#x1411;', ENT_NOQUOTES, 'UTF-8');
// These are two Gothic alphabet letters. See
// http://en.wikipedia.org/wiki/Gothic_alphabet
// They are not in our tables, but should at least give us '?' (unknown).
$five_byte = html_entity_decode('&#x10330;&#x10338;', ENT_NOQUOTES, 'UTF-8');
return array(
// Each test case is (language code, input, output).
// Test ASCII in English.
array('en', $random, $random),
// Test ASCII in some other language with no overrides.
array('fr', $random, $random),
// Test 3 and 4-byte characters in a language without overrides.
// Note: if the data tables change, these will need to change too! They
// are set up to test that data table loading works, so values come
// directly from the data files.
array('fr', $three_byte, 'c'),
array('fr', $four_byte, 'wii'),
// Test 5-byte characters.
array('en', $five_byte, '??'),
// Test a language with no overrides.
array('en', $two_byte, 'A O U A O aouaohello'),
// Test language overrides provided by core.
array('de', $two_byte, 'Ae Oe Ue A O aeoeueaohello'),
array('de', $random, $random),
array('dk', $two_byte, 'A O U Aa Oe aouaaoehello'),
array('dk', $random, $random),
array('kg', $three_byte, 'ts'),
// Test strings in some other languages.
// Turkish, provided by drupal.org user Kartagis.
array('tr', 'Abayı serdiler bize. Söyleyeceğim yüzlerine. Sanırım hepimiz aynı şeyi düşünüyoruz.', 'Abayi serdiler bize. Soyleyecegim yuzlerine. Sanirim hepimiz ayni seyi dusunuyoruz.'),
// Illegal/unknown unicode.
array('en', chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80), '?'),
// Max length.
array('de', $two_byte, 'Ae Oe', '?', 5),
);
}
/**
* Tests the transliteration with max length.
*/
public function testTransliterationWithMaxLength() {
$transliteration = new PhpTransliteration();
// Test with max length, using German. It should never split up the
// transliteration of a single character.
$input = 'Ä Ö Ü Å Ø äöüåøhello';
$trunc_output = 'Ae Oe Ue A O aeoe';
$this->assertSame($trunc_output, $transliteration->transliterate($input, 'de', '?', 17), 'Truncating to 17 characters works');
$this->assertSame($trunc_output, $transliteration->transliterate($input, 'de', '?', 18), 'Truncating to 18 characters works');
}
/**
* Tests inclusion is safe.
*
* @covers ::readLanguageOverrides
*/
public function testSafeInclude() {
// The overrides in the transliteration data directory transliterates 0x82
// into "safe" but the overrides one directory higher transliterates the
// same character into "security hole". So by using "../index" as the
// language code we can test the ../ is stripped from the langcode.
vfsStream::setup('transliteration', NULL, [
'index.php' => '<?php $overrides = ["../index" => [0x82 => "security hole"]];',
'dir' => [
'index.php' => '<?php $overrides = ["../index" => [0x82 => "safe"]];',
],
]);
$transliteration = new PhpTransliteration(vfsStream::url('transliteration/dir'));
$transliterated = $transliteration->transliterate(chr(0xC2) . chr(0x82), '../index');
$this->assertSame($transliterated, 'safe');
}
}

View file

@ -0,0 +1,221 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\ArgumentsResolverTest.
*/
namespace Drupal\Tests\Component\Utility {
use Drupal\Component\Utility\ArgumentsResolver;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Utility\ArgumentsResolver
* @group Access
*/
class ArgumentsResolverTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
}
/**
* Tests the getArgument() method.
*
* @dataProvider providerTestGetArgument
*/
public function testGetArgument($callable, $scalars, $objects, $wildcards, $expected) {
$arguments = (new ArgumentsResolver($scalars, $objects, $wildcards))->getArguments($callable);
$this->assertSame($expected, $arguments);
}
/**
* Provides test data to testGetArgument().
*/
public function providerTestGetArgument() {
$data = [];
// Test an optional parameter with no provided value.
$data[] = [
function($foo = 'foo') {}, [], [], [] , ['foo'],
];
// Test an optional parameter with a provided value.
$data[] = [
function($foo = 'foo') {}, ['foo' => 'bar'], [], [], ['bar'],
];
// Test with a provided value.
$data[] = [
function($foo) {}, ['foo' => 'bar'], [], [], ['bar'],
];
// Test with an explicitly NULL value.
$data[] = [
function($foo) {}, [], ['foo' => NULL], [], [NULL],
];
// Test with a raw value that overrides the provided upcast value, since
// it is not typehinted.
$scalars = ['foo' => 'baz'];
$objects = ['foo' => new \stdClass()];
$data[] = [
function($foo) {}, $scalars, $objects, [], ['baz'],
];
return $data;
}
/**
* Tests getArgument() with an object.
*/
public function testGetArgumentObject() {
$callable = function(\stdClass $object) {};
$object = new \stdClass();
$arguments = (new ArgumentsResolver([], ['object' => $object], []))->getArguments($callable);
$this->assertSame([$object], $arguments);
}
/**
* Tests getArgument() with a wildcard object for a parameter with a custom name.
*/
public function testGetWildcardArgument() {
$callable = function(\stdClass $custom_name) {};
$object = new \stdClass();
$arguments = (new ArgumentsResolver([], [], [$object]))->getArguments($callable);
$this->assertSame([$object], $arguments);
}
/**
* Tests getArgument() with a Route, Request, and Account object.
*/
public function testGetArgumentOrder() {
$a1 = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface1');
$a2 = $this->getMock('\Drupal\Tests\Component\Utility\TestClass');
$a3 = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface2');
$objects = [
't1' => $a1,
'tc' => $a2,
];
$wildcards = [$a3];
$resolver = new ArgumentsResolver([], $objects, $wildcards);
$callable = function(TestInterface1 $t1, TestClass $tc, TestInterface2 $t2) {};
$arguments = $resolver->getArguments($callable);
$this->assertSame([$a1, $a2, $a3], $arguments);
// Test again, but with the arguments in a different order.
$callable = function(TestInterface2 $t2, TestClass $tc, TestInterface1 $t1) {};
$arguments = $resolver->getArguments($callable);
$this->assertSame([$a3, $a2, $a1], $arguments);
}
/**
* Tests getArgument() with a wildcard parameter with no typehint.
*
* Without the typehint, the wildcard object will not be passed to the callable.
*
* @expectedException \RuntimeException
* @expectedExceptionMessage requires a value for the "$route" argument.
*/
public function testGetWildcardArgumentNoTypehint() {
$a = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface1');
$wildcards = [$a];
$resolver = new ArgumentsResolver([], [], $wildcards);
$callable = function($route) {};
$arguments = $resolver->getArguments($callable);
$this->assertNull($arguments);
}
/**
* Tests getArgument() with a named parameter with no typehint and a value.
*
* Without the typehint, passing a value to a named parameter will still
* receive the provided value.
*/
public function testGetArgumentRouteNoTypehintAndValue() {
$scalars = ['route' => 'foo'];
$resolver = new ArgumentsResolver($scalars, [], []);
$callable = function($route) {};
$arguments = $resolver->getArguments($callable);
$this->assertSame(['foo'], $arguments);
}
/**
* Tests handleUnresolvedArgument() for a scalar argument.
*
* @expectedException \RuntimeException
* @expectedExceptionMessage requires a value for the "$foo" argument.
*/
public function testHandleNotUpcastedArgument() {
$objects = ['foo' => 'bar'];
$scalars = ['foo' => 'baz'];
$resolver = new ArgumentsResolver($scalars, $objects, []);
$callable = function(\stdClass $foo) {};
$arguments = $resolver->getArguments($callable);
$this->assertNull($arguments);
}
/**
* Tests handleUnresolvedArgument() for missing arguments.
*
* @expectedException \RuntimeException
* @expectedExceptionMessage requires a value for the "$foo" argument.
*
* @dataProvider providerTestHandleUnresolvedArgument
*/
public function testHandleUnresolvedArgument($callable) {
$resolver = new ArgumentsResolver([], [], []);
$arguments = $resolver->getArguments($callable);
$this->assertNull($arguments);
}
/**
* Provides test data to testHandleUnresolvedArgument().
*/
public function providerTestHandleUnresolvedArgument() {
$data = [];
$data[] = [function($foo) {}];
$data[] = [[new TestClass(), 'access']];
$data[] = ['test_access_arguments_resolver_access'];
return $data;
}
}
/**
* Provides a test class.
*/
class TestClass {
public function access($foo) {
}
}
/**
* Provides a test interface.
*/
interface TestInterface1 {
}
/**
* Provides a different test interface.
*/
interface TestInterface2 {
}
}
namespace {
function test_access_arguments_resolver_access($foo) {
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\BytesTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Bytes;
use Drupal\Tests\UnitTestCase;
/**
* Tests bytes size parsing helper methods.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Bytes
*/
class BytesTest extends UnitTestCase {
/**
* Tests \Drupal\Component\Utility\Bytes::toInt().
*
* @param int $size
* The value for the size argument for
* \Drupal\Component\Utility\Bytes::toInt().
* @param int $expected_int
* The expected return value from
* \Drupal\Component\Utility\Bytes::toInt().
*
* @dataProvider providerTestToInt
* @covers ::toInt
*/
public function testToInt($size, $expected_int) {
$this->assertEquals($expected_int, Bytes::toInt($size));
}
/**
* Provides data for testToInt.
*
* @return array
* An array of arrays, each containing the argument for
* \Drupal\Component\Utility\Bytes::toInt(): size, and the expected return
* value.
*/
public function providerTestToInt() {
return array(
array('1', 1),
array('1 byte', 1),
array('1 KB' , Bytes::KILOBYTE),
array('1 MB' , pow(Bytes::KILOBYTE, 2)),
array('1 GB' , pow(Bytes::KILOBYTE, 3)),
array('1 TB' , pow(Bytes::KILOBYTE, 4)),
array('1 PB' , pow(Bytes::KILOBYTE, 5)),
array('1 EB' , pow(Bytes::KILOBYTE, 6)),
array('1 ZB' , pow(Bytes::KILOBYTE, 7)),
array('1 YB' , pow(Bytes::KILOBYTE, 8)),
array('23476892 bytes', 23476892),
array('76MRandomStringThatShouldBeIgnoredByParseSize.', 79691776), // 76 MB
array('76.24 Giggabyte', 81862076662), // 76.24 GB (with typo)
);
}
}

View file

@ -0,0 +1,126 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\ColorTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Color;
use Drupal\Tests\UnitTestCase;
/**
* Tests Color utility class conversions.
*
* @group Utility
*/
class ColorTest extends UnitTestCase {
/**
* Tests Color::hexToRgb().
*
* @param string $value
* The hex color value.
* @param string $expected
* The expected rgb color value.
* @param bool $invalid
* Whether this value is invalid and exception should be expected.
*
* @dataProvider providerTestHexToRgb
*/
public function testHexToRgb($value, $expected, $invalid = FALSE) {
if ($invalid) {
$this->setExpectedException('InvalidArgumentException');
}
$this->assertSame($expected, Color::hexToRgb($value));
}
/**
* Data provider for testHexToRgb().
*
* @see testHexToRgb()
*
* @return array
* An array of arrays containing:
* - The hex color value.
* - The rgb color array value.
* - (optional) Boolean indicating invalid status. Defaults to FALSE.
*/
public function providerTestHexToRgb() {
$invalid = array();
// Any invalid arguments should throw an exception.
foreach (array('', '-1', '1', '12', '12345', '1234567', '123456789', '123456789a', 'foo') as $value) {
$invalid[] = array($value, '', TRUE);
}
// Duplicate all invalid value tests with additional '#' prefix.
// The '#' prefix inherently turns the data type into a string.
foreach ($invalid as $value) {
$invalid[] = array('#' . $value[0], '', TRUE);
}
// Add invalid data types (hex value must be a string).
foreach (array(
1, 12, 1234, 12345, 123456, 1234567, 12345678, 123456789, 123456789,
-1, PHP_INT_MAX, PHP_INT_MAX + 1, -PHP_INT_MAX, 0x0, 0x010
) as $value) {
$invalid[] = array($value, '', TRUE);
}
// And some valid values.
$valid = array(
// Shorthands without alpha.
array('hex' => '#000', 'rgb' => array('red' => 0, 'green' => 0, 'blue' => 0)),
array('hex' => '#fff', 'rgb' => array('red' => 255, 'green' => 255, 'blue' => 255)),
array('hex' => '#abc', 'rgb' => array('red' => 170, 'green' => 187, 'blue' => 204)),
array('hex' => 'cba', 'rgb' => array('red' => 204, 'green' => 187, 'blue' => 170)),
// Full without alpha.
array('hex' => '#000000', 'rgb' => array('red' => 0, 'green' => 0, 'blue' => 0)),
array('hex' => '#ffffff', 'rgb' => array('red' => 255, 'green' => 255, 'blue' => 255)),
array('hex' => '#010203', 'rgb' => array('red' => 1, 'green' => 2, 'blue' => 3)),
);
return array_merge($invalid, $valid);
}
/**
* Tests Color::rgbToHex().
*
* @param string $value
* The rgb color value.
* @param string $expected
* The expected hex color value.
*
* @dataProvider providerTestRbgToHex
*/
public function testRgbToHex($value, $expected) {
$this->assertSame($expected, Color::rgbToHex($value));
}
/**
* Data provider for testRgbToHex().
*
* @see testRgbToHex()
*
* @return array
* An array of arrays containing:
* - The rgb color array value.
* - The hex color value.
*/
public function providerTestRbgToHex() {
// Input using named RGB array (e.g., as returned by Color::hexToRgb()).
$tests = array(
array(array('red' => 0, 'green' => 0, 'blue' => 0), '#000000'),
array(array('red' => 255, 'green' => 255, 'blue' => 255), '#ffffff'),
array(array('red' => 119, 'green' => 119, 'blue' => 119), '#777777'),
array(array('red' => 1, 'green' => 2, 'blue' => 3), '#010203'),
);
// Input using indexed RGB array (e.g.: array(10, 10, 10)).
foreach ($tests as $test) {
$tests[] = array(array_values($test[0]), $test[1]);
}
// Input using CSS RGB string notation (e.g.: 10, 10, 10).
foreach ($tests as $test) {
$tests[] = array(implode(', ', $test[0]), $test[1]);
}
return $tests;
}
}

View file

@ -0,0 +1,155 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\CryptTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Utility\Crypt;
/**
* Tests random byte generation.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Crypt
*/
class CryptTest extends UnitTestCase {
/**
* Tests random byte generation.
*
* @covers ::randomBytes
*/
public function testRandomBytes() {
for ($i = 1; $i < 10; $i++) {
$count = rand(10, 10000);
// Check that different values are being generated.
$this->assertNotEquals(Crypt::randomBytes($count), Crypt::randomBytes($count));
// Check the length.
$this->assertEquals(strlen(Crypt::randomBytes($count)), $count);
}
}
/**
* Tests hash generation.
*
* @dataProvider providerTestHashBase64
* @covers ::hashBase64
*
* @param string $data
* Data to hash.
* @param string $expected_hash
* Expected result from hashing $data.
*/
public function testHashBase64($data, $expected_hash) {
$hash = Crypt::hashBase64($data);
$this->assertEquals($expected_hash, $hash, 'The correct hash was not calculated.');
}
/**
* Tests HMAC generation.
*
* @dataProvider providerTestHmacBase64
* @covers ::hmacBase64
*
* @param string $data
* Data to hash.
* @param string $key
* Key to use in hashing process.
* @param string $expected_hmac
* Expected result from hashing $data using $key.
*/
public function testHmacBase64($data, $key, $expected_hmac) {
$hmac = Crypt::hmacBase64($data, $key);
$this->assertEquals($expected_hmac, $hmac, 'The correct hmac was not calculated.');
}
/**
* Tests the hmacBase64 method with invalid parameters.
*
* @dataProvider providerTestHmacBase64Invalid
* @expectedException InvalidArgumentException
* @covers ::hmacBase64
*
* @param string $data
* Data to hash.
* @param string $key
* Key to use in hashing process.
*/
public function testHmacBase64Invalid($data, $key) {
Crypt::hmacBase64($data, $key);
}
/**
* Provides data for self::testHashBase64().
*
* @return array Test data.
*/
public function providerTestHashBase64() {
return array(
array(
'data' => 'The SHA (Secure Hash Algorithm) is one of a number of cryptographic hash functions. A cryptographic hash is like a signature for a text or a data file. SHA-256 algorithm generates an almost-unique, fixed size 256-bit (32-byte) hash. Hash is a one way function it cannot be decrypted back. This makes it suitable for password validation, challenge hash authentication, anti-tamper, digital signatures.',
'expectedHash' => '034rT6smZAVRxpq8O98cFFNLIVx_Ph1EwLZQKcmRR_s',
),
array(
'data' => 'SHA-256 is one of the successor hash functions to SHA-1, and is one of the strongest hash functions available.',
'expected_hash' => 'yuqkDDYqprL71k4xIb6K6D7n76xldO4jseRhEkEE6SI',
),
);
}
/**
* Provides data for self::testHmacBase64().
*
* @return array Test data.
*/
public function providerTestHmacBase64() {
return array(
array(
'data' => 'Calculates a base-64 encoded, URL-safe sha-256 hmac.',
'key' => 'secret-key',
'expected_hmac' => '2AaH63zwjhekWZlEpAiufyfhAHIzbQhl9Hd9oCi3_c8',
),
);
}
/**
* Provides data for self::testHmacBase64().
*
* @return array Test data.
*/
public function providerTestHmacBase64Invalid() {
return array(
array(new \stdClass(), new \stdClass()),
array(new \stdClass(), 'string'),
array(new \stdClass(), 1),
array(new \stdClass(), 0),
array(NULL, new \stdClass()),
array('string', new \stdClass()),
array(1, new \stdClass()),
array(0, new \stdClass()),
array(array(), array()),
array(array(), NULL),
array(array(), 'string'),
array(array(), 1),
array(array(), 0),
array(NULL, array()),
array(1, array()),
array(0, array()),
array('string', array()),
array(array(), NULL),
array(NULL, NULL),
array(NULL, 'string'),
array(NULL, 1),
array(NULL, 0),
array(1, NULL),
array(0, NULL),
array('string', NULL),
);
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\EnvironmentTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Environment;
use Drupal\Tests\UnitTestCase;
/**
* Test PHP Environment helper methods.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Environment
*/
class EnvironmentTest extends UnitTestCase {
/**
* Tests \Drupal\Component\Utility\Environment::checkMemoryLimit().
*
* @dataProvider providerTestCheckMemoryLimit
* @covers ::checkMemoryLimit
*
* @param string $required
* The required memory argument for
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
* @param string $custom_memory_limit
* The custom memory limit argument for
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
* @param bool $expected
* The expected return value from
* \Drupal\Component\Utility\Environment::checkMemoryLimit().
*/
public function testCheckMemoryLimit($required, $custom_memory_limit, $expected) {
$actual = Environment::checkMemoryLimit($required, $custom_memory_limit);
$this->assertEquals($expected, $actual);
}
/**
* Provides data for testCheckMemoryLimit().
*
* @return array
* An array of arrays, each containing the arguments for
* \Drupal\Component\Utility\Environment::checkMemoryLimit():
* required and memory_limit, and the expected return value.
*/
public function providerTestCheckMemoryLimit() {
$memory_limit = ini_get('memory_limit');
$twice_avail_memory = ($memory_limit * 2) . 'MB';
return array(
// Minimal amount of memory should be available.
array('30MB', NULL, TRUE),
// Exceed a custom (unlimited) memory limit.
array($twice_avail_memory, -1, TRUE),
// Exceed a custom memory limit.
array('30MB', '16MB', FALSE),
// Available = required.
array('30MB', '30MB', TRUE),
);
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\HtmlTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Html;
use Drupal\Tests\UnitTestCase;
/**
* Tests \Drupal\Component\Utility\Html.
*
* @group Common
*
* @coversDefaultClass \Drupal\Component\Utility\Html
*/
class HtmlTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
$property->setAccessible(TRUE);
$property->setValue(NULL);
}
/**
* Tests the Html::cleanCssIdentifier() method.
*
* @param string $expected
* The expected result.
* @param string $source
* The string being transformed to an ID.
* @param array|null $filter
* (optional) An array of string replacements to use on the identifier. If
* NULL, no filter will be passed and a default will be used.
*
* @dataProvider providerTestCleanCssIdentifier
*
* @covers ::cleanCssIdentifier
*/
public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
if ($filter !== NULL) {
$this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
}
else {
$this->assertSame($expected, Html::cleanCssIdentifier($source));
}
}
/**
* Provides test data for testCleanCssIdentifier().
*
* @return array
* Test data.
*/
public function providerTestCleanCssIdentifier() {
$id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
$id2 = '¡¢£¤¥';
$id3 = 'css__identifier__with__double__underscores';
return array(
// Verify that no valid ASCII characters are stripped from the identifier.
array($id1, $id1, array()),
// Verify that valid UTF-8 characters are not stripped from the identifier.
array($id2, $id2, array()),
// Verify that invalid characters (including non-breaking space) are stripped from the identifier.
array($id3, $id3),
// Verify that double underscores are not stripped from the identifier.
array('invalididentifier', 'invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()),
// Verify that an identifier starting with a digit is replaced.
array('_cssidentifier', '1cssidentifier', array()),
// Verify that an identifier starting with a hyphen followed by a digit is
// replaced.
array('__cssidentifier', '-1cssidentifier', array()),
// Verify that an identifier starting with two hyphens is replaced.
array('__cssidentifier', '--cssidentifier', array())
);
}
/**
* Tests that Html::getClass() cleans the class name properly.
*
* @coversDefaultClass ::getClass
*/
public function testHtmlClass() {
// Verify Drupal coding standards are enforced.
$this->assertSame(Html::getClass('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.');
}
/**
* Tests the Html::getUniqueId() method.
*
* @param string $expected
* The expected result.
* @param string $source
* The string being transformed to an ID.
* @param bool $reset
* (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
*
* @dataProvider providerTestHtmlGetUniqueId
*
* @covers ::getUniqueId
*/
public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
if ($reset) {
Html::resetSeenIds();
}
$this->assertSame($expected, Html::getUniqueId($source));
}
/**
* Provides test data for testHtmlGetId().
*
* @return array
* Test data.
*/
public function providerTestHtmlGetUniqueId() {
$id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
return array(
// Verify that letters, digits, and hyphens are not stripped from the ID.
array($id, $id),
// Verify that invalid characters are stripped from the ID.
array('invalididentifier', 'invalid,./:@\\^`{Üidentifier'),
// Verify Drupal coding standards are enforced.
array('id-name-1', 'ID NAME_[1]'),
// Verify that a repeated ID is made unique.
array('test-unique-id', 'test-unique-id', TRUE),
array('test-unique-id--2', 'test-unique-id'),
array('test-unique-id--3', 'test-unique-id'),
);
}
/**
* Tests the Html::getUniqueId() method.
*
* @param string $expected
* The expected result.
* @param string $source
* The string being transformed to an ID.
*
* @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
*
* @covers ::getUniqueId
*/
public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
Html::setIsAjax(TRUE);
$id = Html::getUniqueId($source);
// Note, we truncate two hyphens at the end.
// @see \Drupal\Component\Utility\Html::getId()
if (strpos($source, '--') !== FALSE) {
$random_suffix = substr($id, strlen($source) + 1);
}
else {
$random_suffix = substr($id, strlen($source) + 2);
}
$expected = $expected . $random_suffix;
$this->assertSame($expected, $id);
}
/**
* Provides test data for testHtmlGetId().
*
* @return array
* Test data.
*/
public function providerTestHtmlGetUniqueIdWithAjaxIds() {
return array(
array('test-unique-id1--', 'test-unique-id1'),
// Note, we truncate two hyphens at the end.
// @see \Drupal\Component\Utility\Html::getId()
array('test-unique-id1---', 'test-unique-id1--'),
array('test-unique-id2--', 'test-unique-id2'),
);
}
/**
* Tests the Html::getUniqueId() method.
*
* @param string $expected
* The expected result.
* @param string $source
* The string being transformed to an ID.
*
* @dataProvider providerTestHtmlGetId
*
* @covers ::getId
*/
public function testHtmlGetId($expected, $source) {
Html::setIsAjax(FALSE);
$this->assertSame($expected, Html::getId($source));
}
/**
* Provides test data for testHtmlGetId().
*
* @return array
* Test data.
*/
public function providerTestHtmlGetId() {
$id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
return array(
// Verify that letters, digits, and hyphens are not stripped from the ID.
array($id, $id),
// Verify that invalid characters are stripped from the ID.
array('invalididentifier', 'invalid,./:@\\^`{Üidentifier'),
// Verify Drupal coding standards are enforced.
array('id-name-1', 'ID NAME_[1]'),
// Verify that a repeated ID is made unique.
array('test-unique-id', 'test-unique-id'),
array('test-unique-id', 'test-unique-id'),
);
}
/**
* Tests Html::decodeEntities().
*
* @dataProvider providerDecodeEntities
* @covers ::decodeEntities
*/
public function testDecodeEntities($text, $expected) {
$this->assertEquals($expected, Html::decodeEntities($text));
}
/**
* Data provider for testDecodeEntities().
*
* @see testCheckPlain()
*/
public function providerDecodeEntities() {
return array(
array('Drupal', 'Drupal'),
array('<script>', '<script>'),
array('&lt;script&gt;', '<script>'),
array('&#60;script&#62;', '<script>'),
array('&amp;lt;script&amp;gt;', '&lt;script&gt;'),
array('"', '"'),
array('&#34;', '"'),
array('&amp;#34;', '&#34;'),
array('&quot;', '"'),
array('&amp;quot;', '&quot;'),
array("'", "'"),
array('&#39;', "'"),
array('&amp;#39;', '&#39;'),
array('©', '©'),
array('&copy;', '©'),
array('&#169;', '©'),
array('→', '→'),
array('&#8594;', '→'),
array('➼', '➼'),
array('&#10172;', '➼'),
array('&euro;', '€'),
);
}
}

View file

@ -0,0 +1,164 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\ImageTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Image;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Utility\Image
* @group Image
*/
class ImageTest extends UnitTestCase {
/**
* Tests all control flow branches in image_dimensions_scale().
*
* @dataProvider providerTestScaleDimensions
*/
public function testScaleDimensions($input, $output) {
// Process the test dataset.
$return_value = Image::scaleDimensions($input['dimensions'], $input['width'], $input['height'], $input['upscale']);
// Check the width.
$this->assertEquals($output['dimensions']['width'], $input['dimensions']['width'], sprintf('Computed width (%s) does not equal expected width (%s)', $output['dimensions']['width'], $input['dimensions']['width']));
// Check the height.
$this->assertEquals($output['dimensions']['height'], $input['dimensions']['height'], sprintf('Computed height (%s) does not equal expected height (%s)', $output['dimensions']['height'], $input['dimensions']['height']));
// Check the return value.
$this->assertEquals($output['return_value'], $return_value, 'Incorrect return value.');
}
/**
* Provides data for image dimension scale tests.
*
* @return array
* Keyed array containing:
* - 'input' - Array which contains input for
* Image::scaleDimensions().
* - 'output' - Array which contains expected output after passing
* through Image::scaleDimensions. Also contains a boolean
* 'return_value' which should match the expected return value.
*
* @see testScaleDimensions()
*/
public function providerTestScaleDimensions() {
// Define input / output datasets to test different branch conditions.
$tests = array();
// Test branch conditions:
// - No height.
// - Upscale, don't need to upscale.
$tests[] = array(
'input' => array(
'dimensions' => array(
'width' => 1000,
'height' => 2000,
),
'width' => 200,
'height' => NULL,
'upscale' => TRUE,
),
'output' => array(
'dimensions' => array(
'width' => 200,
'height' => 400,
),
'return_value' => TRUE,
),
);
// Test branch conditions:
// - No width.
// - Don't upscale, don't need to upscale.
$tests[] = array(
'input' => array(
'dimensions' => array(
'width' => 1000,
'height' => 800,
),
'width' => NULL,
'height' => 140,
'upscale' => FALSE,
),
'output' => array(
'dimensions' => array(
'width' => 175,
'height' => 140,
),
'return_value' => TRUE,
),
);
// Test branch conditions:
// - Source aspect ratio greater than target.
// - Upscale, need to upscale.
$tests[] = array(
'input' => array(
'dimensions' => array(
'width' => 8,
'height' => 20,
),
'width' => 200,
'height' => 140,
'upscale' => TRUE,
),
'output' => array(
'dimensions' => array(
'width' => 56,
'height' => 140,
),
'return_value' => TRUE,
),
);
// Test branch condition: target aspect ratio greater than source.
$tests[] = array(
'input' => array(
'dimensions' => array(
'width' => 2000,
'height' => 800,
),
'width' => 200,
'height' => 140,
'upscale' => FALSE,
),
'output' => array(
'dimensions' => array(
'width' => 200,
'height' => 80,
),
'return_value' => TRUE,
),
);
// Test branch condition: don't upscale, need to upscale.
$tests[] = array(
'input' => array(
'dimensions' => array(
'width' => 100,
'height' => 50,
),
'width' => 200,
'height' => 140,
'upscale' => FALSE,
),
'output' => array(
'dimensions' => array(
'width' => 100,
'height' => 50,
),
'return_value' => FALSE,
),
);
return $tests;
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\NestedArrayTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\NestedArray;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Component\Utility\NestedArray
* @group Utility
*/
class NestedArrayTest extends UnitTestCase {
/**
* Form array to check.
*
* @var array
*/
protected $form;
/**
* Array of parents for the nested element.
*
* @var array
*/
protected $parents;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a form structure with a nested element.
$this->form['details']['element'] = array(
'#value' => 'Nested element',
);
// Set up parent array.
$this->parents = array('details', 'element');
}
/**
* Tests getting nested array values.
*
* @covers ::getValue
*/
public function testGetValue() {
// Verify getting a value of a nested element.
$value = NestedArray::getValue($this->form, $this->parents);
$this->assertSame('Nested element', $value['#value'], 'Nested element value found.');
// Verify changing a value of a nested element by reference.
$value = &NestedArray::getValue($this->form, $this->parents);
$value['#value'] = 'New value';
$value = NestedArray::getValue($this->form, $this->parents);
$this->assertSame('New value', $value['#value'], 'Nested element value was changed by reference.');
$this->assertSame('New value', $this->form['details']['element']['#value'], 'Nested element value was changed by reference.');
// Verify that an existing key is reported back.
$key_exists = NULL;
NestedArray::getValue($this->form, $this->parents, $key_exists);
$this->assertTrue($key_exists, 'Existing key found.');
// Verify that a non-existing key is reported back and throws no errors.
$key_exists = NULL;
$parents = $this->parents;
$parents[] = 'foo';
NestedArray::getValue($this->form, $parents, $key_exists);
$this->assertFalse($key_exists, 'Non-existing key not found.');
}
/**
* Tests setting nested array values.
*
* @covers ::setValue
*/
public function testSetValue() {
$new_value = array(
'#value' => 'New value',
'#required' => TRUE,
);
// Verify setting the value of a nested element.
NestedArray::setValue($this->form, $this->parents, $new_value);
$this->assertSame('New value', $this->form['details']['element']['#value'], 'Changed nested element value found.');
$this->assertTrue($this->form['details']['element']['#required'], 'New nested element value found.');
}
/**
* Tests force-setting values.
*
* @covers ::setValue
*/
public function testSetValueForce() {
$new_value = array(
'one',
);
$this->form['details']['non-array-parent'] = 'string';
$parents = array('details', 'non-array-parent', 'child');
NestedArray::setValue($this->form, $parents, $new_value, TRUE);
$this->assertSame($new_value, $this->form['details']['non-array-parent']['child'], 'The nested element was not forced to the new value.');
}
/**
* Tests unsetting nested array values.
*
* @covers ::unsetValue
*/
public function testUnsetValue() {
// Verify unsetting a non-existing nested element throws no errors and the
// non-existing key is properly reported.
$key_existed = NULL;
$parents = $this->parents;
$parents[] = 'foo';
NestedArray::unsetValue($this->form, $parents, $key_existed);
$this->assertTrue(isset($this->form['details']['element']['#value']), 'Outermost nested element key still exists.');
$this->assertFalse($key_existed, 'Non-existing key not found.');
// Verify unsetting a nested element.
$key_existed = NULL;
NestedArray::unsetValue($this->form, $this->parents, $key_existed);
$this->assertFalse(isset($this->form['details']['element']), 'Removed nested element not found.');
$this->assertTrue($key_existed, 'Existing key was found.');
}
/**
* Tests existence of array key.
*/
public function testKeyExists() {
// Verify that existing key is found.
$this->assertTrue(NestedArray::keyExists($this->form, $this->parents), 'Nested key found.');
// Verify that non-existing keys are not found.
$parents = $this->parents;
$parents[] = 'foo';
$this->assertFalse(NestedArray::keyExists($this->form, $parents), 'Non-existing nested key not found.');
}
/**
* Tests NestedArray::mergeDeepArray().
*
* @covers ::mergeDeep
* @covers ::mergeDeepArray
*/
public function testMergeDeepArray() {
$link_options_1 = array(
'fragment' => 'x',
'attributes' => array('title' => 'X', 'class' => array('a', 'b')),
'language' => 'en',
);
$link_options_2 = array(
'fragment' => 'y',
'attributes' => array('title' => 'Y', 'class' => array('c', 'd')),
'absolute' => TRUE,
);
$expected = array(
'fragment' => 'y',
'attributes' => array('title' => 'Y', 'class' => array('a', 'b', 'c', 'd')),
'language' => 'en',
'absolute' => TRUE,
);
$this->assertSame($expected, NestedArray::mergeDeepArray(array($link_options_1, $link_options_2)), 'NestedArray::mergeDeepArray() returned a properly merged array.');
// Test wrapper function, NestedArray::mergeDeep().
$this->assertSame($expected, NestedArray::mergeDeep($link_options_1, $link_options_2), 'NestedArray::mergeDeep() returned a properly merged array.');
}
/**
* Tests that arrays with implicit keys are appended, not merged.
*
* @covers ::mergeDeepArray
*/
public function testMergeImplicitKeys() {
$a = array(
'subkey' => array('X', 'Y'),
);
$b = array(
'subkey' => array('X'),
);
// Drupal core behavior.
$expected = array(
'subkey' => array('X', 'Y', 'X'),
);
$actual = NestedArray::mergeDeepArray(array($a, $b));
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() creates new numeric keys in the implicit sequence.');
}
/**
* Tests that even with explicit keys, values are appended, not merged.
*
* @covers ::mergeDeepArray
*/
public function testMergeExplicitKeys() {
$a = array(
'subkey' => array(
0 => 'A',
1 => 'B',
),
);
$b = array(
'subkey' => array(
0 => 'C',
1 => 'D',
),
);
// Drupal core behavior.
$expected = array(
'subkey' => array(
0 => 'A',
1 => 'B',
2 => 'C',
3 => 'D',
),
);
$actual = NestedArray::mergeDeepArray(array($a, $b));
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() creates new numeric keys in the explicit sequence.');
}
/**
* Tests that array keys values on the first array are ignored when merging.
*
* Even if the initial ordering would place the data from the second array
* before those in the first one, they are still appended, and the keys on
* the first array are deleted and regenerated.
*
* @covers ::mergeDeepArray
*/
public function testMergeOutOfSequenceKeys() {
$a = array(
'subkey' => array(
10 => 'A',
30 => 'B',
),
);
$b = array(
'subkey' => array(
20 => 'C',
0 => 'D',
),
);
// Drupal core behavior.
$expected = array(
'subkey' => array(
0 => 'A',
1 => 'B',
2 => 'C',
3 => 'D',
),
);
$actual = NestedArray::mergeDeepArray(array($a, $b));
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() ignores numeric key order when merging.');
}
}

View file

@ -0,0 +1,163 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\NumberTest.
*
* @see \Drupal\Component\Utility\Number
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Number;
use Drupal\Tests\UnitTestCase;
/**
* Tests number manipulation utilities.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Number
*/
class NumberTest extends UnitTestCase {
/**
* Tests Number::validStep() without offset.
*
* @dataProvider providerTestValidStep
* @covers ::validStep
*
* @param numeric $value
* The value argument for Number::validStep().
* @param numeric $step
* The step argument for Number::validStep().
* @param bool $expected
* Expected return value from Number::validStep().
*/
public function testValidStep($value, $step, $expected) {
$return = Number::validStep($value, $step);
$this->assertEquals($expected, $return);
}
/**
* Tests Number::validStep() with offset.
*
* @dataProvider providerTestValidStepOffset
* @covers ::validStep
*
* @param numeric $value
* The value argument for Number::validStep().
* @param numeric $step
* The step argument for Number::validStep().
* @param numeric $offset
* The offset argument for Number::validStep().
* @param bool $expected
* Expected return value from Number::validStep().
*/
public function testValidStepOffset($value, $step, $offset, $expected) {
$return = Number::validStep($value, $step, $offset);
$this->assertEquals($expected, $return);
}
/**
* Provides data for self::testNumberStep().
*
* @see \Drupal\Tests\Component\Utility\Number::testValidStep
*/
public static function providerTestValidStep() {
return array(
// Value and step equal.
array(10.3, 10.3, TRUE),
// Valid integer steps.
array(42, 21, TRUE),
array(42, 3, TRUE),
// Valid float steps.
array(42, 10.5, TRUE),
array(1, 1/3, TRUE),
array(-100, 100/7, TRUE),
array(1000, -10, TRUE),
// Valid and very small float steps.
array(1000.12345, 1e-10, TRUE),
array(3.9999999999999, 1e-13, TRUE),
// Invalid integer steps.
array(100, 30, FALSE),
array(-10, 4, FALSE),
// Invalid float steps.
array(6, 5/7, FALSE),
array(10.3, 10.25, FALSE),
// Step mismatches very close to being valid.
array(70 + 9e-7, 10 + 9e-7, FALSE),
array(1936.5, 3e-8, FALSE),
);
}
/**
* Data provider for \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset().
*
* @see \Drupal\Test\Component\Utility\NumberTest::testValidStepOffset()
*/
public static function providerTestValidStepOffset() {
return array(
// Try obvious fits.
array(11.3, 10.3, 1, TRUE),
array(100, 10, 50, TRUE),
array(-100, 90/7, -10, TRUE),
array(2/7 + 5/9, 1/7, 5/9, TRUE),
// Ensure a small offset is still invalid.
array(10.3, 10.3, 0.0001, FALSE),
array(1/5, 1/7, 1/11, FALSE),
// Try negative values and offsets.
array(1000, 10, -5, FALSE),
array(-10, 4, 0, FALSE),
array(-10, 4, -4, FALSE),
);
}
/**
* Tests the alphadecimal conversion functions.
*
* @dataProvider providerTestConversions
* @covers ::intToAlphadecimal
* @covers ::alphadecimalToInt
*
* @param int $value
* The integer value.
* @param string $expected
* The expected alphadecimal value.
*/
public function testConversions($value, $expected) {
$this->assertSame(Number::intToAlphadecimal($value), $expected);
$this->assertSame($value, Number::alphadecimalToInt($expected));
}
/**
* Data provider for testConversions().
*
* @see testConversions()
*
* @return array
* An array containing:
* - The integer value.
* - The alphadecimal value.
*/
public function providerTestConversions() {
return array(
array(0, '00'),
array(1, '01'),
array(10, '0a'),
array(20, '0k'),
array(35, '0z'),
array(36, '110'),
array(100, '12s'),
);
}
}

View file

@ -0,0 +1,172 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\RandomTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Random;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Tests\UnitTestCase;
/**
* Tests random data generation.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Random
*/
class RandomTest extends UnitTestCase {
/**
* The first random string passed to the test callback.
*
* @see \Drupal\Tests\Component\Utility\RandomTest::_RandomStringValidate()
*
* @var string
*/
protected $firstStringGenerated = '';
/**
* Tests unique random string generation.
*
* @covers ::string
*/
public function testRandomStringUniqueness() {
$strings = array();
$random = new Random();
for ($i = 0; $i <= 50; $i++) {
$str = $random->string(1, TRUE);
$this->assertFalse(isset($strings[$str]), SafeMarkup::format('Generated duplicate random string !string', array('!string' => $str)));
$strings[$str] = TRUE;
}
}
/**
* Tests unique random name generation.
*
* @covers ::name
*/
public function testRandomNamesUniqueness() {
$names = array();
$random = new Random();
for ($i = 0; $i <= 10; $i++) {
$str = $random->name(1, TRUE);
$this->assertFalse(isset($names[$str]), SafeMarkup::format('Generated duplicate random name !name', array('!name' => $str)));
$names[$str] = TRUE;
}
}
/**
* Tests infinite loop prevention whilst generating random names.
*
* @covers ::name
* @expectedException \RuntimeException
*/
public function testRandomNameException() {
// There are fewer than 100 possibilities so an exception should occur to
// prevent infinite loops.
$random = new Random();
for ($i = 0; $i <= 100; $i++) {
$str = $random->name(1, TRUE);
$names[$str] = TRUE;
}
}
/**
* Tests infinite loop prevention whilst generating random strings.
*
* @covers ::string
* @expectedException \RuntimeException
*/
public function testRandomStringException() {
// There are fewer than 100 possibilities so an exception should occur to
// prevent infinite loops.
$random = new Random();
for ($i = 0; $i <= 100; $i++) {
$str = $random->string(1, TRUE);
$names[$str] = TRUE;
}
}
/**
* Tests random name generation if uniqueness is not enforced.
*
* @covers ::name
*/
public function testRandomNameNonUnique() {
// There are fewer than 100 possibilities if we were forcing uniqueness so
// exception would occur.
$random = new Random();
for ($i = 0; $i <= 100; $i++) {
$random->name(1);
}
$this->assertTrue(TRUE, 'No exception thrown when uniqueness is not enforced.');
}
/**
* Tests random string if uniqueness is not enforced.
*
* @covers ::string
*/
public function testRandomStringNonUnique() {
// There are fewer than 100 possibilities if we were forcing uniqueness so
// exception would occur.
$random = new Random();
for ($i = 0; $i <= 100; $i++) {
$random->string(1);
}
$this->assertTrue(TRUE, 'No exception thrown when uniqueness is not enforced.');
}
/**
* Tests random object generation to ensure the expected number of properties.
*
* @covers ::object
*/
public function testRandomObject() {
// For values of 0 and 1 \Drupal\Component\Utility\Random::object() will
// have different execution paths.
$random = new Random();
for ($i = 0; $i <= 1; $i++) {
$obj = $random->object($i);
$this->assertEquals($i, count(get_object_vars($obj)), 'Generated random object has expected number of properties');
}
}
/**
* Tests random string validation callbacks.
*
* @covers ::string
*/
public function testRandomStringValidator() {
$random = new Random();
$this->firstStringGenerated = '';
$str = $random->string(1, TRUE, array($this, '_RandomStringValidate'));
$this->assertNotEquals($this->firstStringGenerated, $str);
}
/**
* Callback for random string validation.
*
* @see \Drupal\Component\Utility\Random::name()
* @see \Drupal\Tests\Component\Utility\RandomTest::testRandomStringValidator()
*
* @param string $string
* The random string to validate.
*
* @return bool
* TRUE if the random string is valid, FALSE if not.
*/
public function _RandomStringValidate($string) {
// Return FALSE for the first generated string and any string that is the
// same, as the test expects a different string to be returned.
if (empty($this->firstStringGenerated) || $string == $this->firstStringGenerated) {
$this->firstStringGenerated = $string;
return FALSE;
}
return TRUE;
}
}

View file

@ -0,0 +1,286 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\SafeMarkupTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Tests\UnitTestCase;
/**
* Tests marking strings as safe.
*
* @group Utility
* @coversDefaultClass \Drupal\Component\Utility\SafeMarkup
*/
class SafeMarkupTest extends UnitTestCase {
/**
* Tests SafeMarkup::set() and SafeMarkup::isSafe().
*
* @dataProvider providerSet
*
* @param string $text
* The text or object to provide to SafeMarkup::set().
* @param string $message
* The message to provide as output for the test.
*
* @covers ::set
*/
public function testSet($text, $message) {
$returned = SafeMarkup::set($text);
$this->assertTrue(is_string($returned), 'The return value of SafeMarkup::set() is really a string');
$this->assertEquals($returned, $text, 'The passed in value should be equal to the string value according to PHP');
$this->assertTrue(SafeMarkup::isSafe($text), $message);
$this->assertTrue(SafeMarkup::isSafe($returned), 'The return value has been marked as safe');
}
/**
* Data provider for testSet().
*
* @see testSet()
*/
public function providerSet() {
// Checks that invalid multi-byte sequences are rejected.
$tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE);
$tests[] = array("Fooÿñ", 'SafeMarkup::set() accepts valid sequence "Fooÿñ"');
$tests[] = array(new TextWrapper("Fooÿñ"), 'SafeMarkup::set() accepts valid sequence "Fooÿñ" in an object implementing __toString()');
$tests[] = array("<div>", 'SafeMarkup::set() accepts HTML');
return $tests;
}
/**
* Tests SafeMarkup::set() and SafeMarkup::isSafe() with different providers.
*
* @covers ::isSafe
*/
public function testStrategy() {
$returned = SafeMarkup::set('string0', 'html');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "html" provider is safe for default (html)');
$returned = SafeMarkup::set('string1', 'all');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "all" provider is safe for default (html)');
$returned = SafeMarkup::set('string2', 'css');
$this->assertFalse(SafeMarkup::isSafe($returned), 'String set with "css" provider is not safe for default (html)');
$returned = SafeMarkup::set('string3');
$this->assertFalse(SafeMarkup::isSafe($returned, 'all'), 'String set with "html" provider is not safe for "all"');
}
/**
* Tests SafeMarkup::isSafe() with different objects.
*
* @covers ::isSafe
*/
public function testIsSafe() {
$safe_string = $this->getMock('\Drupal\Component\Utility\SafeStringInterface');
$this->assertTrue(SafeMarkup::isSafe($safe_string));
$string_object = new SafeMarkupTestString('test');
$this->assertFalse(SafeMarkup::isSafe($string_object));
}
/**
* Tests SafeMarkup::setMultiple().
*
* @covers ::setMultiple
*/
public function testSetMultiple() {
$texts = array(
'multistring0' => array('html' => TRUE),
'multistring1' => array('all' => TRUE),
);
SafeMarkup::setMultiple($texts);
foreach ($texts as $string => $providers) {
$this->assertTrue(SafeMarkup::isSafe($string), 'The value has been marked as safe for html');
}
}
/**
* Tests SafeMarkup::setMultiple().
*
* Only TRUE may be passed in as the value.
*
* @covers ::setMultiple
*
* @expectedException \UnexpectedValueException
*/
public function testInvalidSetMultiple() {
$texts = array(
'invalidstring0' => array('html' => 1),
);
SafeMarkup::setMultiple($texts);
}
/**
* Tests SafeMarkup::checkPlain().
*
* @dataProvider providerCheckPlain
* @covers ::checkPlain
*
* @param string $text
* The text to provide to SafeMarkup::checkPlain().
* @param string $expected
* The expected output from the function.
* @param string $message
* The message to provide as output for the test.
* @param bool $ignorewarnings
* Whether or not to ignore PHP 5.3+ invalid multibyte sequence warnings.
*/
function testCheckPlain($text, $expected, $message, $ignorewarnings = FALSE) {
$result = $ignorewarnings ? @SafeMarkup::checkPlain($text) : SafeMarkup::checkPlain($text);
$this->assertEquals($expected, $result, $message);
}
/**
* Data provider for testCheckPlain().
*
* @see testCheckPlain()
*/
function providerCheckPlain() {
// Checks that invalid multi-byte sequences are rejected.
$tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE);
$tests[] = array("\xc2\"", '', 'SafeMarkup::checkPlain() rejects invalid sequence "\xc2\""', TRUE);
$tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() accepts valid sequence "Fooÿñ"');
// Checks that special characters are escaped.
$tests[] = array("<script>", '&lt;script&gt;', 'SafeMarkup::checkPlain() escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'SafeMarkup::checkPlain() escapes reserved HTML characters.');
return $tests;
}
/**
* Tests string formatting with SafeMarkup::format().
*
* @dataProvider providerFormat
* @covers ::format
*
* @param string $string
* The string to run through SafeMarkup::format().
* @param string $args
* The arguments to pass into SafeMarkup::format().
* @param string $expected
* The expected result from calling the function.
* @param string $message
* The message to display as output to the test.
* @param bool $expected_is_safe
* Whether the result is expected to be safe for HTML display.
*/
function testFormat($string, $args, $expected, $message, $expected_is_safe) {
$result = SafeMarkup::format($string, $args);
$this->assertEquals($expected, $result, $message);
$this->assertEquals($expected_is_safe, SafeMarkup::isSafe($result), 'SafeMarkup::format correctly sets the result as safe or not safe.');
}
/**
* Data provider for testFormat().
*
* @see testFormat()
*/
function providerFormat() {
$tests[] = array('Simple text', array(), 'Simple text', 'SafeMarkup::format leaves simple text alone.', TRUE);
$tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: &lt;script&gt;', 'SafeMarkup::format replaces and escapes string.', TRUE);
$tests[] = array('Escaped text: @value', array('@value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE);
$tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE);
$tests[] = array('Placeholder text: %value', array('%value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.', TRUE);
$tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'SafeMarkup::format replaces verbatim string as-is.', FALSE);
$tests[] = array('Verbatim text: !value', array('!value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'SafeMarkup::format replaces verbatim string as-is.', TRUE);
return $tests;
}
/**
* Tests SafeMarkup::placeholder().
*
* @covers ::placeholder
*/
function testPlaceholder() {
$this->assertEquals('<em class="placeholder">Some text</em>', SafeMarkup::placeholder('Some text'));
}
/**
* Tests SafeMarkup::replace().
*
* @dataProvider providerReplace
* @covers ::replace
*/
public function testReplace($search, $replace, $subject, $expected, $is_safe) {
$result = SafeMarkup::replace($search, $replace, $subject);
$this->assertEquals($expected, $result);
$this->assertEquals($is_safe, SafeMarkup::isSafe($result));
}
/**
* Data provider for testReplace().
*
* @see testReplace()
*/
public function providerReplace() {
$tests = [];
// Subject unsafe.
$tests[] = [
'<placeholder>',
SafeMarkup::set('foo'),
'<placeholder>bazqux',
'foobazqux',
FALSE,
];
// All safe.
$tests[] = [
'<placeholder>',
SafeMarkup::set('foo'),
SafeMarkup::set('<placeholder>barbaz'),
'foobarbaz',
TRUE,
];
// Safe subject, but should result in unsafe string because replacement is
// unsafe.
$tests[] = [
'<placeholder>',
'fubar',
SafeMarkup::set('<placeholder>barbaz'),
'fubarbarbaz',
FALSE,
];
// Array with all safe.
$tests[] = [
['<placeholder1>', '<placeholder2>', '<placeholder3>'],
[SafeMarkup::set('foo'), SafeMarkup::set('bar'), SafeMarkup::set('baz')],
SafeMarkup::set('<placeholder1><placeholder2><placeholder3>'),
'foobarbaz',
TRUE,
];
// Array with unsafe replacement.
$tests[] = [
['<placeholder1>', '<placeholder2>', '<placeholder3>',],
[SafeMarkup::set('bar'), SafeMarkup::set('baz'), 'qux'],
SafeMarkup::set('<placeholder1><placeholder2><placeholder3>'),
'barbazqux',
FALSE,
];
return $tests;
}
}
class SafeMarkupTestString {
protected $string;
public function __construct($string) {
$this->string = $string;
}
public function __toString() {
return $this->string;
}
}

View file

@ -0,0 +1,328 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\SortArrayTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Utility\SortArray;
/**
* Tests the SortArray component.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\SortArray
*/
class SortArrayTest extends UnitTestCase {
/**
* Tests SortArray::sortByWeightElement() input against expected output.
*
* @dataProvider providerSortByWeightElement
* @covers ::sortByWeightElement
* @covers ::sortByKeyInt
*
* @param array $a
* The first input array for the SortArray::sortByWeightElement() method.
* @param array $b
* The second input array for the SortArray::sortByWeightElement().
* @param integer $expected
* The expected output from calling the method.
*/
public function testSortByWeightElement($a, $b, $expected) {
$result = SortArray::sortByWeightElement($a, $b);
$this->assertBothNegativePositiveOrZero($expected, $result);
}
/**
* Data provider for SortArray::sortByWeightElement().
*
* @return array
* An array of tests, matching the parameter inputs for
* testSortByWeightElement.
*
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByWeightElement()
*/
public function providerSortByWeightElement() {
$tests = array();
// Weights set and equal.
$tests[] = array(
array('weight' => 1),
array('weight' => 1),
0
);
// Weights set and $a is less (lighter) than $b.
$tests[] = array(
array('weight' => 1),
array('weight' => 2),
-1
);
// Weights set and $a is greater (heavier) than $b.
$tests[] = array(
array('weight' => 2),
array('weight' => 1),
1
);
// Weights not set.
$tests[] = array(
array(),
array(),
0
);
// Weights for $b not set.
$tests[] = array(
array('weight' => 1),
array(),
1
);
// Weights for $a not set.
$tests[] = array(
array(),
array('weight' => 1),
-1
);
return $tests;
}
/**
* Tests SortArray::sortByWeightProperty() input against expected output.
*
* @dataProvider providerSortByWeightProperty
* @covers ::sortByWeightProperty
* @covers ::sortByKeyInt
*
* @param array $a
* The first input array for the SortArray::sortByWeightProperty() method.
* @param array $b
* The second input array for the SortArray::sortByWeightProperty().
* @param integer $expected
* The expected output from calling the method.
*/
public function testSortByWeightProperty($a, $b, $expected) {
$result = SortArray::sortByWeightProperty($a, $b);
$this->assertBothNegativePositiveOrZero($expected, $result);
}
/**
* Data provider for SortArray::sortByWeightProperty().
*
* @return array
* An array of tests, matching the parameter inputs for
* testSortByWeightProperty.
*
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByWeightProperty()
*/
public function providerSortByWeightProperty() {
$tests = array();
// Weights set and equal.
$tests[] = array(
array('#weight' => 1),
array('#weight' => 1),
0
);
// Weights set and $a is less (lighter) than $b.
$tests[] = array(
array('#weight' => 1),
array('#weight' => 2),
-1
);
// Weights set and $a is greater (heavier) than $b.
$tests[] = array(
array('#weight' => 2),
array('#weight' => 1),
1
);
// Weights not set.
$tests[] = array(
array(),
array(),
0
);
// Weights for $b not set.
$tests[] = array(
array('#weight' => 1),
array(),
1
);
// Weights for $a not set.
$tests[] = array(
array(),
array('#weight' => 1),
-1
);
return $tests;
}
/**
* Tests SortArray::sortByTitleElement() input against expected output.
*
* @dataProvider providerSortByTitleElement
* @covers ::sortByTitleElement
* @covers ::sortByKeyString
*
* @param array $a
* The first input item for comparison.
* @param array $b
* The second item for comparison.
* @param integer $expected
* The expected output from calling the method.
*/
public function testSortByTitleElement($a, $b, $expected) {
$result = SortArray::sortByTitleElement($a, $b);
$this->assertBothNegativePositiveOrZero($expected, $result);
}
/**
* Data provider for SortArray::sortByTitleElement().
*
* @return array
* An array of tests, matching the parameter inputs for
* testSortByTitleElement.
*
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByTitleElement()
*/
public function providerSortByTitleElement() {
$tests = array();
// Titles set and equal.
$tests[] = array(
array('title' => 'test'),
array('title' => 'test'),
0
);
// Title $a not set.
$tests[] = array(
array(),
array('title' => 'test'),
-4
);
// Title $b not set.
$tests[] = array(
array('title' => 'test'),
array(),
4
);
// Titles set but not equal.
$tests[] = array(
array('title' => 'test'),
array('title' => 'testing'),
-1
);
// Titles set but not equal.
$tests[] = array(
array('title' => 'testing'),
array('title' => 'test'),
1
);
return $tests;
}
/**
* Tests SortArray::sortByTitleProperty() input against expected output.
*
* @dataProvider providerSortByTitleProperty
* @covers ::sortByTitleProperty
* @covers ::sortByKeyString
*
* @param array $a
* The first input item for comparison.
* @param array $b
* The second item for comparison.
* @param integer $expected
* The expected output from calling the method.
*/
public function testSortByTitleProperty($a, $b, $expected) {
$result = SortArray::sortByTitleProperty($a, $b);
$this->assertBothNegativePositiveOrZero($expected, $result);
}
/**
* Data provider for SortArray::sortByTitleProperty().
*
* @return array
* An array of tests, matching the parameter inputs for
* testSortByTitleProperty.
*
* @see \Drupal\Tests\Component\Utility\SortArrayTest::testSortByTitleProperty()
*/
public function providerSortByTitleProperty() {
$tests = array();
// Titles set and equal.
$tests[] = array(
array('#title' => 'test'),
array('#title' => 'test'),
0
);
// Title $a not set.
$tests[] = array(
array(),
array('#title' => 'test'),
-4
);
// Title $b not set.
$tests[] = array(
array('#title' => 'test'),
array(),
4
);
// Titles set but not equal.
$tests[] = array(
array('#title' => 'test'),
array('#title' => 'testing'),
-1
);
// Titles set but not equal.
$tests[] = array(
array('#title' => 'testing'),
array('#title' => 'test'),
1
);
return $tests;
}
/**
* Asserts that numbers are either both negative, both positive or both zero.
*
* The exact values returned by comparison functions differ between PHP
* versions and are considered an "implementation detail".
*
* @param int $expected
* Expected comparison function return value.
* @param int $result
* Actual comparison function return value.
*/
protected function assertBothNegativePositiveOrZero($expected, $result) {
$this->assertTrue(is_numeric($expected) && is_numeric($result), 'Parameters are numeric.');
$this->assertTrue(($expected < 0 && $result < 0) || ($expected > 0 && $result > 0) || ($expected === 0 && $result === 0), 'Numbers are either both negative, both positive or both zero.');
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\TextWrapper.
*/
namespace Drupal\Tests\Component\Utility;
/**
* Used by SafeMarkupTest to test that a class with a __toString() method works.
*/
class TextWrapper {
/**
* The text value.
*
* @var string
*/
protected $text = '';
/**
* Constructs a \Drupal\Tests\Component\Utility\TextWrapper
*
* @param string $text
*/
public function __construct($text) {
$this->text = $text;
}
/**
* Magic method
*
* @return string
*/
public function __toString() {
return $this->text;
}
}

View file

@ -0,0 +1,73 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\TimerTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Utility\Timer;
/**
* Tests the Timer system.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Timer
*/
class TimerTest extends UnitTestCase {
/**
* Tests Timer::read() time accumulation accuracy across multiple restarts.
*
* @covers ::start
* @covers ::stop
* @covers ::read
*/
public function testTimer() {
Timer::start('test');
usleep(5000);
$value = Timer::read('test');
usleep(5000);
$value2 = Timer::read('test');
usleep(5000);
$value3 = Timer::read('test');
usleep(5000);
$value4 = Timer::read('test');
// Although we sleep for 5 milliseconds, we should test that at least 4 ms
// have past because usleep() is not reliable on Windows. See
// http://php.net/manual/en/function.usleep.php for more information. The
// purpose of the test to validate that the Timer class can measure elapsed
// time not the granularity of usleep() on a particular OS.
$this->assertGreaterThanOrEqual(4, $value, 'Timer failed to measure at least 4 milliseconds of sleeping while running.');
$this->assertGreaterThanOrEqual($value + 4, $value2, 'Timer failed to measure at least 8 milliseconds of sleeping while running.');
$this->assertGreaterThanOrEqual($value2 + 4, $value3, 'Timer failed to measure at least 12 milliseconds of sleeping while running.');
$this->assertGreaterThanOrEqual($value3 + 4, $value4, 'Timer failed to measure at least 16 milliseconds of sleeping while running.');
// Stop the timer.
$value5 = Timer::stop('test');
$this->assertGreaterThanOrEqual($value4, $value5['time'], 'Timer measured after stopping was not greater than last measurement.');
// Read again.
$value6 = Timer::read('test');
$this->assertEquals($value5['time'], $value6, 'Timer measured after stopping was not equal to the stopped time.');
// Restart.
Timer::start('test');
usleep(5000);
$value7 = Timer::read('test');
$this->assertGreaterThanOrEqual($value6 + 4, $value7, 'Timer failed to measure at least 16 milliseconds of sleeping while running.');
// Stop again.
$value8 = Timer::stop('test');
$value9 = Timer::read('test');
$this->assertEquals($value8['time'], $value9, 'Timer measured after stopping not equal to stop time.');
}
}

View file

@ -0,0 +1,532 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\UnicodeTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Utility\Unicode;
/**
* Test unicode handling features implemented in Unicode component.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Unicode
*/
class UnicodeTest extends UnitTestCase {
/**
* {@inheritdoc}
*
* @covers ::check
*/
public function setUp() {
// Initialize unicode component.
Unicode::check();
}
/**
* Getting and settings the multibyte environment status.
*
* @dataProvider providerTestStatus
* @covers ::getStatus
* @covers ::setStatus
*/
public function testStatus($value, $expected, $invalid = FALSE) {
if ($invalid) {
$this->setExpectedException('InvalidArgumentException');
}
Unicode::setStatus($value);
$this->assertEquals($expected, Unicode::getStatus());
}
/**
* Data provider for testStatus().
*
* @see testStatus()
*
* @return array
* An array containing:
* - The status value to set.
* - The status value to expect after setting the new value.
* - (optional) Boolean indicating invalid status. Defaults to FALSE.
*/
public function providerTestStatus() {
return array(
array(Unicode::STATUS_SINGLEBYTE, Unicode::STATUS_SINGLEBYTE),
array(rand(10, 100), Unicode::STATUS_SINGLEBYTE, TRUE),
array(rand(10, 100), Unicode::STATUS_SINGLEBYTE, TRUE),
array(Unicode::STATUS_MULTIBYTE, Unicode::STATUS_MULTIBYTE),
array(rand(10, 100), Unicode::STATUS_MULTIBYTE, TRUE),
array(Unicode::STATUS_ERROR, Unicode::STATUS_ERROR),
array(Unicode::STATUS_MULTIBYTE, Unicode::STATUS_MULTIBYTE),
);
}
/**
* Tests multibyte encoding and decoding.
*
* @dataProvider providerTestMimeHeader
* @covers ::mimeHeaderEncode
* @covers ::mimeHeaderDecode
*/
public function testMimeHeader($value, $encoded) {
$this->assertEquals($encoded, Unicode::mimeHeaderEncode($value));
$this->assertEquals($value, Unicode::mimeHeaderDecode($encoded));
}
/**
* Data provider for testMimeHeader().
*
* @see testMimeHeader()
*
* @return array
* An array containing a string and its encoded value.
*/
public function providerTestMimeHeader() {
return array(
array('tést.txt', '=?UTF-8?B?dMOpc3QudHh0?='),
// Simple ASCII characters.
array('ASCII', 'ASCII'),
);
}
/**
* Tests multibyte strtolower.
*
* @dataProvider providerStrtolower
* @covers ::strtolower
* @covers ::caseFlip
*/
public function testStrtolower($text, $expected, $multibyte = FALSE) {
$status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
Unicode::setStatus($status);
$this->assertEquals($expected, Unicode::strtolower($text));
}
/**
* Data provider for testStrtolower().
*
* @see testStrtolower()
*
* @return array
* An array containing a string, its lowercase version and whether it should
* be processed as multibyte.
*/
public function providerStrtolower() {
$cases = array(
array('tHe QUIcK bRoWn', 'the quick brown'),
array('FrançAIS is ÜBER-åwesome', 'français is über-åwesome'),
);
foreach ($cases as $case) {
// Test the same string both in multibyte and singlebyte conditions.
array_push($case, TRUE);
$cases[] = $case;
}
// Add a multibyte string.
$cases[] = array('ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', 'αβγδεζηθικλμνξοσὠ', TRUE);
return $cases;
}
/**
* Tests multibyte strtoupper.
*
* @dataProvider providerStrtoupper
* @covers ::strtoupper
* @covers ::caseFlip
*/
public function testStrtoupper($text, $expected, $multibyte = FALSE) {
$status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
Unicode::setStatus($status);
$this->assertEquals($expected, Unicode::strtoupper($text));
}
/**
* Data provider for testStrtoupper().
*
* @see testStrtoupper()
*
* @return array
* An array containing a string, its uppercase version and whether it should
* be processed as multibyte.
*/
public function providerStrtoupper() {
$cases = array(
array('tHe QUIcK bRoWn', 'THE QUICK BROWN'),
array('FrançAIS is ÜBER-åwesome', 'FRANÇAIS IS ÜBER-ÅWESOME'),
);
foreach ($cases as $case) {
// Test the same string both in multibyte and singlebyte conditions.
array_push($case, TRUE);
$cases[] = $case;
}
// Add a multibyte string.
$cases[] = array('αβγδεζηθικλμνξοσὠ', 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', TRUE);
return $cases;
}
/**
* Tests multibyte ucfirst.
*
* @dataProvider providerUcfirst
* @covers ::ucfirst
*/
public function testUcfirst($text, $expected) {
$this->assertEquals($expected, Unicode::ucfirst($text));
}
/**
* Data provider for testUcfirst().
*
* @see testUcfirst()
*
* @return array
* An array containing a string and its uppercase first version.
*/
public function providerUcfirst() {
return array(
array('tHe QUIcK bRoWn', 'THe QUIcK bRoWn'),
array('françAIS', 'FrançAIS'),
array('über', 'Über'),
array('åwesome', 'Åwesome'),
// A multibyte string.
array('σion', 'Σion'),
);
}
/**
* Tests multibyte lcfirst.
*
* @dataProvider providerLcfirst
* @covers ::lcfirst
*/
public function testLcfirst($text, $expected, $multibyte = FALSE) {
$status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
Unicode::setStatus($status);
$this->assertEquals($expected, Unicode::lcfirst($text));
}
/**
* Data provider for testLcfirst().
*
* @see testLcfirst()
*
* @return array
* An array containing a string, its lowercase version and whether it should
* be processed as multibyte.
*/
public function providerLcfirst() {
return array(
array('tHe QUIcK bRoWn', 'tHe QUIcK bRoWn'),
array('FrançAIS is ÜBER-åwesome', 'françAIS is ÜBER-åwesome'),
array('Über', 'über'),
array('Åwesome', 'åwesome'),
// Add a multibyte string.
array('ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', 'αΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', TRUE),
);
}
/**
* Tests multibyte ucwords.
*
* @dataProvider providerUcwords
* @covers ::ucwords
*/
public function testUcwords($text, $expected, $multibyte = FALSE) {
$status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
Unicode::setStatus($status);
$this->assertEquals($expected, Unicode::ucwords($text));
}
/**
* Data provider for testUcwords().
*
* @see testUcwords()
*
* @return array
* An array containing a string, its capitalized version and whether it should
* be processed as multibyte.
*/
public function providerUcwords() {
return array(
array('tHe QUIcK bRoWn', 'THe QUIcK BRoWn'),
array('françAIS', 'FrançAIS'),
array('über', 'Über'),
array('åwesome', 'Åwesome'),
// Make sure we don't mangle extra spaces.
array('frànçAIS is über-åwesome', 'FrànçAIS Is Über-Åwesome'),
// Add a multibyte string.
array('σion', 'Σion', TRUE),
);
}
/**
* Tests multibyte strlen.
*
* @dataProvider providerStrlen
* @covers ::strlen
*/
public function testStrlen($text, $expected) {
// Run through multibyte code path.
Unicode::setStatus(Unicode::STATUS_MULTIBYTE);
$this->assertEquals($expected, Unicode::strlen($text));
// Run through singlebyte code path.
Unicode::setStatus(Unicode::STATUS_SINGLEBYTE);
$this->assertEquals($expected, Unicode::strlen($text));
}
/**
* Data provider for testStrlen().
*
* @see testStrlen()
*
* @return array
* An array containing a string and its length.
*/
public function providerStrlen() {
return array(
array('tHe QUIcK bRoWn', 15),
array('ÜBER-åwesome', 12),
);
}
/**
* Tests multibyte substr.
*
* @dataProvider providerSubstr
* @covers ::substr
*/
public function testSubstr($text, $start, $length, $expected) {
// Run through multibyte code path.
Unicode::setStatus(Unicode::STATUS_MULTIBYTE);
$this->assertEquals($expected, Unicode::substr($text, $start, $length));
// Run through singlebyte code path.
Unicode::setStatus(Unicode::STATUS_SINGLEBYTE);
$this->assertEquals($expected, Unicode::substr($text, $start, $length));
}
/**
* Data provider for testSubstr().
*
* @see testSubstr()
*
* @return array
* An array containing:
* - The string to test.
* - The start number to be processed by substr.
* - The length number to be processed by substr.
* - The expected string result.
*/
public function providerSubstr() {
return array(
array('frànçAIS is über-åwesome', 0, NULL, 'frànçAIS is über-åwesome'),
array('frànçAIS is über-åwesome', 0, 0, ''),
array('frànçAIS is über-åwesome', 0, 1, 'f'),
array('frànçAIS is über-åwesome', 0, 8, 'frànçAIS'),
array('frànçAIS is über-åwesome', 0, 23, 'frànçAIS is über-åwesom'),
array('frànçAIS is über-åwesome', 0, 24, 'frànçAIS is über-åwesome'),
array('frànçAIS is über-åwesome', 0, 25, 'frànçAIS is über-åwesome'),
array('frànçAIS is über-åwesome', 0, 100, 'frànçAIS is über-åwesome'),
array('frànçAIS is über-åwesome', 4, 4, 'çAIS'),
array('frànçAIS is über-åwesome', 1, 0, ''),
array('frànçAIS is über-åwesome', 100, 0, ''),
array('frànçAIS is über-åwesome', -4, 2, 'so'),
array('frànçAIS is über-åwesome', -4, 3, 'som'),
array('frànçAIS is über-åwesome', -4, 4, 'some'),
array('frànçAIS is über-åwesome', -4, 5, 'some'),
array('frànçAIS is über-åwesome', -7, 10, 'åwesome'),
array('frànçAIS is über-åwesome', 5, -10, 'AIS is üb'),
array('frànçAIS is über-åwesome', 0, -10, 'frànçAIS is üb'),
array('frànçAIS is über-åwesome', 0, -1, 'frànçAIS is über-åwesom'),
array('frànçAIS is über-åwesome', -7, -2, 'åweso'),
array('frànçAIS is über-åwesome', -7, -6, 'å'),
array('frànçAIS is über-åwesome', -7, -7, ''),
array('frànçAIS is über-åwesome', -7, -8, ''),
array('...', 0, 2, '..'),
array('以呂波耳・ほへとち。リヌルヲ。', 1, 3, '呂波耳'),
);
}
/**
* Tests multibyte truncate.
*
* @dataProvider providerTruncate
* @covers ::truncate
*/
public function testTruncate($text, $max_length, $expected, $wordsafe = FALSE, $add_ellipsis = FALSE) {
$this->assertEquals($expected, Unicode::truncate($text, $max_length, $wordsafe, $add_ellipsis));
}
/**
* Data provider for testTruncate().
*
* @see testTruncate()
*
* @return array
* An array containing:
* - The string to test.
* - The max length to truncate this string to.
* - The expected string result.
* - (optional) Boolean for the $wordsafe flag. Defaults to FALSE.
* - (optional) Boolean for the $add_ellipsis flag. Defaults to FALSE.
*/
public function providerTruncate() {
return array(
array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome'),
array('frànçAIS is über-åwesome', 23, 'frànçAIS is über-åwesom'),
array('frànçAIS is über-åwesome', 17, 'frànçAIS is über-'),
array('以呂波耳・ほへとち。リヌルヲ。', 6, '以呂波耳・ほ'),
array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome', FALSE, TRUE),
array('frànçAIS is über-åwesome', 23, 'frànçAIS is über-åweso…', FALSE, TRUE),
array('frànçAIS is über-åwesome', 17, 'frànçAIS is über…', FALSE, TRUE),
array('123', 1, '…', TRUE, TRUE),
array('123', 2, '1…', TRUE, TRUE),
array('123', 3, '123', TRUE, TRUE),
array('1234', 3, '12…', TRUE, TRUE),
array('1234567890', 10, '1234567890', TRUE, TRUE),
array('12345678901', 10, '123456789…', TRUE, TRUE),
array('12345678901', 11, '12345678901', TRUE, TRUE),
array('123456789012', 11, '1234567890…', TRUE, TRUE),
array('12345 7890', 10, '12345 7890', TRUE, TRUE),
array('12345 7890', 9, '12345…', TRUE, TRUE),
array('123 567 90', 10, '123 567 90', TRUE, TRUE),
array('123 567 901', 10, '123 567…', TRUE, TRUE),
array('Stop. Hammertime.', 17, 'Stop. Hammertime.', TRUE, TRUE),
array('Stop. Hammertime.', 16, 'Stop…', TRUE, TRUE),
array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome', TRUE, TRUE),
array('frànçAIS is über-åwesome', 23, 'frànçAIS is über…', TRUE, TRUE),
array('frànçAIS is über-åwesome', 17, 'frànçAIS is über…', TRUE, TRUE),
array('¿Dónde está el niño?', 20, '¿Dónde está el niño?', TRUE, TRUE),
array('¿Dónde está el niño?', 19, '¿Dónde está el…', TRUE, TRUE),
array('¿Dónde está el niño?', 13, '¿Dónde está…', TRUE, TRUE),
array('¿Dónde está el niño?', 10, '¿Dónde…', TRUE, TRUE),
array('Help! Help! Help!', 17, 'Help! Help! Help!', TRUE, TRUE),
array('Help! Help! Help!', 16, 'Help! Help!…', TRUE, TRUE),
array('Help! Help! Help!', 15, 'Help! Help!…', TRUE, TRUE),
array('Help! Help! Help!', 14, 'Help! Help!…', TRUE, TRUE),
array('Help! Help! Help!', 13, 'Help! Help!…', TRUE, TRUE),
array('Help! Help! Help!', 12, 'Help! Help!…', TRUE, TRUE),
array('Help! Help! Help!', 11, 'Help! Help…', TRUE, TRUE),
array('Help! Help! Help!', 10, 'Help!…', TRUE, TRUE),
array('Help! Help! Help!', 9, 'Help!…', TRUE, TRUE),
array('Help! Help! Help!', 8, 'Help!…', TRUE, TRUE),
array('Help! Help! Help!', 7, 'Help!…', TRUE, TRUE),
array('Help! Help! Help!', 6, 'Help!…', TRUE, TRUE),
array('Help! Help! Help!', 5, 'Help…', TRUE, TRUE),
array('Help! Help! Help!', 4, 'Hel…', TRUE, TRUE),
array('Help! Help! Help!', 3, 'He…', TRUE, TRUE),
array('Help! Help! Help!', 2, 'H…', TRUE, TRUE),
);
}
/**
* Tests multibyte truncate bytes.
*
* @dataProvider providerTestTruncateBytes
* @covers ::truncateBytes
*
* @param string $text
* The string to truncate.
* @param int $max_length
* The upper limit on the returned string length.
* @param string $expected
* The expected return from Unicode::truncateBytes().
*/
public function testTruncateBytes($text, $max_length, $expected) {
$this->assertEquals($expected, Unicode::truncateBytes($text, $max_length), 'The string was not correctly truncated.');
}
/**
* Provides data for self::testTruncateBytes().
*
* @return array
* An array of arrays, each containing the parameters to
* self::testTruncateBytes().
*/
public function providerTestTruncateBytes() {
return array(
// String shorter than max length.
array('Short string', 42, 'Short string'),
// Simple string longer than max length.
array('Longer string than previous.', 10, 'Longer str'),
// Unicode.
array('以呂波耳・ほへとち。リヌルヲ。', 10, '以呂波'),
);
}
/**
* Tests UTF-8 validation.
*
* @dataProvider providerTestValidateUtf8
* @covers ::validateUtf8
*
* @param string $text
* The text to validate.
* @param bool $expected
* The expected return value from Unicode::validateUtf8().
* @param string $message
* The message to display on failure.
*/
public function testValidateUtf8($text, $expected, $message) {
$this->assertEquals($expected, Unicode::validateUtf8($text), $message);
}
/**
* Provides data for self::testValidateUtf8().
*
* @return array
* An array of arrays, each containing the parameters for
* self::testValidateUtf8().
*
* Invalid UTF-8 examples sourced from http://stackoverflow.com/a/11709412/109119.
*/
public function providerTestValidateUtf8() {
return array(
// Empty string.
array('', TRUE, 'An empty string did not validate.'),
// Simple text string.
array('Simple text.', TRUE, 'A simple ASCII text string did not validate.'),
// Invalid UTF-8, overlong 5 byte encoding.
array(chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80), FALSE, 'Invalid UTF-8 was validated.'),
// High code-point without trailing characters.
array(chr(0xD0) . chr(0x01), FALSE, 'Invalid UTF-8 was validated.'),
);
}
/**
* Tests UTF-8 conversion.
*
* @dataProvider providerTestConvertToUtf8
* @covers ::convertToUtf8
*
* @param string $data
* The data to be converted.
* @param string $encoding
* The encoding the data is in.
* @param string|bool $expected
* The expected result.
*/
public function testConvertToUtf8($data, $encoding, $expected) {
$this->assertEquals($expected, Unicode::convertToUtf8($data, $encoding));
}
/**
* Provides data to self::testConvertToUtf8().
*
* @return array
* An array of arrays, each containing the parameters to
* self::testConvertUtf8(). }
*/
public function providerTestConvertToUtf8() {
return array(
array(chr(0x97), 'Windows-1252', '—'),
array(chr(0x99), 'Windows-1252', '™'),
array(chr(0x80), 'Windows-1252', '€'),
);
}
}

View file

@ -0,0 +1,567 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\UrlHelperTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Tests\UnitTestCase;
/**
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\UrlHelper
*/
class UrlHelperTest extends UnitTestCase {
/**
* Provides test data for testBuildQuery().
*
* @return array
*/
public function providerTestBuildQuery() {
return array(
array(array('a' => ' &#//+%20@۞'), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'),
array(array(' &#//+%20@۞' => 'a'), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'),
array(array('a' => '1', 'b' => '2', 'c' => '3'), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'),
array(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo'), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'),
array(array('foo' => NULL), 'foo', 'Simple parameters are properly added.'),
);
}
/**
* Tests query building.
*
* @dataProvider providerTestBuildQuery
* @covers ::buildQuery
*
* @param array $query
* The array of query parameters.
* @param string $expected
* The expected query string.
* @param string $message
* The assertion message.
*/
public function testBuildQuery($query, $expected, $message) {
$this->assertEquals(UrlHelper::buildQuery($query), $expected, $message);
}
/**
* Data provider for testValidAbsolute().
*
* @return array
*/
public function providerTestValidAbsoluteData() {
$urls = array(
'example.com',
'www.example.com',
'ex-ample.com',
'3xampl3.com',
'example.com/parenthesis',
'example.com/index.html#pagetop',
'example.com:8080',
'subdomain.example.com',
'example.com/index.php/node',
'example.com/index.php/node?param=false',
'user@www.example.com',
'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
'127.0.0.1',
'example.org?',
'john%20doe:secret:foo@example.org/',
'example.org/~,$\'*;',
'caf%C3%A9.example.org',
'[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
);
return $this->dataEnhanceWithScheme($urls);
}
/**
* Tests valid absolute URLs.
*
* @dataProvider providerTestValidAbsoluteData
* @covers ::isValid
*
* @param string $url
* The url to test.
* @param string $scheme
* The scheme to test.
*/
public function testValidAbsolute($url, $scheme) {
$test_url = $scheme . '://' . $url;
$valid_url = UrlHelper::isValid($test_url, TRUE);
$this->assertTrue($valid_url, SafeMarkup::format('@url is a valid URL.', array('@url' => $test_url)));
}
/**
* Provides data for testInvalidAbsolute().
*
* @return array
*/
public function providerTestInvalidAbsolute() {
$data = array(
'',
'ex!ample.com',
'ex%ample.com',
);
return $this->dataEnhanceWithScheme($data);
}
/**
* Tests invalid absolute URLs.
*
* @dataProvider providerTestInvalidAbsolute
* @covers ::isValid
*
* @param string $url
* The url to test.
* @param string $scheme
* The scheme to test.
*/
public function testInvalidAbsolute($url, $scheme) {
$test_url = $scheme . '://' . $url;
$valid_url = UrlHelper::isValid($test_url, TRUE);
$this->assertFalse($valid_url, SafeMarkup::format('@url is NOT a valid URL.', array('@url' => $test_url)));
}
/**
* Provides data for testValidRelative().
*
* @return array
*/
public function providerTestValidRelativeData() {
$data = array(
'paren(the)sis',
'index.html#pagetop',
'index.php/node',
'index.php/node?param=false',
'login.php?do=login&style=%23#pagetop',
);
return $this->dataEnhanceWithPrefix($data);
}
/**
* Tests valid relative URLs.
*
* @dataProvider providerTestValidRelativeData
* @covers ::isValid
*
* @param string $url
* The url to test.
* @param string $prefix
* The prefix to test.
*/
public function testValidRelative($url, $prefix) {
$test_url = $prefix . $url;
$valid_url = UrlHelper::isValid($test_url);
$this->assertTrue($valid_url, SafeMarkup::format('@url is a valid URL.', array('@url' => $test_url)));
}
/**
* Provides data for testInvalidRelative().
*
* @return array
*/
public function providerTestInvalidRelativeData() {
$data = array(
'ex^mple',
'example<>',
'ex%ample',
);
return $this->dataEnhanceWithPrefix($data);
}
/**
* Tests invalid relative URLs.
*
* @dataProvider providerTestInvalidRelativeData
* @covers ::isValid
*
* @param string $url
* The url to test.
* @param string $prefix
* The prefix to test.
*/
public function testInvalidRelative($url, $prefix) {
$test_url = $prefix . $url;
$valid_url = UrlHelper::isValid($test_url);
$this->assertFalse($valid_url, SafeMarkup::format('@url is NOT a valid URL.', array('@url' => $test_url)));
}
/**
* Tests query filtering.
*
* @dataProvider providerTestFilterQueryParameters
* @covers ::filterQueryParameters
*
* @param array $query
* The array of query parameters.
* @param array $exclude
* A list of $query array keys to remove. Use "parent[child]" to exclude
* nested items.
* @param array $expected
* An array containing query parameters.
*/
public function testFilterQueryParameters($query, $exclude, $expected) {
$filtered = UrlHelper::filterQueryParameters($query, $exclude);
$this->assertEquals($expected, $filtered, 'The query was not properly filtered.');
}
/**
* Provides data to self::testFilterQueryParameters().
*
* @return array
*/
public static function providerTestFilterQueryParameters() {
return array(
// Test without an exclude filter.
array(
'query' => array('a' => array('b' => 'c')),
'exclude' => array(),
'expected' => array('a' => array('b' => 'c')),
),
// Exclude the 'b' element.
array(
'query' => array('a' => array('b' => 'c', 'd' => 'e')),
'exclude' => array('a[b]'),
'expected' => array('a' => array('d' => 'e')),
),
);
}
/**
* Tests url parsing.
*
* @dataProvider providerTestParse
* @covers ::parse
*
* @param string $url
* URL to test.
* @param array $expected
* Associative array with expected parameters.
*/
public function testParse($url, $expected) {
$parsed = UrlHelper::parse($url);
$this->assertEquals($expected, $parsed, 'The url was not properly parsed.');
}
/**
* Provides data for self::testParse().
*
* @return array
*/
public static function providerTestParse() {
return array(
array(
'http://www.example.com/my/path',
array(
'path' => 'http://www.example.com/my/path',
'query' => array(),
'fragment' => '',
),
),
array(
'http://www.example.com/my/path?destination=home#footer',
array(
'path' => 'http://www.example.com/my/path',
'query' => array(
'destination' => 'home',
),
'fragment' => 'footer',
),
),
array(
'http://',
array(
'path' => '',
'query' => array(),
'fragment' => '',
),
),
array(
'https://',
array(
'path' => '',
'query' => array(),
'fragment' => '',
),
),
array(
'/my/path?destination=home#footer',
array(
'path' => '/my/path',
'query' => array(
'destination' => 'home',
),
'fragment' => 'footer',
),
),
);
}
/**
* Tests path encoding.
*
* @dataProvider providerTestEncodePath
* @covers ::encodePath
*
* @param string $path
* A path to encode.
* @param string $expected
* The expected encoded path.
*/
public function testEncodePath($path, $expected) {
$encoded = UrlHelper::encodePath($path);
$this->assertEquals($expected, $encoded);
}
/**
* Provides data for self::testEncodePath().
*
* @return array
*/
public static function providerTestEncodePath() {
return array(
array('unencoded path with spaces', 'unencoded%20path%20with%20spaces'),
array('slashes/should/be/preserved', 'slashes/should/be/preserved'),
);
}
/**
* Tests external versus internal paths.
*
* @dataProvider providerTestIsExternal
* @covers ::isExternal
*
* @param string $path
* URL or path to test.
* @param bool $expected
* Expected result.
*/
public function testIsExternal($path, $expected) {
$isExternal = UrlHelper::isExternal($path);
$this->assertEquals($expected, $isExternal);
}
/**
* Provides data for self::testIsExternal().
*
* @return array
*/
public static function providerTestIsExternal() {
return array(
array('/internal/path', FALSE),
array('https://example.com/external/path', TRUE),
array('javascript://fake-external-path', FALSE),
// External URL without an explicit protocol.
array('//www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE),
// Internal URL starting with a slash.
array('/www.drupal.org', FALSE),
);
}
/**
* Tests bad protocol filtering and escaping.
*
* @dataProvider providerTestFilterBadProtocol
* @covers ::setAllowedProtocols
* @covers ::filterBadProtocol
*
* @param string $uri
* Protocol URI.
* @param string $expected
* Expected escaped value.
* @param array $protocols
* Protocols to allow.
*/
public function testFilterBadProtocol($uri, $expected, $protocols) {
UrlHelper::setAllowedProtocols($protocols);
$filtered = UrlHelper::filterBadProtocol($uri);
$this->assertEquals($expected, $filtered);
}
/**
* Provides data for self::testTestFilterBadProtocol().
*
* @return array
*/
public static function providerTestFilterBadProtocol() {
return array(
array('javascript://example.com?foo&bar', '//example.com?foo&amp;bar', array('http', 'https')),
// Test custom protocols.
array('http://example.com?foo&bar', '//example.com?foo&amp;bar', array('https')),
// Valid protocol.
array('http://example.com?foo&bar', 'http://example.com?foo&amp;bar', array('https', 'http')),
// Colon not part of the URL scheme.
array('/test:8888?foo&bar', '/test:8888?foo&amp;bar', array('http')),
);
}
/**
* Tests dangerous url protocol filtering.
*
* @dataProvider providerTestStripDangerousProtocols
* @covers ::setAllowedProtocols
* @covers ::stripDangerousProtocols
*
* @param string $uri
* Protocol URI.
* @param string $expected
* Expected escaped value.
* @param array $protocols
* Protocols to allow.
*/
public function testStripDangerousProtocols($uri, $expected, $protocols) {
UrlHelper::setAllowedProtocols($protocols);
$stripped = UrlHelper::stripDangerousProtocols($uri);
$this->assertEquals($expected, $stripped);
}
/**
* Provides data for self::testStripDangerousProtocols().
*
* @return array
*/
public static function providerTestStripDangerousProtocols() {
return array(
array('javascript://example.com', '//example.com', array('http', 'https')),
// Test custom protocols.
array('http://example.com', '//example.com', array('https')),
// Valid protocol.
array('http://example.com', 'http://example.com', array('https', 'http')),
// Colon not part of the URL scheme.
array('/test:8888', '/test:8888', array('http')),
);
}
/**
* Enhances test urls with schemes
*
* @param array $urls
* The list of urls.
*
* @return array
* A list of provider data with schemes.
*/
protected function dataEnhanceWithScheme(array $urls) {
$url_schemes = array('http', 'https', 'ftp');
$data = array();
foreach ($url_schemes as $scheme) {
foreach ($urls as $url) {
$data[] = array($url, $scheme);
}
}
return $data;
}
/**
* Enhances test urls with prefixes.
*
* @param array $urls
* The list of urls.
*
* @return array
* A list of provider data with prefixes.
*/
protected function dataEnhanceWithPrefix(array $urls) {
$prefixes = array('', '/');
$data = array();
foreach ($prefixes as $prefix) {
foreach ($urls as $url) {
$data[] = array($url, $prefix);
}
}
return $data;
}
/**
* Test detecting external urls that point to local resources.
*
* @param string $url
* The external url to test.
* @param string $base_url
* The base url.
* @param bool $expected
* TRUE if an external URL points to this installation as determined by the
* base url.
*
* @covers ::externalIsLocal
* @dataProvider providerTestExternalIsLocal
*/
public function testExternalIsLocal($url, $base_url, $expected) {
$this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url));
}
/**
* Provider for local external url detection.
*
* @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocal()
*/
public function providerTestExternalIsLocal() {
return array(
// Different mixes of trailing slash.
array('http://example.com', 'http://example.com', TRUE),
array('http://example.com/', 'http://example.com', TRUE),
array('http://example.com', 'http://example.com/', TRUE),
array('http://example.com/', 'http://example.com/', TRUE),
// Sub directory of site.
array('http://example.com/foo', 'http://example.com/', TRUE),
array('http://example.com/foo/bar', 'http://example.com/foo', TRUE),
array('http://example.com/foo/bar', 'http://example.com/foo/', TRUE),
// Different sub-domain.
array('http://example.com', 'http://www.example.com/', FALSE),
array('http://example.com/', 'http://www.example.com/', FALSE),
array('http://example.com/foo', 'http://www.example.com/', FALSE),
// Different TLD.
array('http://example.com', 'http://example.ca', FALSE),
array('http://example.com', 'http://example.ca/', FALSE),
array('http://example.com/', 'http://example.ca/', FALSE),
array('http://example.com/foo', 'http://example.ca', FALSE),
array('http://example.com/foo', 'http://example.ca/', FALSE),
// Different site path.
array('http://example.com/foo', 'http://example.com/bar', FALSE),
array('http://example.com', 'http://example.com/bar', FALSE),
array('http://example.com/bar', 'http://example.com/bar/', FALSE),
);
}
/**
* Test invalid url arguments.
*
* @param string $url
* The url to test.
* @param string $base_url
* The base url.
*
* @covers ::externalIsLocal
* @dataProvider providerTestExternalIsLocalInvalid
* @expectedException \InvalidArgumentException
*/
public function testExternalIsLocalInvalid($url, $base_url) {
UrlHelper::externalIsLocal($url, $base_url);
}
/**
* Provides invalid argument data for local external url detection.
*
* @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocalInvalid()
*/
public function providerTestExternalIsLocalInvalid() {
return array(
array('http://example.com/foo', ''),
array('http://example.com/foo', 'bar'),
array('http://example.com/foo', 'http://'),
// Invalid destination urls.
array('', 'http://example.com/foo'),
array('bar', 'http://example.com/foo'),
array('/bar', 'http://example.com/foo'),
array('bar/', 'http://example.com/foo'),
array('http://', 'http://example.com/foo'),
);
}
}

View file

@ -0,0 +1,172 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\UserAgentTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\UserAgent;
use Drupal\Tests\UnitTestCase;
/**
* Tests bytes size parsing helper methods.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\UserAgent
*/
class UserAgentTest extends UnitTestCase {
/**
* Helper method to supply language codes to testGetBestMatchingLangcode().
*
* @return array
* Language codes, ordered by priority.
*/
protected function getLanguages() {
return array(
// In our test case, 'en' has priority over 'en-US'.
'en',
'en-US',
// But 'fr-CA' has priority over 'fr'.
'fr-CA',
'fr',
// 'es-MX' is alone.
'es-MX',
// 'pt' is alone.
'pt',
// Language codes with more then one dash are actually valid.
// eh-oh-laa-laa is the official language code of the Teletubbies.
'eh-oh-laa-laa',
// Chinese languages.
'zh-hans',
'zh-hant',
'zh-hant-tw',
);
}
/**
* Helper method to supply language mappings to testGetBestMatchingLangcode().
*
* @return array
* Language mappings.
*/
protected function getMappings() {
return array(
'no' => 'nb',
'pt' => 'pt-pt',
'zh' => 'zh-hans',
'zh-tw' => 'zh-hant',
'zh-hk' => 'zh-hant',
'zh-mo' => 'zh-hant',
'zh-cht' => 'zh-hant',
'zh-cn' => 'zh-hans',
'zh-sg' => 'zh-hans',
'zh-chs' => 'zh-hans',
);
}
/**
* Test matching language from user agent.
*
* @dataProvider providerTestGetBestMatchingLangcode
* @covers ::getBestMatchingLangcode
*/
public function testGetBestMatchingLangcode($accept_language, $expected) {
$result = UserAgent::getBestMatchingLangcode($accept_language, $this->getLanguages(), $this->getMappings());
$this->assertSame($expected, $result);
}
/**
* Data provider for testGetBestMatchingLangcode().
*
* @return array
* - An accept-language string.
* - Expected best matching language code.
*/
public function providerTestGetBestMatchingLangcode() {
return array(
// Equal qvalue for each language, choose the site preferred one.
array('en,en-US,fr-CA,fr,es-MX', 'en'),
array('en-US,en,fr-CA,fr,es-MX', 'en'),
array('fr,en', 'en'),
array('en,fr', 'en'),
array('en-US,fr', 'en-US'),
array('fr,en-US', 'en-US'),
array('fr,fr-CA', 'fr-CA'),
array('fr-CA,fr', 'fr-CA'),
array('fr', 'fr-CA'),
array('fr;q=1', 'fr-CA'),
array('fr,es-MX', 'fr-CA'),
array('fr,es', 'fr-CA'),
array('es,fr', 'fr-CA'),
array('es-MX,de', 'es-MX'),
array('de,es-MX', 'es-MX'),
// Different cases and whitespace.
array('en', 'en'),
array('En', 'en'),
array('EN', 'en'),
array(' en', 'en'),
array('en ', 'en'),
array('en, fr', 'en'),
// A less specific language from the browser matches a more specific one
// from the website, and the other way around for compatibility with
// some versions of Internet Explorer.
array('es', 'es-MX'),
array('es-MX', 'es-MX'),
array('pt', 'pt'),
array('pt-PT', 'pt'),
array('pt-PT;q=0.5,pt-BR;q=1,en;q=0.7', 'en'),
array('pt-PT;q=1,pt-BR;q=0.5,en;q=0.7', 'en'),
array('pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7', 'en'),
array('pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7', 'en'),
// Language code with several dashes are valid. The less specific language
// from the browser matches the more specific one from the website.
array('eh-oh-laa-laa', 'eh-oh-laa-laa'),
array('eh-oh-laa', 'eh-oh-laa-laa'),
array('eh-oh', 'eh-oh-laa-laa'),
array('eh', 'eh-oh-laa-laa'),
// Different qvalues.
array('fr,en;q=0.5', 'fr-CA'),
array('fr,en;q=0.5,fr-CA;q=0.25', 'fr'),
// Silly wildcards are also valid.
array('*,fr-CA;q=0.5', 'en'),
array('*,en;q=0.25', 'fr-CA'),
array('en,en-US;q=0.5,fr;q=0.25', 'en'),
array('en-US,en;q=0.5,fr;q=0.25', 'en-US'),
// Unresolvable cases.
array('', FALSE),
array('de,pl', FALSE),
array('iecRswK4eh', FALSE),
array($this->randomMachineName(10), FALSE),
// Chinese langcodes.
array('zh-cn, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hans'),
array('zh-tw, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'),
array('zh-hant, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'),
array('zh-hans, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hans'),
// @todo: This is copied from RFC4647 but our regex skips the numbers so
// they where removed. Our code should be updated so private1-private2 is
// valid. http://tools.ietf.org/html/rfc4647#section-3.4
array('zh-hant-CN-x-private-private, en-us;q=0.90, en;q=0.80, zh;q=0.70', 'zh-hant'),
array('zh-cn', 'zh-hans'),
array('zh-sg', 'zh-hans'),
array('zh-tw', 'zh-hant'),
array('zh-hk', 'zh-hant'),
array('zh-mo', 'zh-hant'),
array('zh-hans', 'zh-hans'),
array('zh-hant', 'zh-hant'),
array('zh-chs', 'zh-hans'),
array('zh-cht', 'zh-hant'),
);
}
}

View file

@ -0,0 +1,120 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\VariableTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Tests\UnitTestCase;
use Drupal\Component\Utility\Variable;
/**
* Test variable export functionality in Variable component.
*
* @group Variable
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Variable
*/
class VariableTest extends UnitTestCase {
/**
* Data provider for testExport().
*
* @return array
* An array containing:
* - The expected export string.
* - The variable to export.
*/
public function providerTestExport() {
return array(
// Array.
array(
'array()',
array(),
),
array(
// non-associative.
"array(\n 1,\n 2,\n 3,\n 4,\n)",
array(1, 2, 3, 4),
),
array(
// associative.
"array(\n 'a' => 1,\n)",
array('a' => 1),
),
// Bool.
array(
'TRUE',
TRUE,
),
array(
'FALSE',
FALSE,
),
// Strings.
array(
"'string'",
'string',
),
array(
'"\n\r\t"',
"\n\r\t",
),
array(
// 2 backslashes. \\
"'\\'",
'\\',
),
array(
// Double-quote "
"'\"'",
"\"",
),
array(
// Single-quote '
'"\'"',
"'",
),
// Object.
array(
// A stdClass object.
'(object) array()',
new \stdClass(),
),
array(
// A not-stdClass object.
"Drupal\Tests\Component\Utility\StubVariableTestClass::__set_state(array(\n))",
new StubVariableTestClass(),
),
);
}
/**
* Tests exporting variables.
*
* @dataProvider providerTestExport
* @covers ::export
*
* @param string $expected
* The expected exported variable.
* @param mixed $variable
* The variable to be exported.
*/
public function testExport($expected, $variable) {
$this->assertEquals($expected, Variable::export($variable));
}
}
/**
* No-op test class for VariableTest::testExport().
*
* @see Drupal\Tests\Component\Utility\VariableTest::testExport()
* @see Drupal\Tests\Component\Utility\VariableTest::providerTestExport()
*/
class StubVariableTestClass {
}

View file

@ -0,0 +1,609 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\XssTest.
*/
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Tests\UnitTestCase;
/**
* XSS Filtering tests.
*
* @group Utility
*
* @coversDefaultClass \Drupal\Component\Utility\Xss
*
* Script injection vectors mostly adopted from http://ha.ckers.org/xss.html.
*
* Relevant CVEs:
* - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973,
* CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740.
*/
class XssTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$allowed_protocols = array(
'http',
'https',
'ftp',
'news',
'nntp',
'telnet',
'mailto',
'irc',
'ssh',
'sftp',
'webcal',
'rtsp',
);
UrlHelper::setAllowedProtocols($allowed_protocols);
}
/**
* Tests limiting allowed tags and XSS prevention.
*
* XSS tests assume that script is disallowed by default and src is allowed
* by default, but on* and style attributes are disallowed.
*
* @param string $value
* The value to filter.
* @param string $expected
* The expected result.
* @param string $message
* The assertion message to display upon failure.
* @param array $allowed_tags
* (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter().
*
* @dataProvider providerTestFilterXssNormalized
*/
public function testFilterXssNormalized($value, $expected, $message, array $allowed_tags = NULL) {
if ($allowed_tags === NULL) {
$value = Xss::filter($value);
}
else {
$value = Xss::filter($value, $allowed_tags);
}
$this->assertNormalized($value, $expected, $message);
}
/**
* Data provider for testFilterXssNormalized().
*
* @see testFilterXssNormalized()
*
* @return array
* An array of arrays containing strings:
* - The value to filter.
* - The value to expect after filtering.
* - The assertion message.
* - (optional) The allowed HTML HTML tags array that should be passed to
* \Drupal\Component\Utility\Xss::filter().
*/
public function providerTestFilterXssNormalized() {
return array(
array(
"Who&#039;s Online",
"who's online",
'HTML filter -- html entity number',
),
array(
"Who&amp;#039;s Online",
"who&#039;s online",
'HTML filter -- encoded html entity number',
),
array(
"Who&amp;amp;#039; Online",
"who&amp;#039; online",
'HTML filter -- double encoded html entity number',
),
// Custom elements with dashes in the tag name.
array(
"<test-element></test-element>",
"<test-element></test-element>",
'Custom element with dashes in tag name.',
array('test-element'),
),
);
}
/**
* Tests limiting to allowed tags and XSS prevention.
*
* XSS tests assume that script is disallowed by default and src is allowed
* by default, but on* and style attributes are disallowed.
*
* @param string $value
* The value to filter.
* @param string $expected
* The string that is expected to be missing.
* @param string $message
* The assertion message to display upon failure.
* @param array $allowed_tags
* (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter().
*
* @dataProvider providerTestFilterXssNotNormalized
*/
public function testFilterXssNotNormalized($value, $expected, $message, array $allowed_tags = NULL) {
if ($allowed_tags === NULL) {
$value = Xss::filter($value);
}
else {
$value = Xss::filter($value, $allowed_tags);
}
$this->assertNotNormalized($value, $expected, $message);
}
/**
* Data provider for testFilterXssNotNormalized().
*
* @see testFilterXssNotNormalized()
*
* @return array
* An array of arrays containing the following elements:
* - The value to filter.
* - The value to expect that's missing after filtering.
* - The assertion message.
* - (optional) The allowed HTML HTML tags array that should be passed to
* \Drupal\Component\Utility\Xss::filter().
*/
public function providerTestFilterXssNotNormalized() {
$cases = array(
// Tag stripping, different ways to work around removal of HTML tags.
array(
'<script>alert(0)</script>',
'script',
'HTML tag stripping -- simple script without special characters.',
),
array(
'<script src="http://www.example.com" />',
'script',
'HTML tag stripping -- empty script with source.',
),
array(
'<ScRipt sRc=http://www.example.com/>',
'script',
'HTML tag stripping evasion -- varying case.',
),
array(
"<script\nsrc\n=\nhttp://www.example.com/\n>",
'script',
'HTML tag stripping evasion -- multiline tag.',
),
array(
'<script/a src=http://www.example.com/a.js></script>',
'script',
'HTML tag stripping evasion -- non whitespace character after tag name.',
),
array(
'<script/src=http://www.example.com/a.js></script>',
'script',
'HTML tag stripping evasion -- no space between tag and attribute.',
),
// Null between < and tag name works at least with IE6.
array(
"<\0scr\0ipt>alert(0)</script>",
'ipt',
'HTML tag stripping evasion -- breaking HTML with nulls.',
),
array(
"<scrscriptipt src=http://www.example.com/a.js>",
'script',
'HTML tag stripping evasion -- filter just removing "script".',
),
array(
'<<script>alert(0);//<</script>',
'script',
'HTML tag stripping evasion -- double opening brackets.',
),
array(
'<script src=http://www.example.com/a.js?<b>',
'script',
'HTML tag stripping evasion -- no closing tag.',
),
// DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should
// work consistently.
array(
'<script>>',
'script',
'HTML tag stripping evasion -- double closing tag.',
),
array(
'<script src=//www.example.com/.a>',
'script',
'HTML tag stripping evasion -- no scheme or ending slash.',
),
array(
'<script src=http://www.example.com/.a',
'script',
'HTML tag stripping evasion -- no closing bracket.',
),
array(
'<script src=http://www.example.com/ <',
'script',
'HTML tag stripping evasion -- opening instead of closing bracket.',
),
array(
'<nosuchtag attribute="newScriptInjectionVector">',
'nosuchtag',
'HTML tag stripping evasion -- unknown tag.',
),
array(
'<t:set attributeName="innerHTML" to="&lt;script defer&gt;alert(0)&lt;/script&gt;">',
't:set',
'HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).',
),
array(
'<img """><script>alert(0)</script>',
'script',
'HTML tag stripping evasion -- a malformed image tag.',
array('img'),
),
array(
'<blockquote><script>alert(0)</script></blockquote>',
'script',
'HTML tag stripping evasion -- script in a blockqoute.',
array('blockquote'),
),
array(
"<!--[if true]><script>alert(0)</script><![endif]-->",
'script',
'HTML tag stripping evasion -- script within a comment.',
),
// Dangerous attributes removal.
array(
'<p onmouseover="http://www.example.com/">',
'onmouseover',
'HTML filter attributes removal -- events, no evasion.',
array('p'),
),
array(
'<li style="list-style-image: url(javascript:alert(0))">',
'style',
'HTML filter attributes removal -- style, no evasion.',
array('li'),
),
array(
'<img onerror =alert(0)>',
'onerror',
'HTML filter attributes removal evasion -- spaces before equals sign.',
array('img'),
),
array(
'<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>',
'onabort',
'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.',
array('img'),
),
array(
'<img oNmediAError=alert(0)>',
'onmediaerror',
'HTML filter attributes removal evasion -- varying case.',
array('img'),
),
// Works at least with IE6.
array(
"<img o\0nfocus\0=alert(0)>",
'focus',
'HTML filter attributes removal evasion -- breaking with nulls.',
array('img'),
),
// Only whitelisted scheme names allowed in attributes.
array(
'<img src="javascript:alert(0)">',
'javascript',
'HTML scheme clearing -- no evasion.',
array('img'),
),
array(
'<img src=javascript:alert(0)>',
'javascript',
'HTML scheme clearing evasion -- no quotes.',
array('img'),
),
// A bit like CVE-2006-0070.
array(
'<img src="javascript:confirm(0)">',
'javascript',
'HTML scheme clearing evasion -- no alert ;)',
array('img'),
),
array(
'<img src=`javascript:alert(0)`>',
'javascript',
'HTML scheme clearing evasion -- grave accents.',
array('img'),
),
array(
'<img dynsrc="javascript:alert(0)">',
'javascript',
'HTML scheme clearing -- rare attribute.',
array('img'),
),
array(
'<table background="javascript:alert(0)">',
'javascript',
'HTML scheme clearing -- another tag.',
array('table'),
),
array(
'<base href="javascript:alert(0);//">',
'javascript',
'HTML scheme clearing -- one more attribute and tag.',
array('base'),
),
array(
'<img src="jaVaSCriPt:alert(0)">',
'javascript',
'HTML scheme clearing evasion -- varying case.',
array('img'),
),
array(
'<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#48;&#41;>',
'javascript',
'HTML scheme clearing evasion -- UTF-8 decimal encoding.',
array('img'),
),
array(
'<img src=&#00000106&#0000097&#00000118&#0000097&#00000115&#0000099&#00000114&#00000105&#00000112&#00000116&#0000058&#0000097&#00000108&#00000101&#00000114&#00000116&#0000040&#0000048&#0000041>',
'javascript',
'HTML scheme clearing evasion -- long UTF-8 encoding.',
array('img'),
),
array(
'<img src=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x30&#x29>',
'javascript',
'HTML scheme clearing evasion -- UTF-8 hex encoding.',
array('img'),
),
array(
"<img src=\"jav\tascript:alert(0)\">",
'script',
'HTML scheme clearing evasion -- an embedded tab.',
array('img'),
),
array(
'<img src="jav&#x09;ascript:alert(0)">',
'script',
'HTML scheme clearing evasion -- an encoded, embedded tab.',
array('img'),
),
array(
'<img src="jav&#x000000A;ascript:alert(0)">',
'script',
'HTML scheme clearing evasion -- an encoded, embedded newline.',
array('img'),
),
// With &#xD; this test would fail, but the entity gets turned into
// &amp;#xD;, so it's OK.
array(
'<img src="jav&#x0D;ascript:alert(0)">',
'script',
'HTML scheme clearing evasion -- an encoded, embedded carriage return.',
array('img'),
),
array(
"<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">",
'cript',
'HTML scheme clearing evasion -- broken into many lines.',
array('img'),
),
array(
"<img src=\"jav\0a\0\0cript:alert(0)\">",
'cript',
'HTML scheme clearing evasion -- embedded nulls.',
array('img'),
),
array(
'<img src="vbscript:msgbox(0)">',
'vbscript',
'HTML scheme clearing evasion -- another scheme.',
array('img'),
),
array(
'<img src="nosuchscheme:notice(0)">',
'nosuchscheme',
'HTML scheme clearing evasion -- unknown scheme.',
array('img'),
),
// Netscape 4.x javascript entities.
array(
'<br size="&{alert(0)}">',
'alert',
'Netscape 4.x javascript entities.',
array('br'),
),
// DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with
// Internet Explorer 6.
array(
"<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>",
'style',
'HTML filter -- invalid UTF-8.',
array('p'),
),
);
// @fixme This dataset currently fails under 5.4 because of
// https://www.drupal.org/node/1210798. Restore after its fixed.
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
$cases[] = array(
'<img src=" &#14; javascript:alert(0)">',
'javascript',
'HTML scheme clearing evasion -- spaces and metacharacters before scheme.',
array('img'),
);
}
return $cases;
}
/**
* Checks that invalid multi-byte sequences are rejected.
*
* @param string $value
* The value to filter.
* @param string $expected
* The expected result.
* @param string $message
* The assertion message to display upon failure.
*
* @dataProvider providerTestInvalidMultiByte
*/
public function testInvalidMultiByte($value, $expected, $message) {
$this->assertEquals(Xss::filter($value), $expected, $message);
}
/**
* Data provider for testInvalidMultiByte().
*
* @see testInvalidMultiByte()
*
* @return array
* An array of arrays containing strings:
* - The value to filter.
* - The value to expect after filtering.
* - The assertion message.
*/
public function providerTestInvalidMultiByte() {
return array(
array("Foo\xC0barbaz", '', 'Xss::filter() accepted invalid sequence "Foo\xC0barbaz"'),
array("Fooÿñ", "Fooÿñ", 'Xss::filter() rejects valid sequence Fooÿñ"'),
array("\xc0aaa", '', 'HTML filter -- overlong UTF-8 sequences.'),
);
}
/**
* Checks that strings starting with a question sign are correctly processed.
*/
public function testQuestionSign() {
$value = Xss::filter('<?xml:namespace ns="urn:schemas-microsoft-com:time">');
$this->assertTrue(stripos($value, '<?xml') === FALSE, 'HTML tag stripping evasion -- starting with a question sign (processing instructions).');
}
/**
* Check that strings in HTML attributes are are correctly processed.
*
* @covers ::attributes
* @dataProvider providerTestAttributes
*/
public function testAttribute($value, $expected, $message, $allowed_tags = NULL) {
$value = Xss::filter($value, $allowed_tags);
$this->assertEquals($expected, $value, $message);
}
/**
* Data provider for testFilterXssAdminNotNormalized().
*/
public function providerTestAttributes() {
return array(
array(
'<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">',
'<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">',
'Image tag with alt and title attribute',
array('img')
),
array(
'<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
'<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
'Image tag with data attribute',
array('img')
),
);
}
/**
* Checks that \Drupal\Component\Utility\Xss::filterAdmin() correctly strips unallowed tags.
*/
public function testFilterXSSAdmin() {
$value = Xss::filterAdmin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />');
$this->assertEquals($value, '', 'Admin HTML filter -- should never allow some tags.');
}
/**
* Tests the loose, admin HTML filter.
*
* @param string $value
* The value to filter.
* @param string $expected
* The expected result.
* @param string $message
* The assertion message to display upon failure.
*
* @dataProvider providerTestFilterXssAdminNotNormalized
*/
public function testFilterXssAdminNotNormalized($value, $expected, $message) {
$this->assertNotNormalized(Xss::filterAdmin($value), $expected, $message);
}
/**
* Data provider for testFilterXssAdminNotNormalized().
*
* @see testFilterXssAdminNotNormalized()
*
* @return array
* An array of arrays containing strings:
* - The value to filter.
* - The value to expect after filtering.
* - The assertion message.
*/
public function providerTestFilterXssAdminNotNormalized() {
return array(
// DRUPAL-SA-2008-044
array('<object />', 'object', 'Admin HTML filter -- should not allow object tag.'),
array('<script />', 'script', 'Admin HTML filter -- should not allow script tag.'),
);
}
/**
* Asserts that a text transformed to lowercase with HTML entities decoded does contain a given string.
*
* Otherwise fails the test with a given message, similar to all the
* SimpleTest assert* functions.
*
* Note that this does not remove nulls, new lines and other characters that
* could be used to obscure a tag or an attribute name.
*
* @param string $haystack
* Text to look in.
* @param string $needle
* Lowercase, plain text to look for.
* @param string $message
* (optional) Message to display if failed. Defaults to an empty string.
* @param string $group
* (optional) The group this message belongs to. Defaults to 'Other'.
*/
protected function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
$this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) !== FALSE, $message, $group);
}
/**
* Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string.
*
* Otherwise fails the test with a given message, similar to all the
* SimpleTest assert* functions.
*
* Note that this does not remove nulls, new lines, and other character that
* could be used to obscure a tag or an attribute name.
*
* @param string $haystack
* Text to look in.
* @param string $needle
* Lowercase, plain text to look for.
* @param string $message
* (optional) Message to display if failed. Defaults to an empty string.
* @param string $group
* (optional) The group this message belongs to. Defaults to 'Other'.
*/
protected function assertNotNormalized($haystack, $needle, $message = '', $group = 'Other') {
$this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) === FALSE, $message, $group);
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Uuid\UuidTest.
*/
namespace Drupal\Tests\Component\Uuid;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Component\Uuid\Com;
use Drupal\Component\Uuid\Pecl;
use Drupal\Component\Uuid\Php;
use Drupal\Tests\UnitTestCase;
/**
* Tests the handling of Universally Unique Identifiers (UUIDs).
*
* @group Uuid
*/
class UuidTest extends UnitTestCase {
/**
* Tests generating valid UUIDs.
*
* @dataProvider providerUuidInstances
*/
public function testGenerateUuid(UuidInterface $instance) {
$this->assertTrue(Uuid::isValid($instance->generate()), sprintf('UUID generation for %s works.', get_class($instance)));
}
/**
* Tests that generated UUIDs are unique.
*
* @dataProvider providerUuidInstances
*/
public function testUuidIsUnique(UuidInterface $instance) {
$this->assertNotEquals($instance->generate(), $instance->generate(), sprintf('Same UUID was not generated twice with %s.', get_class($instance)));
}
/**
* Dataprovider for UUID instance tests.
*
* @return array
*/
public function providerUuidInstances() {
$instances = array();
$instances[][] = new Php();
// If valid PECL extensions exists add to list.
if (function_exists('uuid_create') && !function_exists('uuid_make')) {
$instances[][] = new Pecl();
}
// If we are on Windows add the com implementation as well.
if (function_exists('com_create_guid')) {
$instances[][] = new Com();
}
return $instances;
}
/**
* Tests UUID validation.
*
* @param string $uuid
* The uuid to check against.
* @param bool $is_valid
* Whether the uuid is valid or not.
* @param string $message
* The message to display on failure.
*
* @dataProvider providerTestValidation
*/
public function testValidation($uuid, $is_valid, $message) {
$this->assertSame($is_valid, Uuid::isValid($uuid), $message);
}
/**
* Dataprovider for UUID instance tests.
*
* @return array
* An array of arrays containing
* - The Uuid to check against.
* - (bool) Whether or not the Uuid is valid.
* - Failure message.
*/
public function providerTestValidation() {
return array(
// These valid UUIDs.
array('6ba7b810-9dad-11d1-80b4-00c04fd430c8', TRUE, 'Basic FQDN UUID did not validate'),
array('00000000-0000-0000-0000-000000000000', TRUE, 'Minimum UUID did not validate'),
array('ffffffff-ffff-ffff-ffff-ffffffffffff', TRUE, 'Maximum UUID did not validate'),
// These are invalid UUIDs.
array('0ab26e6b-f074-4e44-9da-601205fa0e976', FALSE, 'Invalid format was validated'),
array('0ab26e6b-f074-4e44-9daf-1205fa0e9761f', FALSE, 'Invalid length was validated'),
);
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\Tests\ComposerIntegrationTest.
*/
namespace Drupal\Tests;
/**
* Tests Composer integration.
*
* @group Composer
*/
class ComposerIntegrationTest extends UnitTestCase {
/**
* Gets human-readable JSON error messages.
*
* @return string[]
* Keys are JSON_ERROR_* constants.
*/
protected function getErrorMessages() {
$messages = [
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
];
if (version_compare(phpversion(), '5.5.0', '>=')) {
$messages[JSON_ERROR_RECURSION] = 'One or more recursive references in the value to be encoded';
$messages[JSON_ERROR_INF_OR_NAN] = 'One or more NAN or INF values in the value to be encoded';
$messages[JSON_ERROR_UNSUPPORTED_TYPE] = 'A value of a type that cannot be encoded was given';
}
return $messages;
}
/**
* Gets the paths to the folders that contain the Composer integration.
*
* @return string[]
* The paths.
*/
protected function getPaths() {
return [
$this->root,
$this->root . '/core',
$this->root . '/core/lib/Drupal/Component/Plugin',
$this->root . '/core/lib/Drupal/Component/ProxyBuilder',
$this->root . '/core/lib/Drupal/Component/Utility',
];
}
/**
* Tests composer.json.
*/
public function testComposerJson() {
foreach ($this->getPaths() as $path) {
$json = file_get_contents($path . '/composer.json');
$result = json_decode($json);
if (is_null($result)) {
$this->fail($this->getErrorMessages()[json_last_error()]);
}
}
}
}

View file

@ -0,0 +1,568 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\AccessManagerTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessCheckInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Access\CheckProvider;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Access\AccessManager;
use Drupal\Core\Access\DefaultAccessCheck;
use Drupal\Tests\UnitTestCase;
use Drupal\router_test\Access\DefinedTestAccessCheck;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @coversDefaultClass \Drupal\Core\Access\AccessManager
* @group Access
*/
class AccessManagerTest extends UnitTestCase {
/**
* The dependency injection container.
*
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
*/
protected $container;
/**
* The collection of routes, which are tested.
*
* @var \Symfony\Component\Routing\RouteCollection
*/
protected $routeCollection;
/**
* The access manager to test.
*
* @var \Drupal\Core\Access\AccessManager
*/
protected $accessManager;
/**
* The route provider.
*
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $routeProvider;
/**
* The parameter converter.
*
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $paramConverter;
/**
* The mocked account.
*
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $account;
/**
* The access arguments resolver.
*
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $argumentsResolverFactory;
/**
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $currentUser;
/**
* @var \Drupal\Core\Access\CheckProvider
*/
protected $checkProvider;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->container = new ContainerBuilder();
$this->routeCollection = new RouteCollection();
$this->routeCollection->add('test_route_1', new Route('/test-route-1'));
$this->routeCollection->add('test_route_2', new Route('/test-route-2', array(), array('_access' => 'TRUE')));
$this->routeCollection->add('test_route_3', new Route('/test-route-3', array(), array('_access' => 'FALSE')));
$this->routeCollection->add('test_route_4', new Route('/test-route-4/{value}', array(), array('_access' => 'TRUE')));
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$map = array();
foreach ($this->routeCollection->all() as $name => $route) {
$map[] = array($name, array(), $route);
}
$map[] = array('test_route_4', array('value' => 'example'), $this->routeCollection->get('test_route_4'));
$this->routeProvider->expects($this->any())
->method('getRouteByName')
->will($this->returnValueMap($map));
$map = array();
$map[] = array('test_route_1', array(), '/test-route-1');
$map[] = array('test_route_2', array(), '/test-route-2');
$map[] = array('test_route_3', array(), '/test-route-3');
$map[] = array('test_route_4', array('value' => 'example'), '/test-route-4/example');
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->currentUser = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->argumentsResolverFactory = $this->getMock('Drupal\Core\Access\AccessArgumentsResolverFactoryInterface');
$this->checkProvider = new CheckProvider();
$this->checkProvider->setContainer($this->container);
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
}
/**
* Tests \Drupal\Core\Access\AccessManager::setChecks().
*/
public function testSetChecks() {
// Check setChecks without any access checker defined yet.
$this->checkProvider->setChecks($this->routeCollection);
foreach ($this->routeCollection->all() as $route) {
$this->assertNull($route->getOption('_access_checks'));
}
$this->setupAccessChecker();
$this->checkProvider->setChecks($this->routeCollection);
$this->assertEquals($this->routeCollection->get('test_route_1')->getOption('_access_checks'), NULL);
$this->assertEquals($this->routeCollection->get('test_route_2')->getOption('_access_checks'), array('test_access_default'));
$this->assertEquals($this->routeCollection->get('test_route_3')->getOption('_access_checks'), array('test_access_default'));
}
/**
* Tests setChecks with a dynamic access checker.
*/
public function testSetChecksWithDynamicAccessChecker() {
// Setup the access manager.
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
// Setup the dynamic access checker.
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
$this->container->set('test_access', $access_check);
$this->checkProvider->addCheckService('test_access', 'access');
$route = new Route('/test-path', array(), array('_foo' => '1', '_bar' => '1'));
$route2 = new Route('/test-path', array(), array('_foo' => '1', '_bar' => '2'));
$collection = new RouteCollection();
$collection->add('test_route', $route);
$collection->add('test_route2', $route2);
$access_check->expects($this->exactly(2))
->method('applies')
->with($this->isInstanceOf('Symfony\Component\Routing\Route'))
->will($this->returnCallback(function (Route $route) {
return $route->getRequirement('_bar') == 2;
}));
$this->checkProvider->setChecks($collection);
$this->assertEmpty($route->getOption('_access_checks'));
$this->assertEquals(array('test_access'), $route2->getOption('_access_checks'));
}
/**
* Tests \Drupal\Core\Access\AccessManager::check().
*/
public function testCheck() {
$route_matches = [];
// Construct route match objects.
foreach ($this->routeCollection->all() as $route_name => $route) {
$route_matches[$route_name] = new RouteMatch($route_name, $route, [], []);
}
// Check route access without any access checker defined yet.
foreach ($route_matches as $route_match) {
$this->assertEquals(FALSE, $this->accessManager->check($route_match, $this->account));
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_match, $this->account, NULL, TRUE));
}
$this->setupAccessChecker();
// An access checker got setup, but the routes haven't been setup using
// setChecks.
foreach ($route_matches as $route_match) {
$this->assertEquals(FALSE, $this->accessManager->check($route_match, $this->account));
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_match, $this->account, NULL, TRUE));
}
// Now applicable access checks have been saved on each route object.
$this->checkProvider->setChecks($this->routeCollection);
$this->setupAccessArgumentsResolverFactory();
$this->assertEquals(FALSE, $this->accessManager->check($route_matches['test_route_1'], $this->account));
$this->assertEquals(TRUE, $this->accessManager->check($route_matches['test_route_2'], $this->account));
$this->assertEquals(FALSE, $this->accessManager->check($route_matches['test_route_3'], $this->account));
$this->assertEquals(TRUE, $this->accessManager->check($route_matches['test_route_4'], $this->account));
$this->assertEquals(AccessResult::neutral(), $this->accessManager->check($route_matches['test_route_1'], $this->account, NULL, TRUE));
$this->assertEquals(AccessResult::allowed(), $this->accessManager->check($route_matches['test_route_2'], $this->account, NULL, TRUE));
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->check($route_matches['test_route_3'], $this->account, NULL, TRUE));
$this->assertEquals(AccessResult::allowed(), $this->accessManager->check($route_matches['test_route_4'], $this->account, NULL, TRUE));
}
/**
* Tests \Drupal\Core\Access\AccessManager::check() with no account specified.
*
* @covers ::check
*/
public function testCheckWithNullAccount() {
$this->setupAccessChecker();
$this->checkProvider->setChecks($this->routeCollection);
$route = $this->routeCollection->get('test_route_2');
$route_match = new RouteMatch('test_route_2', $route, [], []);
// Asserts that the current user is passed to the access arguments resolver
// factory.
$this->setupAccessArgumentsResolverFactory()
->with($route_match, $this->currentUser, NULL);
$this->assertTrue($this->accessManager->check($route_match));
}
/**
* Provides data for the conjunction test.
*
* @return array
* An array of data for check conjunctions.
*
* @see \Drupal\Tests\Core\Access\AccessManagerTest::testCheckConjunctions()
*/
public function providerTestCheckConjunctions() {
$access_allow = AccessResult::allowed();
$access_deny = AccessResult::neutral();
$access_kill = AccessResult::forbidden();
$access_configurations = array();
$access_configurations[] = array(
'name' => 'test_route_4',
'condition_one' => 'TRUE',
'condition_two' => 'FALSE',
'expected' => $access_kill,
);
$access_configurations[] = array(
'name' => 'test_route_5',
'condition_one' => 'TRUE',
'condition_two' => 'NULL',
'expected' => $access_deny,
);
$access_configurations[] = array(
'name' => 'test_route_6',
'condition_one' => 'FALSE',
'condition_two' => 'NULL',
'expected' => $access_kill,
);
$access_configurations[] = array(
'name' => 'test_route_7',
'condition_one' => 'TRUE',
'condition_two' => 'TRUE',
'expected' => $access_allow,
);
$access_configurations[] = array(
'name' => 'test_route_8',
'condition_one' => 'FALSE',
'condition_two' => 'FALSE',
'expected' => $access_kill,
);
$access_configurations[] = array(
'name' => 'test_route_9',
'condition_one' => 'NULL',
'condition_two' => 'NULL',
'expected' => $access_deny,
);
return $access_configurations;
}
/**
* Test \Drupal\Core\Access\AccessManager::check() with conjunctions.
*
* @dataProvider providerTestCheckConjunctions
*/
public function testCheckConjunctions($name, $condition_one, $condition_two, $expected_access) {
$this->setupAccessChecker();
$access_check = new DefinedTestAccessCheck();
$this->container->register('test_access_defined', $access_check);
$this->checkProvider->addCheckService('test_access_defined', 'access', array('_test_access'));
$route_collection = new RouteCollection();
// Setup a test route for each access configuration.
$requirements = array(
'_access' => $condition_one,
'_test_access' => $condition_two,
);
$route = new Route($name, array(), $requirements);
$route_collection->add($name, $route);
$this->checkProvider->setChecks($route_collection);
$this->setupAccessArgumentsResolverFactory();
$route_match = new RouteMatch($name, $route, array(), array());
$this->assertEquals($expected_access->isAllowed(), $this->accessManager->check($route_match, $this->account));
$this->assertEquals($expected_access, $this->accessManager->check($route_match, $this->account, NULL, TRUE));
}
/**
* Tests the checkNamedRoute method.
*
* @see \Drupal\Core\Access\AccessManager::checkNamedRoute()
*/
public function testCheckNamedRoute() {
$this->setupAccessChecker();
$this->checkProvider->setChecks($this->routeCollection);
$this->setupAccessArgumentsResolverFactory();
$this->paramConverter->expects($this->at(0))
->method('convert')
->with(array(RouteObjectInterface::ROUTE_NAME => 'test_route_2', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_2')))
->will($this->returnValue(array()));
$this->paramConverter->expects($this->at(1))
->method('convert')
->with(array(RouteObjectInterface::ROUTE_NAME => 'test_route_2', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_2')))
->will($this->returnValue(array()));
$this->paramConverter->expects($this->at(2))
->method('convert')
->with(array('value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_4', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_4')))
->will($this->returnValue(array('value' => 'example')));
$this->paramConverter->expects($this->at(3))
->method('convert')
->with(array('value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_4', RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_4')))
->will($this->returnValue(array('value' => 'example')));
// Tests the access with routes with parameters without given request.
$this->assertEquals(TRUE, $this->accessManager->checkNamedRoute('test_route_2', array(), $this->account));
$this->assertEquals(AccessResult::allowed(), $this->accessManager->checkNamedRoute('test_route_2', array(), $this->account, TRUE));
$this->assertEquals(TRUE, $this->accessManager->checkNamedRoute('test_route_4', array('value' => 'example'), $this->account));
$this->assertEquals(AccessResult::allowed(), $this->accessManager->checkNamedRoute('test_route_4', array('value' => 'example'), $this->account, TRUE));
}
/**
* Tests the checkNamedRoute with upcasted values.
*
* @see \Drupal\Core\Access\AccessManager::checkNamedRoute()
*/
public function testCheckNamedRouteWithUpcastedValues() {
$this->routeCollection = new RouteCollection();
$route = new Route('/test-route-1/{value}', array(), array('_test_access' => 'TRUE'));
$this->routeCollection->add('test_route_1', $route);
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$this->routeProvider->expects($this->any())
->method('getRouteByName')
->with('test_route_1', array('value' => 'example'))
->will($this->returnValue($route));
$map = array();
$map[] = array('test_route_1', array('value' => 'example'), '/test-route-1/example');
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
$this->paramConverter->expects($this->atLeastOnce())
->method('convert')
->with(array('value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_1', RouteObjectInterface::ROUTE_OBJECT => $route))
->will($this->returnValue(array('value' => 'upcasted_value')));
$this->setupAccessArgumentsResolverFactory($this->exactly(2))
->with($this->callback(function ($route_match) {
return $route_match->getParameters()->get('value') == 'upcasted_value';
}));
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
$access_check->expects($this->atLeastOnce())
->method('applies')
->will($this->returnValue(TRUE));
$access_check->expects($this->atLeastOnce())
->method('access')
->will($this->returnValue(AccessResult::forbidden()));
$this->container->set('test_access', $access_check);
$this->checkProvider->addCheckService('test_access', 'access');
$this->checkProvider->setChecks($this->routeCollection);
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', array('value' => 'example'), $this->account));
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->checkNamedRoute('test_route_1', array('value' => 'example'), $this->account, TRUE));
}
/**
* Tests the checkNamedRoute with default values.
*
* @covers ::checkNamedRoute
*/
public function testCheckNamedRouteWithDefaultValue() {
$this->routeCollection = new RouteCollection();
$route = new Route('/test-route-1/{value}', array('value' => 'example'), array('_test_access' => 'TRUE'));
$this->routeCollection->add('test_route_1', $route);
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$this->routeProvider->expects($this->any())
->method('getRouteByName')
->with('test_route_1', array())
->will($this->returnValue($route));
$map = array();
$map[] = array('test_route_1', array('value' => 'example'), '/test-route-1/example');
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
$this->paramConverter->expects($this->atLeastOnce())
->method('convert')
->with(array('value' => 'example', RouteObjectInterface::ROUTE_NAME => 'test_route_1', RouteObjectInterface::ROUTE_OBJECT => $route))
->will($this->returnValue(array('value' => 'upcasted_value')));
$this->setupAccessArgumentsResolverFactory($this->exactly(2))
->with($this->callback(function ($route_match) {
return $route_match->getParameters()->get('value') == 'upcasted_value';
}));
$this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
$access_check->expects($this->atLeastOnce())
->method('applies')
->will($this->returnValue(TRUE));
$access_check->expects($this->atLeastOnce())
->method('access')
->will($this->returnValue(AccessResult::forbidden()));
$this->container->set('test_access', $access_check);
$this->checkProvider->addCheckService('test_access', 'access');
$this->checkProvider->setChecks($this->routeCollection);
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account));
$this->assertEquals(AccessResult::forbidden(), $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account, TRUE));
}
/**
* Tests checkNamedRoute given an invalid/non existing route name.
*/
public function testCheckNamedRouteWithNonExistingRoute() {
$this->routeProvider->expects($this->any())
->method('getRouteByName')
->will($this->throwException(new RouteNotFoundException()));
$this->setupAccessChecker();
$this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account), 'A non existing route lead to access.');
$this->assertEquals(AccessResult::forbidden()->addCacheTags(['config:core.extension']), $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account, TRUE), 'A non existing route lead to access.');
}
/**
* Tests that an access checker throws an exception for not allowed values.
*
* @dataProvider providerCheckException
*
* @expectedException \Drupal\Core\Access\AccessException
*/
public function testCheckException($return_value) {
$route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
// Setup a test route for each access configuration.
$requirements = array(
'_test_incorrect_value' => 'TRUE',
);
$options = array(
'_access_checks' => array(
'test_incorrect_value',
),
);
$route = new Route('', array(), $requirements, $options);
$route_provider->expects($this->any())
->method('getRouteByName')
->will($this->returnValue($route));
$this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
$this->paramConverter->expects($this->any())
->method('convert')
->will($this->returnValue(array()));
$this->setupAccessArgumentsResolverFactory();
$container = new ContainerBuilder();
// Register a service that will return an incorrect value.
$access_check = $this->getMock('Drupal\Tests\Core\Access\TestAccessCheckInterface');
$access_check->expects($this->any())
->method('access')
->will($this->returnValue($return_value));
$container->set('test_incorrect_value', $access_check);
$access_manager = new AccessManager($route_provider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider);
$this->checkProvider->setContainer($container);
$this->checkProvider->addCheckService('test_incorrect_value', 'access');
$access_manager->checkNamedRoute('test_incorrect_value', array(), $this->account);
}
/**
* Data provider for testCheckException.
*
* @return array
*/
public function providerCheckException() {
return array(
array(array(1)),
array('string'),
array(0),
array(1),
);
}
/**
* Adds a default access check service to the container and the access manager.
*/
protected function setupAccessChecker() {
$access_check = new DefaultAccessCheck();
$this->container->register('test_access_default', $access_check);
$this->checkProvider->addCheckService('test_access_default', 'access', array('_access'));
}
/**
* Add default expectations to the access arguments resolver factory.
*/
protected function setupAccessArgumentsResolverFactory($constraint = NULL) {
if (!isset($constraint)) {
$constraint = $this->any();
}
return $this->argumentsResolverFactory->expects($constraint)
->method('getArgumentsResolver')
->will($this->returnCallback(function ($route_match, $account) {
$resolver = $this->getMock('Drupal\Component\Utility\ArgumentsResolverInterface');
$resolver->expects($this->any())
->method('getArguments')
->will($this->returnCallback(function ($callable) use ($route_match) {
return array($route_match->getRouteObject());
}));
return $resolver;
}));
}
}
/**
* Defines an interface with a defined access() method for mocking.
*/
interface TestAccessCheckInterface extends AccessCheckInterface {
public function access();
}

View file

@ -0,0 +1,938 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\AccessResultTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Access\AccessResult
* @group Access
*/
class AccessResultTest extends UnitTestCase {
protected function assertDefaultCacheability(AccessResult $access) {
$this->assertSame([], $access->getCacheContexts());
$this->assertSame([], $access->getCacheTags());
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
}
/**
* Tests the construction of an AccessResult object.
*
* @covers ::__construct
* @covers ::neutral
*/
public function testConstruction() {
$verify = function (AccessResult $access) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertDefaultCacheability($access);
};
// Verify the object when using the constructor.
$a = new AccessResultNeutral();
$verify($a);
// Verify the object when using the ::create() convenience method.
$b = AccessResult::neutral();
$verify($b);
$this->assertEquals($a, $b);
}
/**
* @covers ::allowed
* @covers ::isAllowed
* @covers ::isForbidden
* @covers ::isNeutral
*/
public function testAccessAllowed() {
$verify = function (AccessResult $access) {
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
};
// Verify the object when using the ::allowed() convenience static method.
$b = AccessResult::allowed();
$verify($b);
}
/**
* @covers ::forbidden
* @covers ::isAllowed
* @covers ::isForbidden
* @covers ::isNeutral
*/
public function testAccessForbidden() {
$verify = function (AccessResult $access) {
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
};
// Verify the object when using the ::forbidden() convenience static method.
$b = AccessResult::forbidden();
$verify($b);
}
/**
* @covers ::allowedIf
* @covers ::isAllowed
* @covers ::isForbidden
* @covers ::isNeutral
*/
public function testAccessConditionallyAllowed() {
$verify = function (AccessResult $access, $allowed) {
$this->assertSame($allowed, $access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertSame(!$allowed, $access->isNeutral());
$this->assertDefaultCacheability($access);
};
$b1 = AccessResult::allowedIf(TRUE);
$verify($b1, TRUE);
$b2 = AccessResult::allowedIf(FALSE);
$verify($b2, FALSE);
}
/**
* @covers ::forbiddenIf
* @covers ::isAllowed
* @covers ::isForbidden
* @covers ::isNeutral
*/
public function testAccessConditionallyForbidden() {
$verify = function (AccessResult $access, $forbidden) {
$this->assertFalse($access->isAllowed());
$this->assertSame($forbidden, $access->isForbidden());
$this->assertSame(!$forbidden, $access->isNeutral());
$this->assertDefaultCacheability($access);
};
$b1 = AccessResult::forbiddenIf(TRUE);
$verify($b1, TRUE);
$b2 = AccessResult::forbiddenIf(FALSE);
$verify($b2, FALSE);
}
/**
* @covers ::andIf
*/
public function testAndIf() {
$neutral = AccessResult::neutral();
$allowed = AccessResult::allowed();
$forbidden = AccessResult::forbidden();
$unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
$unused_access_result_due_to_lazy_evaluation->expects($this->never())
->method($this->anything());
// ALLOWED && ALLOWED === ALLOWED.
$access = $allowed->andIf($allowed);
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// ALLOWED && NEUTRAL === NEUTRAL.
$access = $allowed->andIf($neutral);
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertDefaultCacheability($access);
// ALLOWED && FORBIDDEN === FORBIDDEN.
$access = $allowed->andIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL && ALLOW == NEUTRAL
$access = $neutral->andIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL && NEUTRAL === NEUTRAL.
$access = $neutral->andIf($neutral);
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL && FORBIDDEN === FORBIDDEN.
$access = $neutral->andIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN && ALLOWED = FORBIDDEN
$access = $forbidden->andif($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN && NEUTRAL = FORBIDDEN
$access = $forbidden->andif($neutral);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN && FORBIDDEN = FORBIDDEN
$access = $forbidden->andif($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN && * === FORBIDDEN: lazy evaluation verification.
$access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
}
/**
* @covers ::orIf
*/
public function testOrIf() {
$neutral = AccessResult::neutral();
$allowed = AccessResult::allowed();
$forbidden = AccessResult::forbidden();
$unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
$unused_access_result_due_to_lazy_evaluation->expects($this->never())
->method($this->anything());
// ALLOWED || ALLOWED === ALLOWED.
$access = $allowed->orIf($allowed);
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// ALLOWED || NEUTRAL === ALLOWED.
$access = $allowed->orIf($neutral);
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// ALLOWED || FORBIDDEN === FORBIDDEN.
$access = $allowed->orIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL || NEUTRAL === NEUTRAL.
$access = $neutral->orIf($neutral);
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL || ALLOWED === ALLOWED.
$access = $neutral->orIf($allowed);
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL || FORBIDDEN === FORBIDDEN.
$access = $neutral->orIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN || ALLOWED === FORBIDDEN.
$access = $forbidden->orIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN || NEUTRAL === FORBIDDEN.
$access = $forbidden->orIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN || FORBIDDEN === FORBIDDEN.
$access = $forbidden->orIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// FORBIDDEN || * === FORBIDDEN.
$access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
}
/**
* @covers ::setCacheMaxAge
* @covers ::getCacheMaxAge
*/
public function testCacheMaxAge() {
$this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
$this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
}
/**
* @covers ::addCacheContexts
* @covers ::resetCacheContexts
* @covers ::getCacheContexts
* @covers ::cachePerPermissions
* @covers ::cachePerUser
* @covers ::allowedIfHasPermission
*/
public function testCacheContexts() {
$verify = function (AccessResult $access, array $contexts) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
$this->assertSame($contexts, $access->getCacheContexts());
$this->assertSame([], $access->getCacheTags());
};
$access = AccessResult::neutral()->addCacheContexts(['foo']);
$verify($access, ['foo']);
// Verify resetting works.
$access->resetCacheContexts();
$verify($access, []);
// Verify idempotency.
$access->addCacheContexts(['foo'])
->addCacheContexts(['foo']);
$verify($access, ['foo']);
// Verify same values in different call order yields the same result.
$access->resetCacheContexts()
->addCacheContexts(['foo'])
->addCacheContexts(['bar']);
$verify($access, ['bar', 'foo']);
$access->resetCacheContexts()
->addCacheContexts(['bar'])
->addCacheContexts(['foo']);
$verify($access, ['bar', 'foo']);
// ::cachePerPermissions() convenience method.
$contexts = array('user.permissions');
$a = AccessResult::neutral()->addCacheContexts($contexts);
$verify($a, $contexts);
$b = AccessResult::neutral()->cachePerPermissions();
$verify($b, $contexts);
$this->assertEquals($a, $b);
// ::cachePerUser() convenience method.
$contexts = array('user');
$a = AccessResult::neutral()->addCacheContexts($contexts);
$verify($a, $contexts);
$b = AccessResult::neutral()->cachePerUser();
$verify($b, $contexts);
$this->assertEquals($a, $b);
// Both.
$contexts = array('user', 'user.permissions');
$a = AccessResult::neutral()->addCacheContexts($contexts);
$verify($a, $contexts);
$b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
$verify($b, $contexts);
$c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
$verify($c, $contexts);
$this->assertEquals($a, $b);
$this->assertEquals($a, $c);
// ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
$account->expects($this->any())
->method('hasPermission')
->with('may herd llamas')
->will($this->returnValue(FALSE));
$contexts = array('user.permissions');
// Verify the object when using the ::allowedIfHasPermission() convenience
// static method.
$b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
$verify($b, $contexts);
}
/**
* @covers ::addCacheTags
* @covers ::resetCacheTags
* @covers ::getCacheTags
* @covers ::cacheUntilEntityChanges
* @covers ::cacheUntilConfigurationChanges
*/
public function testCacheTags() {
$verify = function (AccessResult $access, array $tags) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
$this->assertSame([], $access->getCacheContexts());
$this->assertSame($tags, $access->getCacheTags());
};
$access = AccessResult::neutral()->addCacheTags(['foo:bar']);
$verify($access, ['foo:bar']);
// Verify resetting works.
$access->resetCacheTags();
$verify($access, []);
// Verify idempotency.
$access->addCacheTags(['foo:bar'])
->addCacheTags(['foo:bar']);
$verify($access, ['foo:bar']);
// Verify same values in different call order yields the same result.
$access->resetCacheTags()
->addCacheTags(['bar:baz'])
->addCacheTags(['bar:qux'])
->addCacheTags(['foo:bar'])
->addCacheTags(['foo:baz']);
$verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
$access->resetCacheTags()
->addCacheTags(['foo:bar'])
->addCacheTags(['bar:qux'])
->addCacheTags(['foo:baz'])
->addCacheTags(['bar:baz']);
$verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
// ::cacheUntilEntityChanges() convenience method.
$node = $this->getMock('\Drupal\node\NodeInterface');
$node->expects($this->any())
->method('getCacheTags')
->will($this->returnValue(array('node:20011988')));
$tags = array('node:20011988');
$a = AccessResult::neutral()->addCacheTags($tags);
$verify($a, $tags);
$b = AccessResult::neutral()->cacheUntilEntityChanges($node);
$verify($b, $tags);
$this->assertEquals($a, $b);
// ::cacheUntilConfigurationChanges() convenience method.
$configuration = $this->getMock('\Drupal\Core\Config\ConfigBase');
$configuration->expects($this->any())
->method('getCacheTags')
->will($this->returnValue(array('config:foo.bar.baz')));
$tags = array('config:foo.bar.baz');
$a = AccessResult::neutral()->addCacheTags($tags);
$verify($a, $tags);
$b = AccessResult::neutral()->cacheUntilConfigurationChanges($configuration);
$verify($b, $tags);
$this->assertEquals($a, $b);
}
/**
* @covers ::inheritCacheability
*/
public function testInheritCacheability() {
// andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
$access = AccessResult::allowed();
$other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
$this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
$this->assertSame(['user.permissions'], $access->getCacheContexts());
$this->assertSame(['node:20011988'], $access->getCacheTags());
$this->assertSame(1500, $access->getCacheMaxAge());
// andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
$access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
$other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
$this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
$this->assertSame(['user'], $access->getCacheContexts());
$this->assertSame(['node:14031991'], $access->getCacheTags());
$this->assertSame(43200, $access->getCacheMaxAge());
}
/**
* Provides a list of access result pairs and operations to test.
*
* This tests the propagation of cacheability metadata. Rather than testing
* every single bit of cacheability metadata, which would lead to a mind-
* boggling number of permutations, in this test, we only consider the
* permutations of all pairs of the following set:
* - Allowed, implements CDI and is cacheable.
* - Allowed, implements CDI and is not cacheable.
* - Allowed, does not implement CDI (hence not cacheable).
* - Forbidden, implements CDI and is cacheable.
* - Forbidden, implements CDI and is not cacheable.
* - Forbidden, does not implement CDI (hence not cacheable).
* - Neutral, implements CDI and is cacheable.
* - Neutral, implements CDI and is not cacheable.
* - Neutral, does not implement CDI (hence not cacheable).
*
* (Where "CDI" is CacheableDependencyInterface.)
*
* This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
* are two operations to test (AND and OR), so that leads to a grand total of
* 144 permutations, all of which are tested.
*
* There are two "contagious" patterns:
* - Any operation with a forbidden access result yields a forbidden result.
* This therefore also applies to the cacheability metadata associated with
* a forbidden result. This is the case for bullets 4, 5 and 6 in the set
* above.
* - Any operation yields an access result object that is of the same class
* (implementation) as the first operand. This is because operations are
* invoked on the first operand. Therefore, if the first implementation
* does not implement CacheableDependencyInterface, then the result won't
* either. This is the case for bullets 3, 6 and 9 in the set above.
*/
public function andOrCacheabilityPropagationProvider() {
// ct: cacheable=true, cf: cacheable=false, un: uncacheable.
// Note: the test cases that have a "un" access result as the first operand
// test UncacheableTestAccessResult, not AccessResult. However, we
// definitely want to verify that AccessResult's orIf() and andIf() methods
// work correctly when given an AccessResultInterface implementation that
// does not implement CacheableDependencyInterface, and we want to test the
// full gamut of permutations, so that's not a problem.
$allowed_ct = AccessResult::allowed();
$allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
$allowed_un = new UncacheableTestAccessResult('ALLOWED');
$forbidden_ct = AccessResult::forbidden();
$forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
$forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
$neutral_ct = AccessResult::neutral();
$neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
$neutral_un = new UncacheableTestAccessResult('NEUTRAL');
// Structure:
// - First column: first access result.
// - Second column: operator ('OR' or 'AND').
// - Third column: second access result.
// - Fourth column: whether result implements CacheableDependencyInterface
// - Fifth column: whether the result is cacheable (if column 4 is TRUE)
return [
// Allowed (ct) OR allowed (ct,cf,un).
[$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
[$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
// Allowed (cf) OR allowed (ct,cf,un).
[$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
[$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
// Allowed (un) OR allowed (ct,cf,un).
[$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
[$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
[$allowed_un, 'OR', $allowed_un, FALSE, NULL],
// Allowed (ct) OR forbidden (ct,cf,un).
[$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
[$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
// Allowed (cf) OR forbidden (ct,cf,un).
[$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Allowed (un) OR forbidden (ct,cf,un).
[$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
[$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
[$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
// Allowed (ct) OR neutral (ct,cf,un).
[$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
// Allowed (cf) OR neutral (ct,cf,un).
[$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
[$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
// Allowed (un) OR neutral (ct,cf,un).
[$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
[$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
[$allowed_un, 'OR', $neutral_un, FALSE, NULL],
// Forbidden (ct) OR allowed (ct,cf,un).
[$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
// Forbidden (cf) OR allowed (ct,cf,un).
[$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
[$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
// Forbidden (un) OR allowed (ct,cf,un).
[$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
[$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
[$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
// Forbidden (ct) OR neutral (ct,cf,un).
[$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
// Forbidden (cf) OR neutral (ct,cf,un).
[$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
[$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
// Forbidden (un) OR neutral (ct,cf,un).
[$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
[$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
[$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
// Forbidden (ct) OR forbidden (ct,cf,un).
[$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
// Forbidden (cf) OR forbidden (ct,cf,un).
[$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Forbidden (un) OR forbidden (ct,cf,un).
[$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
[$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
[$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
// Neutral (ct) OR allowed (ct,cf,un).
[$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
[$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
// Neutral (cf) OR allowed (ct,cf,un).
[$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
// Neutral (un) OR allowed (ct,cf,un).
[$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
[$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
[$neutral_un, 'OR', $allowed_un, FALSE, NULL],
// Neutral (ct) OR neutral (ct,cf,un).
[$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
// Neutral (cf) OR neutral (ct,cf,un).
[$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
// Neutral (un) OR neutral (ct,cf,un).
[$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
[$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
[$neutral_un, 'OR', $neutral_un, FALSE, NULL],
// Neutral (ct) OR forbidden (ct,cf,un).
[$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
[$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
// Neutral (cf) OR forbidden (ct,cf,un).
[$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Neutral (un) OR forbidden (ct,cf,un).
[$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
[$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
[$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
// Allowed (ct) AND allowed (ct,cf,un).
[$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
// Allowed (cf) AND allowed (ct,cf,un).
[$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
// Allowed (un) AND allowed (ct,cf,un).
[$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
[$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
[$allowed_un, 'AND', $allowed_un, FALSE, NULL],
// Allowed (ct) AND forbidden (ct,cf,un).
[$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
// Allowed (cf) AND forbidden (ct,cf,un).
[$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
[$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Allowed (un) AND forbidden (ct,cf,un).
[$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
[$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
[$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
// Allowed (ct) AND neutral (ct,cf,un).
[$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
// Allowed (cf) AND neutral (ct,cf,un).
[$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
// Allowed (un) AND neutral (ct,cf,un).
[$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
[$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
[$allowed_un, 'AND', $neutral_un, FALSE, NULL],
// Forbidden (ct) AND allowed (ct,cf,un).
[$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
// Forbidden (cf) AND allowed (ct,cf,un).
[$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
// Forbidden (un) AND allowed (ct,cf,un).
[$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
[$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
[$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
// Forbidden (ct) AND neutral (ct,cf,un).
[$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
// Forbidden (cf) AND neutral (ct,cf,un).
[$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
// Forbidden (un) AND neutral (ct,cf,un).
[$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
[$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
[$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
// Forbidden (ct) AND forbidden (ct,cf,un).
[$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
// Forbidden (cf) AND forbidden (ct,cf,un).
[$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Forbidden (un) AND forbidden (ct,cf,un).
[$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
[$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
[$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
// Neutral (ct) AND allowed (ct,cf,un).
[$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
[$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
// Neutral (cf) AND allowed (ct,cf,un).
[$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
// Neutral (un) AND allowed (ct,cf,un).
[$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
[$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
[$neutral_un, 'AND', $allowed_un, FALSE, NULL],
// Neutral (ct) AND neutral (ct,cf,un).
[$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
[$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
// Neutral (cf) AND neutral (ct,cf,un).
[$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
// Neutral (un) AND neutral (ct,cf,un).
[$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
[$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
[$neutral_un, 'AND', $neutral_un, FALSE, NULL],
// Neutral (ct) AND forbidden (ct,cf,un).
[$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
[$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
// Neutral (cf) AND forbidden (ct,cf,un).
[$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
[$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Neutral (un) AND forbidden (ct,cf,un).
[$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
[$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
[$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
];
}
/**
* @covers ::andIf
* @covers ::orIf
* @covers ::inheritCacheability
*
* @dataProvider andOrCacheabilityPropagationProvider
*/
public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
if ($op === 'OR') {
$result = $first->orIf($second);
}
else if ($op === 'AND') {
$result = $first->andIf($second);
}
else {
throw new \LogicException('Invalid operator specified');
}
if ($implements_cacheable_dependency_interface) {
$this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
if ($result instanceof CacheableDependencyInterface) {
$this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
}
}
else {
$this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
}
}
/**
* @covers ::orIf
*
* Tests the special case of ORing non-forbidden access results that are both
* cacheable but have different cacheability metadata.
* This is only the case for non-forbidden access results; we still abort the
* ORing process as soon as a forbidden access result is encountered. This is
* tested in ::testOrIf().
*/
public function testOrIfCacheabilityMerging() {
$merge_both_directions = function(AccessResult $a, AccessResult $b) {
// A globally cacheable access result.
$a->setCacheMaxAge(3600);
// Another access result that is cacheable per permissions.
$b->setCacheMaxAge(86400)->cachePerPermissions();
$r1 = $a->orIf($b);
$this->assertTrue($r1->getCacheMaxAge() === 3600);
$this->assertSame(['user.permissions'], $r1->getCacheContexts());
$r2 = $b->orIf($a);
$this->assertTrue($r2->getCacheMaxAge() === 3600);
$this->assertSame(['user.permissions'], $r2->getCacheContexts());
};
// Merge either direction, get the same result.
$merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
$merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
$merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
$merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
}
/**
* Tests allowedIfHasPermissions().
*
* @covers ::allowedIfHasPermissions
*
* @dataProvider providerTestAllowedIfHasPermissions
*/
public function testAllowedIfHasPermissions($permissions, $conjunction, $expected_access) {
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
$account->expects($this->any())
->method('hasPermission')
->willReturnMap([
['allowed', TRUE],
['denied', FALSE],
]);
$access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
$this->assertEquals($expected_access, $access_result);
}
/**
* Provides data for the testAllowedIfHasPermissions() method.
*
* @return array
*/
public function providerTestAllowedIfHasPermissions() {
return [
[[], 'AND', AccessResult::allowedIf(FALSE)],
[[], 'OR', AccessResult::allowedIf(FALSE)],
[['allowed'], 'OR', AccessResult::allowedIf(TRUE)->addCacheContexts(['user.permissions'])],
[['allowed'], 'AND', AccessResult::allowedIf(TRUE)->addCacheContexts(['user.permissions'])],
[['denied'], 'OR', AccessResult::allowedIf(FALSE)->addCacheContexts(['user.permissions'])],
[['denied'], 'AND', AccessResult::allowedIf(FALSE)->addCacheContexts(['user.permissions'])],
[['allowed', 'denied'], 'OR', AccessResult::allowedIf(TRUE)->addCacheContexts(['user.permissions'])],
[['denied', 'allowed'], 'OR', AccessResult::allowedIf(TRUE)->addCacheContexts(['user.permissions'])],
[['allowed', 'denied', 'other'], 'OR', AccessResult::allowedIf(TRUE)->addCacheContexts(['user.permissions'])],
[['allowed', 'denied'], 'AND', AccessResult::allowedIf(FALSE)->addCacheContexts(['user.permissions'])],
];
}
}
class UncacheableTestAccessResult implements AccessResultInterface {
/**
* The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
*
* @var string
*/
protected $value;
/**
* Constructs a new UncacheableTestAccessResult object.
*/
public function __construct($value) {
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function isAllowed() {
return $this->value === 'ALLOWED';
}
/**
* {@inheritdoc}
*/
public function isForbidden() {
return $this->value === 'FORBIDDEN';
}
/**
* {@inheritdoc}
*/
public function isNeutral() {
return $this->value === 'NEUTRAL';
}
/**
* {@inheritdoc}
*/
public function orIf(AccessResultInterface $other) {
if ($this->isForbidden() || $other->isForbidden()) {
return new static('FORBIDDEN');
}
elseif ($this->isAllowed() || $other->isAllowed()) {
return new static('ALLOWED');
}
else {
return new static('NEUTRAL');
}
}
/**
* {@inheritdoc}
*/
public function andIf(AccessResultInterface $other) {
if ($this->isForbidden() || $other->isForbidden()) {
return new static('FORBIDDEN');
}
elseif ($this->isAllowed() && $other->isAllowed()) {
return new static('ALLOWED');
}
else {
return new static('NEUTRAL');
}
}
}

View file

@ -0,0 +1,91 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\CsrfAccessCheckTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessResult;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Drupal\Core\Access\CsrfAccessCheck;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Access\CsrfAccessCheck
* @group Access
*/
class CsrfAccessCheckTest extends UnitTestCase {
/**
* The mock CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
*/
protected $csrfToken;
/**
* The access checker.
*
* @var \Drupal\Core\Access\CsrfAccessCheck
*/
protected $accessCheck;
/**
* The mock route match.
*
* @var \Drupal\Core\RouteMatch\RouteMatchInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $routeMatch;
protected function setUp() {
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
->getMock();
$this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
$this->accessCheck = new CsrfAccessCheck($this->csrfToken);
}
/**
* Tests the access() method with a valid token.
*/
public function testAccessTokenPass() {
$this->csrfToken->expects($this->once())
->method('validate')
->with('test_query', 'test-path/42')
->will($this->returnValue(TRUE));
$this->routeMatch->expects($this->once())
->method('getRawParameters')
->will($this->returnValue(array('node' => 42)));
$route = new Route('/test-path/{node}', array(), array('_csrf_token' => 'TRUE'));
$request = Request::create('/test-path/42?token=test_query');
$this->assertEquals(AccessResult::allowed()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch));
}
/**
* Tests the access() method with an invalid token.
*/
public function testAccessTokenFail() {
$this->csrfToken->expects($this->once())
->method('validate')
->with('test_query', 'test-path')
->will($this->returnValue(FALSE));
$this->routeMatch->expects($this->once())
->method('getRawParameters')
->will($this->returnValue(array()));
$route = new Route('/test-path', array(), array('_csrf_token' => 'TRUE'));
$request = Request::create('/test-path?token=test_query');
$this->assertEquals(AccessResult::forbidden()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch));
}
}

View file

@ -0,0 +1,216 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\CsrfTokenGeneratorTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Site\Settings;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Component\Utility\Crypt;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the CsrfTokenGenerator class.
*
* @group Access
* @coversDefaultClass \Drupal\Core\Access\CsrfTokenGenerator
*/
class CsrfTokenGeneratorTest extends UnitTestCase {
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $generator;
/**
* The mock private key instance.
*
* @var \Drupal\Core\PrivateKey|\PHPUnit_Framework_MockObject_MockObject
*/
protected $privateKey;
/**
* The mock session metadata bag.
*
* @var \Drupal\Core\Session\MetadataBag|\PHPUnit_Framework_MockObject_MockObject
*/
protected $sessionMetadata;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->privateKey = $this->getMockBuilder('Drupal\Core\PrivateKey')
->disableOriginalConstructor()
->setMethods(array('get'))
->getMock();
$this->sessionMetadata = $this->getMockBuilder('Drupal\Core\Session\MetadataBag')
->disableOriginalConstructor()
->getMock();
$settings = array(
'hash_salt' => $this->randomMachineName(),
);
new Settings($settings);
$this->generator = new CsrfTokenGenerator($this->privateKey, $this->sessionMetadata);
}
/**
* Set up default expectations on the mocks.
*/
protected function setupDefaultExpectations() {
$key = Crypt::randomBytesBase64();
$this->privateKey->expects($this->any())
->method('get')
->will($this->returnValue($key));
$seed = Crypt::randomBytesBase64();
$this->sessionMetadata->expects($this->any())
->method('getCsrfTokenSeed')
->will($this->returnValue($seed));
}
/**
* Tests CsrfTokenGenerator::get().
*
* @covers ::get
*/
public function testGet() {
$this->setupDefaultExpectations();
$this->assertInternalType('string', $this->generator->get());
$this->assertNotSame($this->generator->get(), $this->generator->get($this->randomMachineName()));
$this->assertNotSame($this->generator->get($this->randomMachineName()), $this->generator->get($this->randomMachineName()));
}
/**
* Tests that a new token seed is generated upon first use.
*
* @covers ::get
*/
public function testGenerateSeedOnGet() {
$key = Crypt::randomBytesBase64();
$this->privateKey->expects($this->any())
->method('get')
->will($this->returnValue($key));
$this->sessionMetadata->expects($this->once())
->method('getCsrfTokenSeed')
->will($this->returnValue(NULL));
$this->sessionMetadata->expects($this->once())
->method('setCsrfTokenSeed')
->with($this->isType('string'));
$this->assertInternalType('string', $this->generator->get());
}
/**
* Tests CsrfTokenGenerator::validate().
*
* @covers ::validate
*/
public function testValidate() {
$this->setupDefaultExpectations();
$token = $this->generator->get();
$this->assertTrue($this->generator->validate($token));
$this->assertFalse($this->generator->validate($token, 'foo'));
$token = $this->generator->get('bar');
$this->assertTrue($this->generator->validate($token, 'bar'));
}
/**
* Tests CsrfTokenGenerator::validate() with different parameter types.
*
* @param mixed $token
* The token to be validated.
* @param mixed $value
* (optional) An additional value to base the token on.
*
* @covers ::validate
* @dataProvider providerTestValidateParameterTypes
*/
public function testValidateParameterTypes($token, $value) {
$this->setupDefaultExpectations();
// The following check might throw PHP fatals and notices, so we disable
// error assertions.
set_error_handler(function () {return TRUE;});
$this->assertFalse($this->generator->validate($token, $value));
restore_error_handler();
}
/**
* Provides data for testValidateParameterTypes.
*
* @return array
* An array of data used by the test.
*/
public function providerTestValidateParameterTypes() {
return array(
array(array(), ''),
array(TRUE, 'foo'),
array(0, 'foo'),
);
}
/**
* Tests CsrfTokenGenerator::validate() with invalid parameter types.
*
* @param mixed $token
* The token to be validated.
* @param mixed $value
* (optional) An additional value to base the token on.
*
* @covers ::validate
* @dataProvider providerTestInvalidParameterTypes
* @expectedException InvalidArgumentException
*/
public function testInvalidParameterTypes($token, $value = '') {
$this->setupDefaultExpectations();
$this->generator->validate($token, $value);
}
/**
* Provides data for testInvalidParameterTypes.
*
* @return array
* An array of data used by the test.
*/
public function providerTestInvalidParameterTypes() {
return array(
array(NULL, new \stdClass()),
array(0, array()),
array('', array()),
array(array(), array()),
);
}
/**
* Tests the exception thrown when no 'hash_salt' is provided in settings.
*
* @covers ::get
* @expectedException \RuntimeException
*/
public function testGetWithNoHashSalt() {
// Update settings with no hash salt.
new Settings(array());
$generator = new CsrfTokenGenerator($this->privateKey, $this->sessionMetadata);
$generator->get();
}
}

View file

@ -0,0 +1,125 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\CustomAccessCheckTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\CustomAccessCheck;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\Core\Access\CustomAccessCheck
* @group Access
*/
class CustomAccessCheckTest extends UnitTestCase {
/**
* The access checker to test.
*
* @var \Drupal\Core\Access\CustomAccessCheck
*/
protected $accessChecker;
/**
* The mocked controller resolver.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $controllerResolver;
/**
* The mocked arguments resolver.
*
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $argumentsResolverFactory;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
$this->argumentsResolverFactory = $this->getMock('Drupal\Core\Access\AccessArgumentsResolverFactoryInterface');
$this->accessChecker = new CustomAccessCheck($this->controllerResolver, $this->argumentsResolverFactory);
}
/**
* Test the access method.
*/
public function testAccess() {
$route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
$this->controllerResolver->expects($this->at(0))
->method('getControllerFromDefinition')
->with('\Drupal\Tests\Core\Access\TestController::accessDeny')
->will($this->returnValue(array(new TestController(), 'accessDeny')));
$resolver0 = $this->getMock('Drupal\Component\Utility\ArgumentsResolverInterface');
$resolver0->expects($this->once())
->method('getArguments')
->will($this->returnValue(array()));
$this->argumentsResolverFactory->expects($this->at(0))
->method('getArgumentsResolver')
->will($this->returnValue($resolver0));
$this->controllerResolver->expects($this->at(1))
->method('getControllerFromDefinition')
->with('\Drupal\Tests\Core\Access\TestController::accessAllow')
->will($this->returnValue(array(new TestController(), 'accessAllow')));
$resolver1 = $this->getMock('Drupal\Component\Utility\ArgumentsResolverInterface');
$resolver1->expects($this->once())
->method('getArguments')
->will($this->returnValue(array()));
$this->argumentsResolverFactory->expects($this->at(1))
->method('getArgumentsResolver')
->will($this->returnValue($resolver1));
$this->controllerResolver->expects($this->at(2))
->method('getControllerFromDefinition')
->with('\Drupal\Tests\Core\Access\TestController::accessParameter')
->will($this->returnValue(array(new TestController(), 'accessParameter')));
$resolver2 = $this->getMock('Drupal\Component\Utility\ArgumentsResolverInterface');
$resolver2->expects($this->once())
->method('getArguments')
->will($this->returnValue(array('parameter' => 'TRUE')));
$this->argumentsResolverFactory->expects($this->at(2))
->method('getArgumentsResolver')
->will($this->returnValue($resolver2));
$route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessDeny'));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->assertEquals(AccessResult::neutral(), $this->accessChecker->access($route, $route_match, $account));
$route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessAllow'));
$this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account));
$route = new Route('/test-route', array('parameter' => 'TRUE'), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessParameter'));
$this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account));
}
}
class TestController {
public function accessAllow() {
return AccessResult::allowed();
}
public function accessDeny() {
return AccessResult::neutral();
}
public function accessParameter($parameter) {
return AccessResult::allowedIf($parameter == 'TRUE');
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\DefaultAccessCheckTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\DefaultAccessCheck;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\Core\Access\DefaultAccessCheck
* @group Access
*/
class DefaultAccessCheckTest extends UnitTestCase {
/**
* The access checker to test.
*
* @var \Drupal\Core\Access\DefaultAccessCheck
*/
protected $accessChecker;
/**
* The mocked account.
*
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $account;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->accessChecker = new DefaultAccessCheck();
}
/**
* Test the access method.
*/
public function testAccess() {
$request = new Request(array());
$route = new Route('/test-route', array(), array('_access' => 'NULL'));
$this->assertEquals(AccessResult::neutral(), $this->accessChecker->access($route, $request, $this->account));
$route = new Route('/test-route', array(), array('_access' => 'FALSE'));
$this->assertEquals(AccessResult::forbidden(), $this->accessChecker->access($route, $request, $this->account));
$route = new Route('/test-route', array(), array('_access' => 'TRUE'));
$this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $request, $this->account));
}
}

View file

@ -0,0 +1,120 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\RouteProcessorCsrfTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Access\RouteProcessorCsrf;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\Core\Access\RouteProcessorCsrf
* @group Access
*/
class RouteProcessorCsrfTest extends UnitTestCase {
/**
* The mock CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
*/
protected $csrfToken;
/**
* The route processor.
*
* @var \Drupal\Core\Access\RouteProcessorCsrf
*/
protected $processor;
protected function setUp() {
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
->getMock();
$this->processor = new RouteProcessorCsrf($this->csrfToken);
}
/**
* Tests the processOutbound() method with no _csrf_token route requirement.
*/
public function testProcessOutboundNoRequirement() {
$this->csrfToken->expects($this->never())
->method('get');
$route = new Route('/test-path');
$parameters = array();
$cacheable_metadata = new CacheableMetadata();
$this->processor->processOutbound('test', $route, $parameters, $cacheable_metadata);
// No parameters should be added to the parameters array.
$this->assertEmpty($parameters);
// Cacheability of routes without a _csrf_token route requirement is
// unaffected.
$this->assertEquals((new CacheableMetadata()), $cacheable_metadata);
}
/**
* Tests the processOutbound() method with a _csrf_token route requirement.
*/
public function testProcessOutbound() {
$this->csrfToken->expects($this->once())
->method('get')
// The leading '/' will be stripped from the path.
->with('test-path')
->will($this->returnValue('test_token'));
$route = new Route('/test-path', array(), array('_csrf_token' => 'TRUE'));
$parameters = array();
$cacheable_metadata = new CacheableMetadata();
$this->processor->processOutbound('test', $route, $parameters, $cacheable_metadata);
// 'token' should be added to the parameters array.
$this->assertArrayHasKey('token', $parameters);
$this->assertSame($parameters['token'], 'test_token');
// Cacheability of routes with a _csrf_token route requirement is max-age=0.
$this->assertEquals((new CacheableMetadata())->setCacheMaxAge(0), $cacheable_metadata);
}
/**
* Tests the processOutbound() method with a dynamic path and one replacement.
*/
public function testProcessOutboundDynamicOne() {
$this->csrfToken->expects($this->once())
->method('get')
->with('test-path/100')
->will($this->returnValue('test_token'));
$route = new Route('/test-path/{slug}', array(), array('_csrf_token' => 'TRUE'));
$parameters = array('slug' => 100);
$cacheable_metadata = new CacheableMetadata();
$this->processor->processOutbound('test', $route, $parameters, $cacheable_metadata);
// Cacheability of routes with a _csrf_token route requirement is max-age=0.
$this->assertEquals((new CacheableMetadata())->setCacheMaxAge(0), $cacheable_metadata);
}
/**
* Tests the processOutbound() method with two parameter replacements.
*/
public function testProcessOutboundDynamicTwo() {
$this->csrfToken->expects($this->once())
->method('get')
->with('100/test-path/test')
->will($this->returnValue('test_token'));
$route = new Route('{slug_1}/test-path/{slug_2}', array(), array('_csrf_token' => 'TRUE'));
$parameters = array('slug_1' => 100, 'slug_2' => 'test');
$cacheable_metadata = new CacheableMetadata();
$this->processor->processOutbound('test', $route, $parameters, $cacheable_metadata);
// Cacheability of routes with a _csrf_token route requirement is max-age=0.
$this->assertEquals((new CacheableMetadata())->setCacheMaxAge(0), $cacheable_metadata);
}
}

View file

@ -0,0 +1,437 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Ajax\AjaxCommandsTest.
*/
namespace Drupal\Tests\Core\Ajax;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\BeforeCommand;
use Drupal\Core\Ajax\ChangedCommand;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\DataCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\RestripeCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\SetDialogOptionCommand;
use Drupal\Core\Ajax\SetDialogTitleCommand;
use Drupal\Core\Ajax\RedirectCommand;
/**
* Test coverage for various classes in the \Drupal\Core\Ajax namespace.
*
* @group Ajax
*/
class AjaxCommandsTest extends UnitTestCase {
/**
* @covers \Drupal\Core\Ajax\AddCssCommand
*/
public function testAddCssCommand() {
$command = new AddCssCommand('p{ text-decoration:blink; }');
$expected = array(
'command' => 'add_css',
'data' => 'p{ text-decoration:blink; }',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\AfterCommand
*/
public function testAfterCommand() {
$command = new AfterCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'after',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\AlertCommand
*/
public function testAlertCommand() {
$command = new AlertCommand('Set condition 1 throughout the ship!');
$expected = array(
'command' => 'alert',
'text' => 'Set condition 1 throughout the ship!',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\AppendCommand
*/
public function testAppendCommand() {
$command = new AppendCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'append',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\BeforeCommand
*/
public function testBeforeCommand() {
$command = new BeforeCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'before',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\ChangedCommand
*/
public function testChangedCommand() {
$command = new ChangedCommand('#page-title', '#page-title-changed');
$expected = array(
'command' => 'changed',
'selector' => '#page-title',
'asterisk' => '#page-title-changed',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\CssCommand
*/
public function testCssCommand() {
$command = new CssCommand('#page-title', array('text-decoration' => 'blink'));
$command->setProperty('font-size', '40px')->setProperty('font-weight', 'bold');
$expected = array(
'command' => 'css',
'selector' => '#page-title',
'argument' => array(
'text-decoration' => 'blink',
'font-size' => '40px',
'font-weight' => 'bold',
),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\DataCommand
*/
public function testDataCommand() {
$command = new DataCommand('#page-title', 'my-data', array('key' => 'value'));
$expected = array(
'command' => 'data',
'selector' => '#page-title',
'name' => 'my-data',
'value' => array('key' => 'value'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\HtmlCommand
*/
public function testHtmlCommand() {
$command = new HtmlCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'html',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\InsertCommand
*/
public function testInsertCommand() {
$command = new InsertCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => NULL,
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\InvokeCommand
*/
public function testInvokeCommand() {
$command = new InvokeCommand('#page-title', 'myMethod', array('var1', 'var2'));
$expected = array(
'command' => 'invoke',
'selector' => '#page-title',
'method' => 'myMethod',
'args' => array('var1', 'var2'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\PrependCommand
*/
public function testPrependCommand() {
$command = new PrependCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'prepend',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\RemoveCommand
*/
public function testRemoveCommand() {
$command = new RemoveCommand('#page-title');
$expected = array(
'command' => 'remove',
'selector' => '#page-title',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\ReplaceCommand
*/
public function testReplaceCommand() {
$command = new ReplaceCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
$expected = array(
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '#page-title',
'data' => '<p>New Text!</p>',
'settings' => array('my-setting' => 'setting'),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\RestripeCommand
*/
public function testRestripeCommand() {
$command = new RestripeCommand('#page-title');
$expected = array(
'command' => 'restripe',
'selector' => '#page-title',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\SettingsCommand
*/
public function testSettingsCommand() {
$command = new SettingsCommand(array('key' => 'value'), TRUE);
$expected = array(
'command' => 'settings',
'settings' => array('key' => 'value'),
'merge' => TRUE,
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\OpenDialogCommand
*/
public function testOpenDialogCommand() {
$command = $this->getMockBuilder('Drupal\Core\Ajax\OpenDialogCommand')
->setConstructorArgs(array(
'#some-dialog', 'Title', '<p>Text!</p>', array(
'url' => FALSE,
'width' => 500,
),
))
->setMethods(array('getRenderedContent'))
->getMock();
// This method calls the render service, which isn't available. We want it
// to do nothing so we mock it to return a known value.
$command->expects($this->once())
->method('getRenderedContent')
->willReturn('rendered content');
$expected = array(
'command' => 'openDialog',
'selector' => '#some-dialog',
'settings' => NULL,
'data' => 'rendered content',
'dialogOptions' => array(
'url' => FALSE,
'width' => 500,
'title' => 'Title',
'modal' => FALSE,
),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\OpenModalDialogCommand
*/
public function testOpenModalDialogCommand() {
$command = $this->getMockBuilder('Drupal\Core\Ajax\OpenModalDialogCommand')
->setConstructorArgs(array(
'Title', '<p>Text!</p>', array(
'url' => 'example',
'width' => 500,
),
))
->setMethods(array('getRenderedContent'))
->getMock();
// This method calls the render service, which isn't available. We want it
// to do nothing so we mock it to return a known value.
$command->expects($this->once())
->method('getRenderedContent')
->willReturn('rendered content');
$expected = array(
'command' => 'openDialog',
'selector' => '#drupal-modal',
'settings' => NULL,
'data' => 'rendered content',
'dialogOptions' => array(
'url' => 'example',
'width' => 500,
'title' => 'Title',
'modal' => TRUE,
),
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\CloseModalDialogCommand
*/
public function testCloseModalDialogCommand() {
$command = new CloseModalDialogCommand();
$expected = array(
'command' => 'closeDialog',
'selector' => '#drupal-modal',
'persist' => FALSE,
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\CloseDialogCommand
*/
public function testCloseDialogCommand() {
$command = new CloseDialogCommand('#some-dialog', TRUE);
$expected = array(
'command' => 'closeDialog',
'selector' => '#some-dialog',
'persist' => TRUE,
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\SetDialogOptionCommand
*/
public function testSetDialogOptionCommand() {
$command = new SetDialogOptionCommand('#some-dialog', 'width', '500');
$expected = array(
'command' => 'setDialogOption',
'selector' => '#some-dialog',
'optionName' => 'width',
'optionValue' => '500',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\SetDialogTitleCommand
*/
public function testSetDialogTitleCommand() {
$command = new SetDialogTitleCommand('#some-dialog', 'Example');
$expected = array(
'command' => 'setDialogOption',
'selector' => '#some-dialog',
'optionName' => 'title',
'optionValue' => 'Example',
);
$this->assertEquals($expected, $command->render());
}
/**
* @covers \Drupal\Core\Ajax\RedirectCommand
*/
public function testRedirectCommand() {
$command = new RedirectCommand('http://example.com');
$expected = array(
'command' => 'redirect',
'url' => 'http://example.com',
);
$this->assertEquals($expected, $command->render());
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Ajax\AjaxResponseTest.
*/
namespace Drupal\Tests\Core\Ajax;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\Render\Element\Ajax;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* @coversDefaultClass \Drupal\Core\Ajax\AjaxResponse
* @group Ajax
*/
class AjaxResponseTest extends UnitTestCase {
/**
* The tested ajax response object.
*
* @var \Drupal\Core\Ajax\AjaxResponse
*/
protected $ajaxResponse;
protected function setUp() {
$this->ajaxResponse = new AjaxResponse();
}
/**
* Tests the add and getCommands method.
*
* @see \Drupal\Core\Ajax\AjaxResponse::addCommand()
* @see \Drupal\Core\Ajax\AjaxResponse::getCommands()
*/
public function testCommands() {
$command_one = $this->getMock('Drupal\Core\Ajax\CommandInterface');
$command_one->expects($this->once())
->method('render')
->will($this->returnValue(array('command' => 'one')));
$command_two = $this->getMock('Drupal\Core\Ajax\CommandInterface');
$command_two->expects($this->once())
->method('render')
->will($this->returnValue(array('command' => 'two')));
$command_three = $this->getMock('Drupal\Core\Ajax\CommandInterface');
$command_three->expects($this->once())
->method('render')
->will($this->returnValue(array('command' => 'three')));
$this->ajaxResponse->addCommand($command_one);
$this->ajaxResponse->addCommand($command_two);
$this->ajaxResponse->addCommand($command_three, TRUE);
// Ensure that the added commands are in the right order.
$commands =& $this->ajaxResponse->getCommands();
$this->assertSame($commands[1], array('command' => 'one'));
$this->assertSame($commands[2], array('command' => 'two'));
$this->assertSame($commands[0], array('command' => 'three'));
// Remove one and change one element from commands and ensure the reference
// worked as expected.
unset($commands[2]);
$commands[0]['class'] = 'test-class';
$commands = $this->ajaxResponse->getCommands();
$this->assertSame($commands[1], array('command' => 'one'));
$this->assertFalse(isset($commands[2]));
$this->assertSame($commands[0], array('command' => 'three', 'class' => 'test-class'));
}
/**
* Tests the support for IE specific headers in file uploads.
*
* @cover ::prepareResponse
*/
public function testPrepareResponseForIeFormRequestsWithFileUpload() {
$request = Request::create('/example', 'POST');
$request->headers->set('Accept', 'text/html');
$response = new AjaxResponse([]);
$response->headers->set('Content-Type', 'application/json; charset=utf-8');
$ajax_response_attachments_processor = $this->getMock('\Drupal\Core\Render\AttachmentsResponseProcessorInterface');
$subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
$event = new FilterResponseEvent(
$this->getMock('\Symfony\Component\HttpKernel\HttpKernelInterface'),
$request,
HttpKernelInterface::MASTER_REQUEST,
$response
);
$subscriber->onResponse($event);
$this->assertEquals('text/html; charset=utf-8', $response->headers->get('Content-Type'));
$this->assertEquals($response->getContent(), '<textarea>[]</textarea>');
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Annotation\TranslationTest.
*/
namespace Drupal\Tests\Core\Annotation;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Annotation\Translation
* @group Annotation
*/
class TranslationTest extends UnitTestCase {
/**
* The translation manager used for testing.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $translationManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->translationManager = $this->getStringTranslationStub();
}
/**
* @covers ::get
*
* @dataProvider providerTestGet
*/
public function testGet(array $values, $expected) {
$container = new ContainerBuilder();
$container->set('string_translation', $this->translationManager);
\Drupal::setContainer($container);
$arguments = isset($values['arguments']) ? $values['arguments'] : array();
$options = isset($values['context']) ? array(
'context' => $values['context'],
) : array();
$this->translationManager->expects($this->once())
->method('translate')
->with($values['value'], $arguments, $options);
$annotation = new Translation($values);
$this->assertSame($expected, (string) $annotation->get());
}
/**
* Provides data to self::testGet().
*/
public function providerTestGet() {
$data = array();
$data[] = array(
array(
'value' => 'Foo',
),
'Foo'
);
$random = $this->randomMachineName();
$random_html_entity = '&' . $random;
$data[] = array(
array(
'value' => 'Foo !bar @baz %qux',
'arguments' => array(
'!bar' => $random,
'@baz' => $random_html_entity,
'%qux' => $random_html_entity,
),
'context' => $this->randomMachineName(),
),
'Foo ' . $random . ' &amp;' . $random . ' <em class="placeholder">&amp;' . $random . '</em>',
);
return $data;
}
}

View file

@ -0,0 +1,178 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\CssCollectionGrouperUnitTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\CssCollectionGrouper;
use Drupal\Tests\UnitTestCase;
/**
* Tests the CSS asset collection grouper.
*
* @group Asset
*/
class CssCollectionGrouperUnitTest extends UnitTestCase {
/**
* A CSS asset grouper.
*
* @var \Drupal\Core\Asset\CssCollectionGrouper object.
*/
protected $grouper;
protected function setUp() {
parent::setUp();
$this->grouper = new CssCollectionGrouper();
}
/**
* Tests \Drupal\Core\Asset\CssCollectionGrouper.
*/
function testGrouper() {
$css_assets = array(
'system.base.css' => array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.012,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'core/modules/system/system.base.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'system.base.css',
),
'system.theme.css' => array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'core/modules/system/system.theme.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'system.theme.css',
),
'jquery.ui.core.css' => array(
'group' => -100,
'type' => 'file',
'weight' => 0.004,
'every_page' => FALSE,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'core/misc/ui/themes/base/jquery.ui.core.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'jquery.ui.core.css',
),
'field.css' => array(
'every_page' => TRUE,
'group' => 0,
'type' => 'file',
'weight' => 0.011,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'core/modules/field/theme/field.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'field.css',
),
'external.css' => array(
'every_page' => FALSE,
'group' => 0,
'type' => 'external',
'weight' => 0.009,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'http://example.com/external.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'external.css',
),
'elements.css' => array(
'group' => 100,
'every_page' => TRUE,
'media' => 'all',
'type' => 'file',
'weight' => 0.001,
'preprocess' => TRUE,
'data' => 'core/themes/bartik/css/base/elements.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'elements.css',
),
'print.css' => array(
'group' => 100,
'every_page' => TRUE,
'media' => 'print',
'type' => 'file',
'weight' => 0.003,
'preprocess' => TRUE,
'data' => 'core/themes/bartik/css/print.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'print.css',
),
);
$groups = $this->grouper->group($css_assets);
$this->assertSame(count($groups), 6, "6 groups created.");
// Check group 1.
$this->assertSame($groups[0]['group'], -100);
$this->assertSame($groups[0]['every_page'], TRUE);
$this->assertSame($groups[0]['type'], 'file');
$this->assertSame($groups[0]['media'], 'all');
$this->assertSame($groups[0]['preprocess'], TRUE);
$this->assertSame(count($groups[0]['items']), 2);
$this->assertContains($css_assets['system.base.css'], $groups[0]['items']);
$this->assertContains($css_assets['system.theme.css'], $groups[0]['items']);
// Check group 2.
$this->assertSame($groups[1]['group'], -100);
$this->assertSame($groups[1]['every_page'], FALSE);
$this->assertSame($groups[1]['type'], 'file');
$this->assertSame($groups[1]['media'], 'all');
$this->assertSame($groups[1]['preprocess'], TRUE);
$this->assertSame(count($groups[1]['items']), 1);
$this->assertContains($css_assets['jquery.ui.core.css'], $groups[1]['items']);
// Check group 3.
$this->assertSame($groups[2]['group'], 0);
$this->assertSame($groups[2]['every_page'], TRUE);
$this->assertSame($groups[2]['type'], 'file');
$this->assertSame($groups[2]['media'], 'all');
$this->assertSame($groups[2]['preprocess'], TRUE);
$this->assertSame(count($groups[2]['items']), 1);
$this->assertContains($css_assets['field.css'], $groups[2]['items']);
// Check group 4.
$this->assertSame($groups[3]['group'], 0);
$this->assertSame($groups[3]['every_page'], FALSE);
$this->assertSame($groups[3]['type'], 'external');
$this->assertSame($groups[3]['media'], 'all');
$this->assertSame($groups[3]['preprocess'], TRUE);
$this->assertSame(count($groups[3]['items']), 1);
$this->assertContains($css_assets['external.css'], $groups[3]['items']);
// Check group 5.
$this->assertSame($groups[4]['group'], 100);
$this->assertSame($groups[4]['every_page'], TRUE);
$this->assertSame($groups[4]['type'], 'file');
$this->assertSame($groups[4]['media'], 'all');
$this->assertSame($groups[4]['preprocess'], TRUE);
$this->assertSame(count($groups[4]['items']), 1);
$this->assertContains($css_assets['elements.css'], $groups[4]['items']);
// Check group 6.
$this->assertSame($groups[5]['group'], 100);
$this->assertSame($groups[5]['every_page'], TRUE);
$this->assertSame($groups[5]['type'], 'file');
$this->assertSame($groups[5]['media'], 'print');
$this->assertSame($groups[5]['preprocess'], TRUE);
$this->assertSame(count($groups[5]['items']), 1);
$this->assertContains($css_assets['print.css'], $groups[5]['items']);
}
}

View file

@ -0,0 +1,507 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\CssCollectionRendererUnitTest.
*/
namespace {
/**
* CssRenderer uses file_create_url(), which *is* available when using the
* Simpletest test runner, but not when using the PHPUnit test runner; hence
* this hack.
*/
if (!function_exists('file_create_url')) {
/**
* Temporary mock for file_create_url(), until that is moved into
* Component/Utility.
*/
function file_create_url($uri) {
return 'file_create_url:' . $uri;
}
}
}
namespace Drupal\Tests\Core\Asset {
use Drupal\Core\Asset\CssCollectionRenderer;
use Drupal\Tests\UnitTestCase;
/**
* Tests the CSS asset collection renderer.
*
* @group Asset
*/
class CssCollectionRendererUnitTest extends UnitTestCase {
/**
* A CSS asset renderer.
*
* @var \Drupal\Core\Asset\CssRenderer object.
*/
protected $renderer;
/**
* A valid file CSS asset group.
*
* @var array
*/
protected $fileCssGroup;
/**
* The state mock class.
*
* @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $state;
protected function setUp() {
parent::setUp();
$this->state = $this->getMock('Drupal\Core\State\StateInterface');
$this->renderer = new CssCollectionRenderer($this->state);
$this->fileCssGroup = array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'media' => 'all',
'preprocess' => TRUE,
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'items' => array(
0 => array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.012,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'tests/Drupal/Tests/Core/Asset/foo.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'foo.css',
),
1 => array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'tests/Drupal/Tests/Core/Asset/bar.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'bar.css',
),
),
);
}
/**
* Provides data for the CSS asset rendering test.
*
* @see testRender
*/
function providerTestRender() {
$create_link_element = function($href, $media = 'all', $browsers = array()) {
return array(
'#type' => 'html_tag',
'#tag' => 'link',
'#attributes' => array(
'rel' => 'stylesheet',
'href' => $href,
'media' => $media,
),
'#browsers' => $browsers,
);
};
$create_style_element = function($value, $media, $browsers = array(), $wrap_in_cdata = FALSE) {
$style_element = array(
'#type' => 'html_tag',
'#tag' => 'style',
'#value' => $value,
'#attributes' => array(
'media' => $media
),
'#browsers' => $browsers,
);
if ($wrap_in_cdata) {
$style_element['#value_prefix'] = "\n/* <![CDATA[ */\n";
$style_element['#value_suffix'] = "\n/* ]]> */\n";
}
return $style_element;
};
$create_file_css_asset = function($data, $media = 'all', $preprocess = TRUE) {
return array('group' => 0, 'every_page' => FALSE, 'type' => 'file', 'media' => $media, 'preprocess' => $preprocess, 'data' => $data, 'browsers' => array());
};
return array(
// Single external CSS asset.
0 => array(
// CSS assets.
array(
0 => array('group' => 0, 'every_page' => TRUE, 'type' => 'external', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'http://example.com/popular.js', 'browsers' => array()),
),
// Render elements.
array(
0 => $create_link_element('http://example.com/popular.js', 'all'),
),
),
// Single file CSS asset.
2 => array(
array(
0 => array('group' => 0, 'every_page' => TRUE, 'type' => 'file', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'public://css/file-every_page-all', 'browsers' => array()),
),
array(
0 => $create_link_element(file_create_url('public://css/file-every_page-all') . '?0', 'all'),
),
),
// 31 file CSS assets: expect 31 link elements.
3 => array(
array(
0 => $create_file_css_asset('public://css/1.css'),
1 => $create_file_css_asset('public://css/2.css'),
2 => $create_file_css_asset('public://css/3.css'),
3 => $create_file_css_asset('public://css/4.css'),
4 => $create_file_css_asset('public://css/5.css'),
5 => $create_file_css_asset('public://css/6.css'),
6 => $create_file_css_asset('public://css/7.css'),
7 => $create_file_css_asset('public://css/8.css'),
8 => $create_file_css_asset('public://css/9.css'),
9 => $create_file_css_asset('public://css/10.css'),
10 => $create_file_css_asset('public://css/11.css'),
11 => $create_file_css_asset('public://css/12.css'),
12 => $create_file_css_asset('public://css/13.css'),
13 => $create_file_css_asset('public://css/14.css'),
14 => $create_file_css_asset('public://css/15.css'),
15 => $create_file_css_asset('public://css/16.css'),
16 => $create_file_css_asset('public://css/17.css'),
17 => $create_file_css_asset('public://css/18.css'),
18 => $create_file_css_asset('public://css/19.css'),
19 => $create_file_css_asset('public://css/20.css'),
20 => $create_file_css_asset('public://css/21.css'),
21 => $create_file_css_asset('public://css/22.css'),
22 => $create_file_css_asset('public://css/23.css'),
23 => $create_file_css_asset('public://css/24.css'),
24 => $create_file_css_asset('public://css/25.css'),
25 => $create_file_css_asset('public://css/26.css'),
26 => $create_file_css_asset('public://css/27.css'),
27 => $create_file_css_asset('public://css/28.css'),
28 => $create_file_css_asset('public://css/29.css'),
29 => $create_file_css_asset('public://css/30.css'),
30 => $create_file_css_asset('public://css/31.css'),
),
array(
0 => $create_link_element(file_create_url('public://css/1.css') . '?0'),
1 => $create_link_element(file_create_url('public://css/2.css') . '?0'),
2 => $create_link_element(file_create_url('public://css/3.css') . '?0'),
3 => $create_link_element(file_create_url('public://css/4.css') . '?0'),
4 => $create_link_element(file_create_url('public://css/5.css') . '?0'),
5 => $create_link_element(file_create_url('public://css/6.css') . '?0'),
6 => $create_link_element(file_create_url('public://css/7.css') . '?0'),
7 => $create_link_element(file_create_url('public://css/8.css') . '?0'),
8 => $create_link_element(file_create_url('public://css/9.css') . '?0'),
9 => $create_link_element(file_create_url('public://css/10.css') . '?0'),
10 => $create_link_element(file_create_url('public://css/11.css') . '?0'),
11 => $create_link_element(file_create_url('public://css/12.css') . '?0'),
12 => $create_link_element(file_create_url('public://css/13.css') . '?0'),
13 => $create_link_element(file_create_url('public://css/14.css') . '?0'),
14 => $create_link_element(file_create_url('public://css/15.css') . '?0'),
15 => $create_link_element(file_create_url('public://css/16.css') . '?0'),
16 => $create_link_element(file_create_url('public://css/17.css') . '?0'),
17 => $create_link_element(file_create_url('public://css/18.css') . '?0'),
18 => $create_link_element(file_create_url('public://css/19.css') . '?0'),
19 => $create_link_element(file_create_url('public://css/20.css') . '?0'),
20 => $create_link_element(file_create_url('public://css/21.css') . '?0'),
21 => $create_link_element(file_create_url('public://css/22.css') . '?0'),
22 => $create_link_element(file_create_url('public://css/23.css') . '?0'),
23 => $create_link_element(file_create_url('public://css/24.css') . '?0'),
24 => $create_link_element(file_create_url('public://css/25.css') . '?0'),
25 => $create_link_element(file_create_url('public://css/26.css') . '?0'),
26 => $create_link_element(file_create_url('public://css/27.css') . '?0'),
27 => $create_link_element(file_create_url('public://css/28.css') . '?0'),
28 => $create_link_element(file_create_url('public://css/29.css') . '?0'),
29 => $create_link_element(file_create_url('public://css/30.css') . '?0'),
30 => $create_link_element(file_create_url('public://css/31.css') . '?0'),
),
),
// 32 file CSS assets with the same properties: expect 2 style elements.
4 => array(
array(
0 => $create_file_css_asset('public://css/1.css'),
1 => $create_file_css_asset('public://css/2.css'),
2 => $create_file_css_asset('public://css/3.css'),
3 => $create_file_css_asset('public://css/4.css'),
4 => $create_file_css_asset('public://css/5.css'),
5 => $create_file_css_asset('public://css/6.css'),
6 => $create_file_css_asset('public://css/7.css'),
7 => $create_file_css_asset('public://css/8.css'),
8 => $create_file_css_asset('public://css/9.css'),
9 => $create_file_css_asset('public://css/10.css'),
10 => $create_file_css_asset('public://css/11.css'),
11 => $create_file_css_asset('public://css/12.css'),
12 => $create_file_css_asset('public://css/13.css'),
13 => $create_file_css_asset('public://css/14.css'),
14 => $create_file_css_asset('public://css/15.css'),
15 => $create_file_css_asset('public://css/16.css'),
16 => $create_file_css_asset('public://css/17.css'),
17 => $create_file_css_asset('public://css/18.css'),
18 => $create_file_css_asset('public://css/19.css'),
19 => $create_file_css_asset('public://css/20.css'),
20 => $create_file_css_asset('public://css/21.css'),
21 => $create_file_css_asset('public://css/22.css'),
22 => $create_file_css_asset('public://css/23.css'),
23 => $create_file_css_asset('public://css/24.css'),
24 => $create_file_css_asset('public://css/25.css'),
25 => $create_file_css_asset('public://css/26.css'),
26 => $create_file_css_asset('public://css/27.css'),
27 => $create_file_css_asset('public://css/28.css'),
28 => $create_file_css_asset('public://css/29.css'),
29 => $create_file_css_asset('public://css/30.css'),
30 => $create_file_css_asset('public://css/31.css'),
31 => $create_file_css_asset('public://css/32.css'),
),
array(
0 => $create_style_element('
@import url("' . file_create_url('public://css/1.css') . '?0");
@import url("' . file_create_url('public://css/2.css') . '?0");
@import url("' . file_create_url('public://css/3.css') . '?0");
@import url("' . file_create_url('public://css/4.css') . '?0");
@import url("' . file_create_url('public://css/5.css') . '?0");
@import url("' . file_create_url('public://css/6.css') . '?0");
@import url("' . file_create_url('public://css/7.css') . '?0");
@import url("' . file_create_url('public://css/8.css') . '?0");
@import url("' . file_create_url('public://css/9.css') . '?0");
@import url("' . file_create_url('public://css/10.css') . '?0");
@import url("' . file_create_url('public://css/11.css') . '?0");
@import url("' . file_create_url('public://css/12.css') . '?0");
@import url("' . file_create_url('public://css/13.css') . '?0");
@import url("' . file_create_url('public://css/14.css') . '?0");
@import url("' . file_create_url('public://css/15.css') . '?0");
@import url("' . file_create_url('public://css/16.css') . '?0");
@import url("' . file_create_url('public://css/17.css') . '?0");
@import url("' . file_create_url('public://css/18.css') . '?0");
@import url("' . file_create_url('public://css/19.css') . '?0");
@import url("' . file_create_url('public://css/20.css') . '?0");
@import url("' . file_create_url('public://css/21.css') . '?0");
@import url("' . file_create_url('public://css/22.css') . '?0");
@import url("' . file_create_url('public://css/23.css') . '?0");
@import url("' . file_create_url('public://css/24.css') . '?0");
@import url("' . file_create_url('public://css/25.css') . '?0");
@import url("' . file_create_url('public://css/26.css') . '?0");
@import url("' . file_create_url('public://css/27.css') . '?0");
@import url("' . file_create_url('public://css/28.css') . '?0");
@import url("' . file_create_url('public://css/29.css') . '?0");
@import url("' . file_create_url('public://css/30.css') . '?0");
@import url("' . file_create_url('public://css/31.css') . '?0");
', 'all'),
1 => $create_style_element('
@import url("' . file_create_url('public://css/32.css') . '?0");
', 'all'),
),
),
// 32 file CSS assets with the same properties, except for the 10th and
// 20th files, they have different 'media' properties. Expect 5 style
// elements.
5 => array(
array(
0 => $create_file_css_asset('public://css/1.css'),
1 => $create_file_css_asset('public://css/2.css'),
2 => $create_file_css_asset('public://css/3.css'),
3 => $create_file_css_asset('public://css/4.css'),
4 => $create_file_css_asset('public://css/5.css'),
5 => $create_file_css_asset('public://css/6.css'),
6 => $create_file_css_asset('public://css/7.css'),
7 => $create_file_css_asset('public://css/8.css'),
8 => $create_file_css_asset('public://css/9.css'),
9 => $create_file_css_asset('public://css/10.css', 'screen'),
10 => $create_file_css_asset('public://css/11.css'),
11 => $create_file_css_asset('public://css/12.css'),
12 => $create_file_css_asset('public://css/13.css'),
13 => $create_file_css_asset('public://css/14.css'),
14 => $create_file_css_asset('public://css/15.css'),
15 => $create_file_css_asset('public://css/16.css'),
16 => $create_file_css_asset('public://css/17.css'),
17 => $create_file_css_asset('public://css/18.css'),
18 => $create_file_css_asset('public://css/19.css'),
19 => $create_file_css_asset('public://css/20.css', 'print'),
20 => $create_file_css_asset('public://css/21.css'),
21 => $create_file_css_asset('public://css/22.css'),
22 => $create_file_css_asset('public://css/23.css'),
23 => $create_file_css_asset('public://css/24.css'),
24 => $create_file_css_asset('public://css/25.css'),
25 => $create_file_css_asset('public://css/26.css'),
26 => $create_file_css_asset('public://css/27.css'),
27 => $create_file_css_asset('public://css/28.css'),
28 => $create_file_css_asset('public://css/29.css'),
29 => $create_file_css_asset('public://css/30.css'),
30 => $create_file_css_asset('public://css/31.css'),
31 => $create_file_css_asset('public://css/32.css'),
),
array(
0 => $create_style_element('
@import url("' . file_create_url('public://css/1.css') . '?0");
@import url("' . file_create_url('public://css/2.css') . '?0");
@import url("' . file_create_url('public://css/3.css') . '?0");
@import url("' . file_create_url('public://css/4.css') . '?0");
@import url("' . file_create_url('public://css/5.css') . '?0");
@import url("' . file_create_url('public://css/6.css') . '?0");
@import url("' . file_create_url('public://css/7.css') . '?0");
@import url("' . file_create_url('public://css/8.css') . '?0");
@import url("' . file_create_url('public://css/9.css') . '?0");
', 'all'),
1 => $create_style_element('
@import url("' . file_create_url('public://css/10.css') . '?0");
', 'screen'),
2 => $create_style_element('
@import url("' . file_create_url('public://css/11.css') . '?0");
@import url("' . file_create_url('public://css/12.css') . '?0");
@import url("' . file_create_url('public://css/13.css') . '?0");
@import url("' . file_create_url('public://css/14.css') . '?0");
@import url("' . file_create_url('public://css/15.css') . '?0");
@import url("' . file_create_url('public://css/16.css') . '?0");
@import url("' . file_create_url('public://css/17.css') . '?0");
@import url("' . file_create_url('public://css/18.css') . '?0");
@import url("' . file_create_url('public://css/19.css') . '?0");
', 'all'),
3 => $create_style_element('
@import url("' . file_create_url('public://css/20.css') . '?0");
', 'print'),
4 => $create_style_element('
@import url("' . file_create_url('public://css/21.css') . '?0");
@import url("' . file_create_url('public://css/22.css') . '?0");
@import url("' . file_create_url('public://css/23.css') . '?0");
@import url("' . file_create_url('public://css/24.css') . '?0");
@import url("' . file_create_url('public://css/25.css') . '?0");
@import url("' . file_create_url('public://css/26.css') . '?0");
@import url("' . file_create_url('public://css/27.css') . '?0");
@import url("' . file_create_url('public://css/28.css') . '?0");
@import url("' . file_create_url('public://css/29.css') . '?0");
@import url("' . file_create_url('public://css/30.css') . '?0");
@import url("' . file_create_url('public://css/31.css') . '?0");
@import url("' . file_create_url('public://css/32.css') . '?0");
', 'all'),
),
),
// 32 file CSS assets with the same properties, except for the 15th, which
// has 'preprocess' = FALSE. Expect 1 link element and 2 style elements.
6 => array(
array(
0 => $create_file_css_asset('public://css/1.css'),
1 => $create_file_css_asset('public://css/2.css'),
2 => $create_file_css_asset('public://css/3.css'),
3 => $create_file_css_asset('public://css/4.css'),
4 => $create_file_css_asset('public://css/5.css'),
5 => $create_file_css_asset('public://css/6.css'),
6 => $create_file_css_asset('public://css/7.css'),
7 => $create_file_css_asset('public://css/8.css'),
8 => $create_file_css_asset('public://css/9.css'),
9 => $create_file_css_asset('public://css/10.css'),
10 => $create_file_css_asset('public://css/11.css'),
11 => $create_file_css_asset('public://css/12.css'),
12 => $create_file_css_asset('public://css/13.css'),
13 => $create_file_css_asset('public://css/14.css'),
14 => $create_file_css_asset('public://css/15.css', 'all', FALSE),
15 => $create_file_css_asset('public://css/16.css'),
16 => $create_file_css_asset('public://css/17.css'),
17 => $create_file_css_asset('public://css/18.css'),
18 => $create_file_css_asset('public://css/19.css'),
19 => $create_file_css_asset('public://css/20.css'),
20 => $create_file_css_asset('public://css/21.css'),
21 => $create_file_css_asset('public://css/22.css'),
22 => $create_file_css_asset('public://css/23.css'),
23 => $create_file_css_asset('public://css/24.css'),
24 => $create_file_css_asset('public://css/25.css'),
25 => $create_file_css_asset('public://css/26.css'),
26 => $create_file_css_asset('public://css/27.css'),
27 => $create_file_css_asset('public://css/28.css'),
28 => $create_file_css_asset('public://css/29.css'),
29 => $create_file_css_asset('public://css/30.css'),
30 => $create_file_css_asset('public://css/31.css'),
31 => $create_file_css_asset('public://css/32.css'),
),
array(
0 => $create_style_element('
@import url("' . file_create_url('public://css/1.css') . '?0");
@import url("' . file_create_url('public://css/2.css') . '?0");
@import url("' . file_create_url('public://css/3.css') . '?0");
@import url("' . file_create_url('public://css/4.css') . '?0");
@import url("' . file_create_url('public://css/5.css') . '?0");
@import url("' . file_create_url('public://css/6.css') . '?0");
@import url("' . file_create_url('public://css/7.css') . '?0");
@import url("' . file_create_url('public://css/8.css') . '?0");
@import url("' . file_create_url('public://css/9.css') . '?0");
@import url("' . file_create_url('public://css/10.css') . '?0");
@import url("' . file_create_url('public://css/11.css') . '?0");
@import url("' . file_create_url('public://css/12.css') . '?0");
@import url("' . file_create_url('public://css/13.css') . '?0");
@import url("' . file_create_url('public://css/14.css') . '?0");
', 'all'),
1 => $create_link_element(file_create_url('public://css/15.css') . '?0'),
2 => $create_style_element('
@import url("' . file_create_url('public://css/16.css') . '?0");
@import url("' . file_create_url('public://css/17.css') . '?0");
@import url("' . file_create_url('public://css/18.css') . '?0");
@import url("' . file_create_url('public://css/19.css') . '?0");
@import url("' . file_create_url('public://css/20.css') . '?0");
@import url("' . file_create_url('public://css/21.css') . '?0");
@import url("' . file_create_url('public://css/22.css') . '?0");
@import url("' . file_create_url('public://css/23.css') . '?0");
@import url("' . file_create_url('public://css/24.css') . '?0");
@import url("' . file_create_url('public://css/25.css') . '?0");
@import url("' . file_create_url('public://css/26.css') . '?0");
@import url("' . file_create_url('public://css/27.css') . '?0");
@import url("' . file_create_url('public://css/28.css') . '?0");
@import url("' . file_create_url('public://css/29.css') . '?0");
@import url("' . file_create_url('public://css/30.css') . '?0");
@import url("' . file_create_url('public://css/31.css') . '?0");
@import url("' . file_create_url('public://css/32.css') . '?0");
', 'all'),
),
),
);
}
/**
* Tests CSS asset rendering.
*
* @dataProvider providerTestRender
*/
function testRender(array $css_assets, array $render_elements) {
$this->state->expects($this->once())
->method('get')
->with('system.css_js_query_string')
->will($this->returnValue(NULL));
$this->assertSame($render_elements, $this->renderer->render($css_assets));
}
/**
* Tests a CSS asset group with the invalid 'type' => 'internal'.
*/
function testRenderInvalidType() {
$this->state->expects($this->once())
->method('get')
->with('system.css_js_query_string')
->will($this->returnValue(NULL));
$this->setExpectedException('Exception', 'Invalid CSS asset type.');
$css_group = array(
'group' => 0,
'every_page' => TRUE,
'type' => 'internal',
'media' => 'all',
'preprocess' => TRUE,
'browsers' => array(),
'data' => 'http://example.com/popular.js'
);
$this->renderer->render($css_group);
}
}
}

View file

@ -0,0 +1,284 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\CssOptimizerUnitTest.
*/
namespace {
/**
* CssOptimizer uses file_create_url(), which *is* available when using the
* Simpletest test runner, but not when using the PHPUnit test runner; hence
* this hack.
*/
if (!function_exists('file_create_url')) {
/**
* Temporary mock for file_create_url(), until that is moved into
* Component/Utility.
*/
function file_create_url($uri) {
return 'file_create_url:' . $uri;
}
}
if (!function_exists('file_uri_scheme')) {
function file_uri_scheme($uri) {
return FALSE;
}
}
}
namespace Drupal\Tests\Core\Asset {
use Drupal\Core\Asset\CssOptimizer;
use Drupal\Tests\UnitTestCase;
/**
* Tests the CSS asset optimizer.
*
* @group Asset
*/
class CssOptimizerUnitTest extends UnitTestCase {
/**
* A CSS asset optimizer.
*
* @var \Drupal\Core\Asset\CssOptimizer object.
*/
protected $optimizer;
protected function setUp() {
parent::setUp();
$this->optimizer = new CssOptimizer();
}
/**
* Provides data for the CSS asset optimizing test.
*/
function providerTestOptimize() {
$path = dirname(__FILE__) . '/css_test_files/';
return array(
// File. Tests:
// - Stripped comments and white-space.
// - Retain white-space in selectors. (https://www.drupal.org/node/472820)
// - Retain pseudo-selectors. (https://www.drupal.org/node/460448)
0 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.012,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_without_import.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_without_import.css',
),
file_get_contents($path . 'css_input_without_import.css.optimized.css'),
),
// File. Tests:
// - Proper URLs in imported files. (https://www.drupal.org/node/265719)
// - A background image with relative paths, which must be rewritten.
// - The rewritten background image path must also be passed through
// file_create_url(). (https://www.drupal.org/node/1961340)
// - Imported files that are external (protocol-relative URL or not)
// should not be expanded. (https://www.drupal.org/node/2014851)
1 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_with_import.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_import.css',
),
str_replace('url(images/icon.png)', 'url(' . file_create_url($path . 'images/icon.png') . ')', file_get_contents($path . 'css_input_with_import.css.optimized.css')),
),
// File. Tests:
// - Retain comment hacks.
2 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'comment_hacks.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'comment_hacks.css',
),
file_get_contents($path . 'comment_hacks.css.optimized.css'),
),
// File in subfolder. Tests:
// - CSS import path is properly interpreted.
// (https://www.drupal.org/node/1198904)
// - Don't adjust data URIs (https://www.drupal.org/node/2142441)
5 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_subfolder/css_input_with_import.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_import.css',
),
str_replace('url(../images/icon.png)', 'url(' . file_create_url($path . 'images/icon.png') . ')', file_get_contents($path . 'css_subfolder/css_input_with_import.css.optimized.css')),
),
// File. Tests:
// - Any @charaset declaration at the beginning of a file should be
// removed without breaking subsequent CSS.
6 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'charset_sameline.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'charset_sameline.css',
),
file_get_contents($path . 'charset.css.optimized.css'),
),
7 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'charset_newline.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'charset_newline.css',
),
file_get_contents($path . 'charset.css.optimized.css'),
),
6 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_with_bom.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_bom.css',
),
'.byte-order-mark-test{content:"☃";}'. "\n",
),
7 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_with_charset.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_charset.css',
),
'.charset-test{content:"€";}' . "\n",
),
8 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_with_bom_and_charset.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_bom_and_charset.css',
),
'.byte-order-mark-charset-test{content:"☃";}' . "\n",
),
9 => array(
array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.013,
'media' => 'all',
'preprocess' => TRUE,
'data' => $path . 'css_input_with_utf16_bom.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'css_input_with_utf16_bom.css',
),
'.utf16-byte-order-mark-test{content:"☃";}' . "\n",
),
);
}
/**
* Tests optimizing a CSS asset group containing 'type' => 'file'.
*
* @dataProvider providerTestOptimize
*/
function testOptimize($css_asset, $expected) {
$this->assertEquals($expected, $this->optimizer->optimize($css_asset), 'Group of file CSS assets optimized correctly.');
}
/**
* Tests a file CSS asset with preprocessing disabled.
*/
function testTypeFilePreprocessingDisabled() {
$this->setExpectedException('Exception', 'Only file CSS assets with preprocessing enabled can be optimized.');
$css_asset = array(
'group' => -100,
'every_page' => TRUE,
'type' => 'file',
'weight' => 0.012,
'media' => 'all',
// Preprocessing disabled.
'preprocess' => FALSE,
'data' => 'tests/Drupal/Tests/Core/Asset/foo.css',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
'basename' => 'foo.css',
);
$this->optimizer->optimize($css_asset);
}
/**
* Tests a CSS asset with 'type' => 'external'.
*/
function testTypeExternal() {
$this->setExpectedException('Exception', 'Only file or inline CSS assets can be optimized.');
$css_asset = array(
'group' => -100,
'every_page' => TRUE,
// Type external.
'type' => 'external',
'weight' => 0.012,
'media' => 'all',
'preprocess' => TRUE,
'data' => 'http://example.com/foo.js',
'browsers' => array('IE' => TRUE, '!IE' => TRUE),
);
$this->optimizer->optimize($css_asset);
}
}
}

View file

@ -0,0 +1,131 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\JsOptimizerUnitTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\JsOptimizer;
use Drupal\Tests\UnitTestCase;
/**
* Tests the JS asset optimizer.
*
* @group Asset
*/
class JsOptimizerUnitTest extends UnitTestCase {
/**
* A JS asset optimizer.
*
* @var \Drupal\Core\Asset\JsOptimizer object.
*/
protected $optimizer;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->optimizer = new JsOptimizer();
}
/**
* Provides data for the JS asset cleaning test.
*
* @see \Drupal\Core\Asset\JsOptimizer::clean().
*
* @returns array
* An array of test data.
*/
function providerTestClean() {
$path = dirname(__FILE__) . '/js_test_files/';
return array(
// File. Tests:
// - Stripped sourceMappingURL with comment # syntax.
0 => array(
file_get_contents($path . 'source_mapping_url.min.js'),
file_get_contents($path . 'source_mapping_url.min.js.optimized.js'),
),
// File. Tests:
// - Stripped sourceMappingURL with comment @ syntax.
1 => array(
file_get_contents($path . 'source_mapping_url_old.min.js'),
file_get_contents($path . 'source_mapping_url_old.min.js.optimized.js'),
),
// File. Tests:
// - Stripped sourceURL with comment # syntax.
2 => array(
file_get_contents($path . 'source_url.min.js'),
file_get_contents($path . 'source_url.min.js.optimized.js'),
),
// File. Tests:
// - Stripped sourceURL with comment @ syntax.
3 => array(
file_get_contents($path . 'source_url_old.min.js'),
file_get_contents($path . 'source_url_old.min.js.optimized.js'),
),
);
}
/**
* Tests cleaning of a JS asset group containing 'type' => 'file'.
*
* @dataProvider providerTestClean
*/
function testClean($js_asset, $expected) {
$this->assertEquals($expected, $this->optimizer->clean($js_asset));
}
/**
* Provides data for the JS asset optimize test.
*
* @see \Drupal\Core\Asset\JsOptimizer::optimize().
*
* @returns array
* An array of test data.
*/
function providerTestOptimize() {
$path = dirname(__FILE__) . '/js_test_files/';
return array(
0 => array(
array(
'type' => 'file',
'preprocess' => TRUE,
'data' => $path . 'utf8_bom.js',
),
file_get_contents($path . 'utf8_bom.js.optimized.js'),
),
1 => array(
array(
'type' => 'file',
'preprocess' => TRUE,
'data' => $path . 'utf16_bom.js',
),
file_get_contents($path . 'utf16_bom.js.optimized.js'),
),
2 => array(
array(
'type' => 'file',
'preprocess' => TRUE,
'data' => $path . 'latin_9.js',
'attributes' => array('charset' => 'ISO-8859-15'),
),
file_get_contents($path . 'latin_9.js.optimized.js'),
),
);
}
/**
* Tests cleaning of a JS asset group containing 'type' => 'file'.
*
* @dataProvider providerTestOptimize
*/
function testOptimize($js_asset, $expected) {
$this->assertEquals($expected, $this->optimizer->optimize($js_asset));
}
}

View file

@ -0,0 +1,177 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\LibraryDependencyResolverTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\LibraryDependencyResolver;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Asset\LibraryDependencyResolver
* @group Asset
*/
class LibraryDependencyResolverTest extends UnitTestCase {
/**
* The tested library dependency resolver.
*
* @var \Drupal\Core\Asset\LibraryDependencyResolver
*/
protected $libraryDependencyResolver;
/**
* The mocked library discovery service.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $libraryDiscovery;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* Test library data.
*
* @var array
*/
protected $libraryData = array(
'no_deps_a' => ['js' => [], 'css' => []],
'no_deps_b' => ['js' => [], 'css' => []],
'no_deps_c' => ['js' => [], 'css' => []],
'deps_a' => ['js' => [], 'css' => [], 'dependencies' => ['test/no_deps_a']],
'deps_b' => ['js' => [], 'css' => [], 'dependencies' => ['test/no_deps_a', 'test/no_deps_b']],
'deps_c' => ['js' => [], 'css' => [], 'dependencies' => ['test/no_deps_b', 'test/no_deps_a']],
'nested_deps_a' => ['js' => [], 'css' => [], 'dependencies' => ['test/deps_a']],
'nested_deps_b' => ['js' => [], 'css' => [], 'dependencies' => ['test/nested_deps_a']],
'nested_deps_c' => ['js' => [], 'css' => [], 'dependencies' => ['test/nested_deps_b']],
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->libraryDiscovery = $this->getMockBuilder('Drupal\Core\Asset\LibraryDiscovery')
->disableOriginalConstructor()
->setMethods(['getLibrariesByExtension'])
->getMock();
$this->libraryDiscovery->expects($this->any())
->method('getLibrariesByExtension')
->with('test')
->will($this->returnValue($this->libraryData));
$this->libraryDependencyResolver= new LibraryDependencyResolver($this->libraryDiscovery);
}
/**
* Provides test data for ::testGetLibrariesWithDependencies().
*/
public function providerTestGetLibrariesWithDependencies() {
return [
// Empty list of libraries.
[[], []],
// Without dependencies.
[['test/no_deps_a'], ['test/no_deps_a']],
[['test/no_deps_a', 'test/no_deps_b'], ['test/no_deps_a', 'test/no_deps_b']],
[['test/no_deps_b', 'test/no_deps_a'], ['test/no_deps_b', 'test/no_deps_a']],
// Single-level (direct) dependencies.
[['test/deps_a'], ['test/no_deps_a', 'test/deps_a']],
[['test/deps_b'], ['test/no_deps_a', 'test/no_deps_b', 'test/deps_b']],
[['test/deps_c'], ['test/no_deps_b', 'test/no_deps_a', 'test/deps_c']],
[['test/deps_a', 'test/deps_b'], ['test/no_deps_a', 'test/deps_a', 'test/no_deps_b', 'test/deps_b']],
[['test/deps_a', 'test/deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/no_deps_b', 'test/deps_c']],
[['test/deps_a', 'test/deps_b', 'test/deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/no_deps_b', 'test/deps_b', 'test/deps_c']],
[['test/deps_b', 'test/deps_a'], ['test/no_deps_a', 'test/no_deps_b', 'test/deps_b', 'test/deps_a']],
[['test/deps_b', 'test/deps_c'], ['test/no_deps_a', 'test/no_deps_b', 'test/deps_b', 'test/deps_c']],
[['test/deps_c', 'test/deps_b'], ['test/no_deps_b', 'test/no_deps_a', 'test/deps_c', 'test/deps_b']],
// Multi-level (indirect) dependencies.
[['test/nested_deps_a'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a']],
[['test/nested_deps_b'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b']],
[['test/nested_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_b'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b']],
[['test/nested_deps_b', 'test/nested_deps_a'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b']],
[['test/nested_deps_a', 'test/nested_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_a'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_c', 'test/nested_deps_b'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_a', 'test/nested_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_c', 'test/nested_deps_a'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_a', 'test/nested_deps_b'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_b', 'test/nested_deps_a'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c']],
// Complex dependencies, combining the above, with many intersections.
[['test/deps_c', 'test/nested_deps_b'], ['test/no_deps_b', 'test/no_deps_a', 'test/deps_c', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b']],
[['test/no_deps_a', 'test/deps_c', 'test/nested_deps_b'], ['test/no_deps_a', 'test/no_deps_b', 'test/deps_c', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b']],
[['test/nested_deps_b', 'test/deps_c', 'test/no_deps_c'], ['test/no_deps_a', 'test/deps_a', 'test/nested_deps_a', 'test/nested_deps_b', 'test/no_deps_b', 'test/deps_c', 'test/no_deps_c']],
];
}
/**
* @covers ::getLibrariesWithDependencies
*
* @dataProvider providerTestGetLibrariesWithDependencies
*/
public function testGetLibrariesWithDependencies(array $libraries, array $expected) {
$this->assertEquals($expected, $this->libraryDependencyResolver->getLibrariesWithDependencies($libraries));
}
/**
* Provides test data for ::testGetMinimalRepresentativeSubset().
*/
public function providerTestGetMinimalRepresentativeSubset() {
return [
// Empty list of libraries.
[[], []],
// Without dependencies.
[['test/no_deps_a'], ['test/no_deps_a']],
[['test/no_deps_a', 'test/no_deps_b'], ['test/no_deps_a', 'test/no_deps_b']],
[['test/no_deps_b', 'test/no_deps_a'], ['test/no_deps_b', 'test/no_deps_a']],
// Single-level (direct) dependencies.
[['test/deps_a'], ['test/deps_a']],
[['test/deps_b'], ['test/deps_b']],
[['test/deps_c'], ['test/deps_c']],
[['test/deps_a', 'test/deps_b'], ['test/deps_a', 'test/deps_b']],
[['test/deps_a', 'test/deps_c'], ['test/deps_a', 'test/deps_c']],
[['test/deps_a', 'test/deps_b', 'test/deps_c'], ['test/deps_a', 'test/deps_b', 'test/deps_c']],
[['test/deps_b', 'test/deps_a'], ['test/deps_b', 'test/deps_a']],
[['test/deps_b', 'test/deps_c'], ['test/deps_b', 'test/deps_c']],
[['test/deps_c', 'test/deps_b'], ['test/deps_c', 'test/deps_b']],
// Multi-level (indirect) dependencies.
[['test/nested_deps_a'], ['test/nested_deps_a']],
[['test/nested_deps_b'], ['test/nested_deps_b']],
[['test/nested_deps_c'], ['test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_b'], ['test/nested_deps_b']],
[['test/nested_deps_b', 'test/nested_deps_a'], ['test/nested_deps_b']],
[['test/nested_deps_a', 'test/nested_deps_c'], ['test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_c'], ['test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_a'], ['test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_b', 'test/nested_deps_c'], ['test/nested_deps_c']],
[['test/nested_deps_a', 'test/nested_deps_c', 'test/nested_deps_b'], ['test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_a', 'test/nested_deps_c'], ['test/nested_deps_c']],
[['test/nested_deps_b', 'test/nested_deps_c', 'test/nested_deps_a'], ['test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_a', 'test/nested_deps_b'], ['test/nested_deps_c']],
[['test/nested_deps_c', 'test/nested_deps_b', 'test/nested_deps_a'], ['test/nested_deps_c']],
// Complex dependencies, combining the above, with many intersections.
[['test/deps_c', 'test/nested_deps_b'], ['test/deps_c', 'test/nested_deps_b']],
[['test/no_deps_a', 'test/deps_c', 'test/nested_deps_b'], ['test/deps_c', 'test/nested_deps_b']],
[['test/nested_deps_b', 'test/deps_c', 'test/no_deps_c'], ['test/nested_deps_b', 'test/deps_c', 'test/no_deps_c']],
];
}
/**
* @covers ::getMinimalRepresentativeSubset
*
* @dataProvider providerTestGetMinimalRepresentativeSubset
*/
public function testGetMinimalRepresentativeSubset(array $libraries, array $expected) {
$this->assertEquals($expected, $this->libraryDependencyResolver->getMinimalRepresentativeSubset($libraries));
}
}

View file

@ -0,0 +1,158 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\LibraryDiscoveryCollectorTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\LibraryDiscoveryCollector;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Asset\LibraryDiscoveryCollector
* @group Asset
*/
class LibraryDiscoveryCollectorTest extends UnitTestCase {
/**
* The mock cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cache;
/**
* The mock lock backend.
*
* @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $lock;
/**
* The mock library discovery parser.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryParser|\PHPUnit_Framework_MockObject_MockObject
*/
protected $libraryDiscoveryParser;
/**
* The library discovery collector under test.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryCollector
*/
protected $libraryDiscoveryCollector;
/**
* The mocked theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $themeManager;
/**
* Test library data.
*
* @var array
*/
protected $libraryData = array(
'test_1' => array(
'js' => array(),
'css' => array(),
),
'test_2' => array(
'js' => array(),
'css' => array(),
),
);
protected $activeTheme;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
$this->themeManager = $this->getMockBuilder('Drupal\Core\Theme\ThemeManagerInterface')
->disableOriginalConstructor()
->getMock();
$this->libraryDiscoveryParser = $this->getMockBuilder('Drupal\Core\Asset\LibraryDiscoveryParser')
->disableOriginalConstructor()
->getMock();
}
/**
* Tests the resolve cache miss function.
*
* @covers ::resolveCacheMiss
*/
public function testResolveCacheMiss() {
$this->activeTheme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$this->themeManager->expects($this->once())
->method('getActiveTheme')
->willReturn($this->activeTheme);
$this->activeTheme->expects($this->once())
->method('getName')
->willReturn('kitten_theme');
$this->libraryDiscoveryCollector = new LibraryDiscoveryCollector($this->cache, $this->lock, $this->libraryDiscoveryParser, $this->themeManager);
$this->libraryDiscoveryParser->expects($this->once())
->method('buildByExtension')
->with('test')
->willReturn($this->libraryData);
$this->assertSame($this->libraryData, $this->libraryDiscoveryCollector->get('test'));
$this->assertSame($this->libraryData, $this->libraryDiscoveryCollector->get('test'));
}
/**
* Tests the destruct method.
*
* @covers ::destruct
*/
public function testDestruct() {
$this->activeTheme = $this->getMockBuilder('Drupal\Core\Theme\ActiveTheme')
->disableOriginalConstructor()
->getMock();
$this->themeManager->expects($this->once())
->method('getActiveTheme')
->willReturn($this->activeTheme);
$this->activeTheme->expects($this->once())
->method('getName')
->willReturn('kitten_theme');
$this->libraryDiscoveryCollector = new LibraryDiscoveryCollector($this->cache, $this->lock, $this->libraryDiscoveryParser, $this->themeManager);
$this->libraryDiscoveryParser->expects($this->once())
->method('buildByExtension')
->with('test')
->willReturn($this->libraryData);
$lock_key = 'library_info:kitten_theme:Drupal\Core\Cache\CacheCollector';
$this->lock->expects($this->once())
->method('acquire')
->with($lock_key)
->will($this->returnValue(TRUE));
$this->cache->expects($this->exactly(2))
->method('get')
->with('library_info:kitten_theme')
->willReturn(FALSE);
$this->cache->expects($this->once())
->method('set')
->with('library_info:kitten_theme', array('test' => $this->libraryData), Cache::PERMANENT, ['library_info']);
$this->lock->expects($this->once())
->method('release')
->with($lock_key);
// This should get data and persist the key.
$this->libraryDiscoveryCollector->get('test');
$this->libraryDiscoveryCollector->destruct();
}
}

View file

@ -0,0 +1,568 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\LibraryDiscoveryParserTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\LibraryDiscoveryParser;
use Drupal\Tests\UnitTestCase;
if (!defined('CSS_AGGREGATE_DEFAULT')) {
define('CSS_AGGREGATE_DEFAULT', 0);
define('CSS_AGGREGATE_THEME', 100);
define('CSS_BASE', -200);
define('CSS_LAYOUT', -100);
define('CSS_COMPONENT', 0);
define('CSS_STATE', 100);
define('CSS_THEME', 200);
define('JS_SETTING', -200);
define('JS_LIBRARY', -100);
define('JS_DEFAULT', 0);
define('JS_THEME', 100);
}
/**
* @coversDefaultClass \Drupal\Core\Asset\LibraryDiscoveryParser
* @group Asset
*/
class LibraryDiscoveryParserTest extends UnitTestCase {
/**
* The tested library discovery parser service.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryParser|\Drupal\Tests\Core\Asset\TestLibraryDiscoveryParser
*/
protected $libraryDiscoveryParser;
/**
* The mocked cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cache;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* The mocked theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $themeManager;
/**
* The mocked lock backend.
*
* @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $lock;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
$this->libraryDiscoveryParser = new TestLibraryDiscoveryParser($this->root, $this->moduleHandler, $this->themeManager);
}
/**
* Tests that basic functionality works for getLibraryByName.
*
* @covers ::buildByExtension
*/
public function testBuildByExtensionSimple() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('example_module')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'example_module', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('example_module', 'example');
$library = $libraries['example'];
$this->assertCount(0, $library['js']);
$this->assertCount(1, $library['css']);
$this->assertCount(0, $library['dependencies']);
$this->assertEquals($path . '/css/example.css', $library['css'][0]['data']);
// Ensures that VERSION is replaced by the current core version.
$this->assertEquals(\Drupal::VERSION, $library['version']);
}
/**
* Tests that a theme can be used instead of a module.
*
* @covers ::buildByExtension
*/
public function testBuildByExtensionWithTheme() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('example_theme')
->will($this->returnValue(FALSE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('theme', 'example_theme', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('example_theme');
$library = $libraries['example'];
$this->assertCount(0, $library['js']);
$this->assertCount(1, $library['css']);
$this->assertCount(0, $library['dependencies']);
$this->assertEquals($path . '/css/example.css', $library['css'][0]['data']);
}
/**
* Tests that a module with a missing library file results in FALSE.
*
* @covers ::buildByExtension
*/
public function testBuildByExtensionWithMissingLibraryFile() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('example_module')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files_not_existing';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'example_module', $path);
$this->assertSame($this->libraryDiscoveryParser->buildByExtension('example_module'), array());
}
/**
* Tests that an exception is thrown when a libraries file couldn't be parsed.
*
* @expectedException \Drupal\Core\Asset\Exception\InvalidLibraryFileException
*
* @covers ::buildByExtension
*/
public function testInvalidLibrariesFile() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('invalid_file')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'invalid_file', $path);
$this->libraryDiscoveryParser->buildByExtension('invalid_file');
}
/**
* Tests that an exception is thrown when no CSS/JS/setting is specified.
*
* @expectedException \Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException
* @expectedExceptionMessage Incomplete library definition for definition 'example' in extension 'example_module_missing_information'
*
* @covers ::buildByExtension
*/
public function testBuildByExtensionWithMissingInformation() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('example_module_missing_information')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'example_module_missing_information', $path);
$this->libraryDiscoveryParser->buildByExtension('example_module_missing_information');
}
/**
* Tests the version property, and how it propagates to the contained assets.
*
* @covers ::buildByExtension
*/
public function testVersion() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('versions')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'versions', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('versions');
$this->assertFalse(array_key_exists('version', $libraries['versionless']));
$this->assertEquals(-1, $libraries['versionless']['css'][0]['version']);
$this->assertEquals(-1, $libraries['versionless']['js'][0]['version']);
$this->assertEquals('9.8.7.6', $libraries['versioned']['version']);
$this->assertEquals('9.8.7.6', $libraries['versioned']['css'][0]['version']);
$this->assertEquals('9.8.7.6', $libraries['versioned']['js'][0]['version']);
$this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['version']);
$this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['css'][0]['version']);
$this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['js'][0]['version']);
}
/**
* Tests the version property with ISO dates.
*
* We want to make sure that versions defined in the YAML file are the same
* versions that are parsed.
*
* For example, ISO dates are converted into UNIX time by the YAML parser.
*
* @covers ::buildByExtension
*/
public function testNonStringVersion() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('versions')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'versions', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('versions');
// As an example, we defined an ISO date in the YAML file and the YAML
// parser converts it into a UNIX timestamp.
$this->assertNotEquals('2014-12-13', $libraries['invalid-version']['version']);
// An example of an ISO date as a string which parses correctly.
$this->assertEquals('2014-12-13', $libraries['valid-version']['version']);
}
/**
* Tests that the version property of external libraries is handled.
*
* @covers ::buildByExtension
*/
public function testExternalLibraries() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('external')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'external', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('external');
$library = $libraries['example_external'];
$this->assertEquals('http://example.com/css/example_external.css', $library['css'][0]['data']);
$this->assertEquals('http://example.com/example_external.js', $library['js'][0]['data']);
$this->assertEquals('3.14', $library['version']);
}
/**
* Ensures that CSS weights are taken into account properly.
*
* @covers ::buildByExtension
*/
public function testDefaultCssWeights() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('css_weights')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'css_weights', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('css_weights');
$library = $libraries['example'];
$css = $library['css'];
$this->assertCount(10, $css);
// The following default weights are tested:
// - CSS_BASE: -200
// - CSS_LAYOUT: -100
// - CSS_COMPONENT: 0
// - CSS_STATE: 100
// - CSS_THEME: 200
$this->assertEquals(200, $css[0]['weight']);
$this->assertEquals(200 + 29, $css[1]['weight']);
$this->assertEquals(-200, $css[2]['weight']);
$this->assertEquals(-200 + 97, $css[3]['weight']);
$this->assertEquals(-100, $css[4]['weight']);
$this->assertEquals(-100 + 92, $css[5]['weight']);
$this->assertEquals(0, $css[6]['weight']);
$this->assertEquals(45, $css[7]['weight']);
$this->assertEquals(100, $css[8]['weight']);
$this->assertEquals(100 + 8, $css[9]['weight']);
}
/**
* Ensures that you cannot provide positive weights for JavaScript libraries.
*
* @expectedException \UnexpectedValueException
*
* @covers ::buildByExtension
*/
public function testJsWithPositiveWeight() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('js_positive_weight')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'js_positive_weight', $path);
$this->libraryDiscoveryParser->buildByExtension('js_positive_weight');
}
/**
* Tests a library with CSS/JavaScript and a setting.
*
* @covers ::buildByExtension
*/
public function testLibraryWithCssJsSetting() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('css_js_settings')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'css_js_settings', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('css_js_settings');
$library = $libraries['example'];
// Ensures that the group and type are set automatically.
$this->assertEquals(-100, $library['js'][0]['group']);
$this->assertEquals('file', $library['js'][0]['type']);
$this->assertEquals($path . '/js/example.js', $library['js'][0]['data']);
$this->assertEquals(0, $library['css'][0]['group']);
$this->assertEquals('file', $library['css'][0]['type']);
$this->assertEquals($path . '/css/base.css', $library['css'][0]['data']);
$this->assertEquals(array('key' => 'value'), $library['drupalSettings']);
}
/**
* Tests a library with dependencies.
*
* @covers ::buildByExtension
*/
public function testLibraryWithDependencies() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('dependencies')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'dependencies', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('dependencies');
$library = $libraries['example'];
$this->assertCount(2, $library['dependencies']);
$this->assertEquals('external/example_external', $library['dependencies'][0]);
$this->assertEquals('example_module/example', $library['dependencies'][1]);
}
/**
* Tests a library with a couple of data formats like full URL.
*
* @covers ::buildByExtension
*/
public function testLibraryWithDataTypes() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('data_types')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'data_types', $path);
$this->libraryDiscoveryParser->setFileValidUri('public://test.css', TRUE);
$this->libraryDiscoveryParser->setFileValidUri('public://test2.css', FALSE);
$libraries = $this->libraryDiscoveryParser->buildByExtension('data_types');
$library = $libraries['example'];
$this->assertCount(5, $library['css']);
$this->assertEquals('external', $library['css'][0]['type']);
$this->assertEquals('http://example.com/test.css', $library['css'][0]['data']);
$this->assertEquals('file', $library['css'][1]['type']);
$this->assertEquals('tmp/test.css', $library['css'][1]['data']);
$this->assertEquals('external', $library['css'][2]['type']);
$this->assertEquals('//cdn.com/test.css', $library['css'][2]['data']);
$this->assertEquals('file', $library['css'][3]['type']);
$this->assertEquals('public://test.css', $library['css'][3]['data']);
}
/**
* Tests a library with JavaScript-specific flags.
*
* @covers ::buildByExtension
*/
public function testLibraryWithJavaScript() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('js')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'js', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('js');
$library = $libraries['example'];
$this->assertCount(2, $library['js']);
$this->assertEquals(FALSE, $library['js'][0]['minified']);
$this->assertEquals(TRUE, $library['js'][1]['minified']);
}
/**
* Tests that an exception is thrown when license is missing when 3rd party.
*
* @expectedException \Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException
* @expectedExceptionMessage Missing license information in library definition for definition 'no-license-info-but-remote' extension 'licenses_missing_information': it has a remote, but no license.
*
* @covers ::buildByExtension
*/
public function testLibraryThirdPartyWithMissingLicense() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('licenses_missing_information')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'licenses_missing_information', $path);
$this->libraryDiscoveryParser->buildByExtension('licenses_missing_information');
}
/**
* Tests a library with various licenses, some GPL-compatible, some not.
*
* @covers ::buildByExtension
*/
public function testLibraryWithLicenses() {
$this->moduleHandler->expects($this->atLeastOnce())
->method('moduleExists')
->with('licenses')
->will($this->returnValue(TRUE));
$path = __DIR__ . '/library_test_files';
$path = substr($path, strlen($this->root) + 1);
$this->libraryDiscoveryParser->setPaths('module', 'licenses', $path);
$libraries = $this->libraryDiscoveryParser->buildByExtension('licenses');
// For libraries without license info, the default license is applied.
$library = $libraries['no-license-info'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$this->assertTrue(isset($library['license']));
$default_license = array(
'name' => 'GNU-GPL-2.0-or-later',
'url' => 'https://www.drupal.org/licensing/faq',
'gpl-compatible' => TRUE,
);
$this->assertEquals($library['license'], $default_license);
// GPL2-licensed libraries.
$library = $libraries['gpl2'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$expected_license = array(
'name' => 'gpl2',
'url' => 'https://url-to-gpl2-license',
'gpl-compatible' => TRUE,
);
$this->assertEquals($library['license'], $expected_license);
// MIT-licensed libraries.
$library = $libraries['mit'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$expected_license = array(
'name' => 'MIT',
'url' => 'https://url-to-mit-license',
'gpl-compatible' => TRUE,
);
$this->assertEquals($library['license'], $expected_license);
// Libraries in the Public Domain.
$library = $libraries['public-domain'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$expected_license = array(
'name' => 'Public Domain',
'url' => 'https://url-to-public-domain-license',
'gpl-compatible' => TRUE,
);
$this->assertEquals($library['license'], $expected_license);
// Apache-licensed libraries.
$library = $libraries['apache'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$expected_license = array(
'name' => 'apache',
'url' => 'https://url-to-apache-license',
'gpl-compatible' => FALSE,
);
$this->assertEquals($library['license'], $expected_license);
// Copyrighted libraries.
$library = $libraries['copyright'];
$this->assertCount(1, $library['css']);
$this->assertCount(1, $library['js']);
$expected_license = array(
'name' => '© Some company',
'gpl-compatible' => FALSE,
);
$this->assertEquals($library['license'], $expected_license);
}
}
/**
* Wraps the tested class to mock the external dependencies.
*/
class TestLibraryDiscoveryParser extends LibraryDiscoveryParser {
protected $paths;
protected $validUris;
protected function drupalGetPath($type, $name) {
return isset($this->paths[$type][$name]) ? $this->paths[$type][$name] : NULL;
}
public function setPaths($type, $name, $path) {
$this->paths[$type][$name] = $path;
}
protected function fileValidUri($source) {
return isset($this->validUris[$source]) ? $this->validUris[$source] : FALSE;
}
public function setFileValidUri($source, $valid) {
$this->validUris[$source] = $valid;
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Asset\LibraryDiscoveryTest.
*/
namespace Drupal\Tests\Core\Asset;
use Drupal\Core\Asset\LibraryDiscovery;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Asset\LibraryDiscovery
* @group Asset
*/
class LibraryDiscoveryTest extends UnitTestCase {
/**
* The tested library discovery service.
*
* @var \Drupal\Core\Asset\LibraryDiscovery
*/
protected $libraryDiscovery;
/**
* The mocked library discovery cache collector.
*
* @var \Drupal\Core\Cache\CacheCollectorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $libraryDiscoveryCollector;
/**
* The cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheTagsInvalidator;
/**
* Test library data.
*
* @var array
*/
protected $libraryData = [
'test_1' => [
'js' => [],
'css' => [
'foo.css' => [],
],
],
'test_2' => [
'js' => [
'bar.js' => [],
],
'css' => [],
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
$this->libraryDiscoveryCollector = $this->getMockBuilder('Drupal\Core\Asset\LibraryDiscoveryCollector')
->disableOriginalConstructor()
->getMock();
$this->libraryDiscovery = new LibraryDiscovery($this->libraryDiscoveryCollector, $this->cacheTagsInvalidator);
}
/**
* @covers ::getLibrariesByExtension
*/
public function testGetLibrariesByExtension() {
$this->libraryDiscoveryCollector->expects($this->once())
->method('get')
->with('test')
->willReturn($this->libraryData);
$this->libraryDiscovery->getLibrariesbyExtension('test');
// Verify that subsequent calls don't trigger hook_library_info_alter()
// and hook_js_settings_alter() invocations, nor do they talk to the
// collector again. This ensures that the alterations made by
// hook_library_info_alter() and hook_js_settings_alter() implementations
// are statically cached, as desired.
$this->libraryDiscovery->getLibraryByName('test', 'test_1');
$this->libraryDiscovery->getLibrariesbyExtension('test');
}
}

View file

@ -0,0 +1 @@
@charset "UTF-8";html{font-family:"sans-serif";}

View file

@ -0,0 +1 @@
html{font-family:"sans-serif";}

View file

@ -0,0 +1,2 @@
@charset "UTF-8";
html{font-family:"sans-serif";}

View file

@ -0,0 +1 @@
@charset "UTF-8";html{font-family:"sans-serif";}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
.test1{display:block;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#c00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";}

View file

@ -0,0 +1,3 @@
.byte-order-mark-test {
content: "☃";
}

View file

@ -0,0 +1,4 @@
@charset "utf-8";
.byte-order-mark-charset-test {
content: "☃";
}

View file

@ -0,0 +1,4 @@
@charset "iso-8859-15";
.charset-test {
content: "¤";
}

View file

@ -0,0 +1,32 @@
@import "import1.css";
@import "import2.css";
@import url("http://example.com/style.css");
@import url("//example.com/style.css");
body {
margin: 0;
padding: 0;
background: #edf5fa;
font: 76%/170% Verdana, sans-serif;
color: #494949;
}
.this .is .a .test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
.this
.is
.a
.test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
textarea, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}

View file

@ -0,0 +1,6 @@
ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
.a
.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}

View file

@ -0,0 +1,65 @@
/**
* @file Basic css that does not use import
*/
body {
margin: 0;
padding: 0;
background: #edf5fa;
font: 76%/170% Verdana, sans-serif;
color: #494949;
}
.this .is .a .test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
/**
* CSS spec says that all whitespace is valid whitespace, so this selector
* should be just as good as the one above.
*/
.this
.is
.a
.test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
some :pseudo .thing {
border-radius: 3px;
}
::-moz-selection {
background: #000;
color:#fff;
}
::selection {
background: #000;
color: #fff;
}
@media print {
* {
background: #000 !important;
color: #fff !important;
}
@page {
margin: 0.5cm;
}
}
@media screen and (max-device-width: 480px) {
background: #000;
color: #fff;
}
textarea, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}

View file

@ -0,0 +1,4 @@
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
.a
.test{font:1em/100% Verdana,sans-serif;color:#494949;}some :pseudo .thing{border-radius:3px;}::-moz-selection{background:#000;color:#fff;}::selection{background:#000;color:#fff;}@media print{*{background:#000 !important;color:#fff !important;}@page{margin:0.5cm;}}@media screen and (max-device-width:480px){background:#000;color:#fff;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}

View file

@ -0,0 +1,29 @@
@import "../import1.css";
@import "../import2.css";
body {
margin: 0;
padding: 0;
background: #edf5fa;
font: 76%/170% Verdana, sans-serif;
color: #494949;
}
.this .is .a .test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
.this
.is
.a
.test {
font: 1em/100% Verdana, sans-serif;
color: #494949;
}
textarea, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}

View file

@ -0,0 +1,6 @@
ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
.a
.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;}

View file

@ -0,0 +1,20 @@
ul, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}
.ui-icon{background-image: url(images/icon.png);}
/* Test data URI images with different quote styles. */
.data .double-quote {
/* http://stackoverflow.com/a/13139830/11023 */
background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
}
.data .single-quote {
background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');
}
.data .no-quote {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);
}

View file

@ -0,0 +1,5 @@
p, select {
font: 1em/160% Verdana, sans-serif;
color: #494949;
}

View file

@ -0,0 +1 @@
var latin9Char = '¤';

View file

@ -0,0 +1 @@
var latin9Char = '€';

View file

@ -0,0 +1,2 @@
(function($) { "use strict"; })
//# sourceMappingURL=source_mapping_url.min.map

View file

@ -0,0 +1 @@
(function($) { "use strict"; })

View file

@ -0,0 +1,2 @@
(function($) { "use strict"; })
//@ sourceMappingURL=source_mapping_url.min.map

View file

@ -0,0 +1 @@
(function($) { "use strict"; })

View file

@ -0,0 +1,2 @@
(function($) { "use strict"; })
//# sourceURL=source_mapping_url.js

View file

@ -0,0 +1 @@
(function($) { "use strict"; })

View file

@ -0,0 +1,2 @@
(function($) { "use strict"; })
//@ sourceURL=source_mapping_url.js

View file

@ -0,0 +1 @@
(function($) { "use strict"; })

Some files were not shown because too many files have changed in this diff Show more