Version 1
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / TypedData / TypedDataTest.php
diff --git a/web/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php b/web/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php
new file mode 100644 (file)
index 0000000..c79f2cf
--- /dev/null
@@ -0,0 +1,634 @@
+<?php
+
+namespace Drupal\KernelTests\Core\TypedData;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\ListDataDefinition;
+use Drupal\Core\TypedData\MapDataDefinition;
+use Drupal\Core\TypedData\Type\BinaryInterface;
+use Drupal\Core\TypedData\Type\BooleanInterface;
+use Drupal\Core\TypedData\Type\DateTimeInterface;
+use Drupal\Core\TypedData\Type\DurationInterface;
+use Drupal\Core\TypedData\Type\FloatInterface;
+use Drupal\Core\TypedData\Type\IntegerInterface;
+use Drupal\Core\TypedData\Type\StringInterface;
+use Drupal\Core\TypedData\Type\UriInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\file\Entity\File;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests the functionality of all core data types.
+ *
+ * @group TypedData
+ */
+class TypedDataTest extends KernelTestBase {
+
+  /**
+   * The typed data manager to use.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['system', 'field', 'file', 'user'];
+
+  protected function setUp() {
+    parent::setup();
+
+    $this->installEntitySchema('file');
+    $this->typedDataManager = $this->container->get('typed_data_manager');
+  }
+
+  /**
+   * Creates a typed data object and ensures it implements TypedDataInterface.
+   *
+   * @see \Drupal\Core\TypedData\TypedDataManager::create()
+   */
+  protected function createTypedData($definition, $value = NULL, $name = NULL) {
+    if (is_array($definition)) {
+      $definition = DataDefinition::create($definition['type']);
+    }
+    $data = $this->typedDataManager->create($definition, $value, $name);
+    $this->assertTrue($data instanceof TypedDataInterface, 'Typed data object is an instance of the typed data interface.');
+    return $data;
+  }
+
+  /**
+   * Tests the basics around constructing and working with typed data objects.
+   */
+  public function testGetAndSet() {
+    // Boolean type.
+    $typed_data = $this->createTypedData(['type' => 'boolean'], TRUE);
+    $this->assertTrue($typed_data instanceof BooleanInterface, 'Typed data object is an instance of BooleanInterface.');
+    $this->assertTrue($typed_data->getValue() === TRUE, 'Boolean value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(FALSE);
+    $this->assertTrue($typed_data->getValue() === FALSE, 'Boolean value was changed.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $this->assertTrue(is_string($typed_data->getString()), 'Boolean value was converted to string');
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Boolean wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // String type.
+    $value = $this->randomString();
+    $typed_data = $this->createTypedData(['type' => 'string'], $value);
+    $this->assertTrue($typed_data instanceof StringInterface, 'Typed data object is an instance of StringInterface.');
+    $this->assertTrue($typed_data->getValue() === $value, 'String value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $new_value = $this->randomString();
+    $typed_data->setValue($new_value);
+    $this->assertTrue($typed_data->getValue() === $new_value, 'String value was changed.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    // Funky test.
+    $this->assertTrue(is_string($typed_data->getString()), 'String value was converted to string');
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'String wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(['no string']);
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // Integer type.
+    $value = rand();
+    $typed_data = $this->createTypedData(['type' => 'integer'], $value);
+    $this->assertTrue($typed_data instanceof IntegerInterface, 'Typed data object is an instance of IntegerInterface.');
+    $this->assertTrue($typed_data->getValue() === $value, 'Integer value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $new_value = rand();
+    $typed_data->setValue($new_value);
+    $this->assertTrue($typed_data->getValue() === $new_value, 'Integer value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Integer value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Integer wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // Float type.
+    $value = 123.45;
+    $typed_data = $this->createTypedData(['type' => 'float'], $value);
+    $this->assertTrue($typed_data instanceof FloatInterface, 'Typed data object is an instance of FloatInterface.');
+    $this->assertTrue($typed_data->getValue() === $value, 'Float value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $new_value = 678.90;
+    $typed_data->setValue($new_value);
+    $this->assertTrue($typed_data->getValue() === $new_value, 'Float value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Float value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Float wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // Date Time type.
+    $value = '2014-01-01T20:00:00+00:00';
+    $typed_data = $this->createTypedData(['type' => 'datetime_iso8601'], $value);
+    $this->assertTrue($typed_data instanceof DateTimeInterface, 'Typed data object is an instance of DateTimeInterface.');
+    $this->assertTrue($typed_data->getValue() == $value, 'Date value was fetched.');
+    $this->assertEqual($typed_data->getValue(), $typed_data->getDateTime()->format('c'), 'Value representation of a date is ISO 8601');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $new_value = '2014-01-02T20:00:00+00:00';
+    $typed_data->setValue($new_value);
+    $this->assertTrue($typed_data->getDateTime()->format('c') === $new_value, 'Date value was changed and set by an ISO8601 date.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $this->assertTrue($typed_data->getDateTime()->format('Y-m-d') == '2014-01-02', 'Date value was changed and set by date string.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDateTime(), 'Date wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+    // Check implementation of DateTimeInterface.
+    $typed_data = $this->createTypedData(['type' => 'datetime_iso8601'], '2014-01-01T20:00:00+00:00');
+    $this->assertTrue($typed_data->getDateTime() instanceof DrupalDateTime);
+    $typed_data->setDateTime(new DrupalDateTime('2014-01-02T20:00:00+00:00'));
+    $this->assertEqual($typed_data->getValue(), '2014-01-02T20:00:00+00:00');
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDateTime());
+
+    // Timestamp type.
+    $value = REQUEST_TIME;
+    $typed_data = $this->createTypedData(['type' => 'timestamp'], $value);
+    $this->assertTrue($typed_data instanceof DateTimeInterface, 'Typed data object is an instance of DateTimeInterface.');
+    $this->assertTrue($typed_data->getValue() == $value, 'Timestamp value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $new_value = REQUEST_TIME + 1;
+    $typed_data->setValue($new_value);
+    $this->assertTrue($typed_data->getValue() === $new_value, 'Timestamp value was changed and set.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDateTime(), 'Timestamp wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+    // Check implementation of DateTimeInterface.
+    $typed_data = $this->createTypedData(['type' => 'timestamp'], REQUEST_TIME);
+    $this->assertTrue($typed_data->getDateTime() instanceof DrupalDateTime);
+    $typed_data->setDateTime(DrupalDateTime::createFromTimestamp(REQUEST_TIME + 1));
+    $this->assertEqual($typed_data->getValue(), REQUEST_TIME + 1);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDateTime());
+
+    // DurationIso8601 type.
+    $value = 'PT20S';
+    $typed_data = $this->createTypedData(['type' => 'duration_iso8601'], $value);
+    $this->assertTrue($typed_data instanceof DurationInterface, 'Typed data object is an instance of DurationInterface.');
+    $this->assertIdentical($typed_data->getValue(), $value, 'DurationIso8601 value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('P40D');
+    $this->assertEqual($typed_data->getDuration()->d, 40, 'DurationIso8601 value was changed and set by duration string.');
+    $this->assertTrue(is_string($typed_data->getString()), 'DurationIso8601 value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'DurationIso8601 wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+    // Check implementation of DurationInterface.
+    $typed_data = $this->createTypedData(['type' => 'duration_iso8601'], 'PT20S');
+    $this->assertTrue($typed_data->getDuration() instanceof \DateInterval);
+    $typed_data->setDuration(new \DateInterval('P40D'));
+    // @todo: Should we make this "nicer"?
+    $this->assertEqual($typed_data->getValue(), 'P0Y0M40DT0H0M0S');
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDuration());
+
+    // Time span type.
+    $value = 20;
+    $typed_data = $this->createTypedData(['type' => 'timespan'], $value);
+    $this->assertTrue($typed_data instanceof DurationInterface, 'Typed data object is an instance of DurationInterface.');
+    $this->assertIdentical($typed_data->getValue(), $value, 'Time span value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(60 * 60 * 4);
+    $this->assertEqual($typed_data->getDuration()->s, 14400, 'Time span was changed');
+    $this->assertTrue(is_string($typed_data->getString()), 'Time span value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Time span wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+    // Check implementation of DurationInterface.
+    $typed_data = $this->createTypedData(['type' => 'timespan'], 20);
+    $this->assertTrue($typed_data->getDuration() instanceof \DateInterval);
+    $typed_data->setDuration(new \DateInterval('PT4H'));
+    $this->assertEqual($typed_data->getValue(), 60 * 60 * 4);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getDuration());
+
+    // URI type.
+    $uri = 'http://example.com/foo/';
+    $typed_data = $this->createTypedData(['type' => 'uri'], $uri);
+    $this->assertTrue($typed_data instanceof UriInterface, 'Typed data object is an instance of UriInterface.');
+    $this->assertTrue($typed_data->getValue() === $uri, 'URI value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue($uri . 'bar.txt');
+    $this->assertTrue($typed_data->getValue() === $uri . 'bar.txt', 'URI value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'URI value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'URI wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+    $typed_data->setValue('public://field/image/Photo on 4-28-14 at 12.01 PM.jpg');
+    $this->assertEqual($typed_data->validate()->count(), 0, 'Filename with spaces is valid.');
+
+    // Generate some files that will be used to test the binary data type.
+    $files = [];
+    for ($i = 0; $i < 3; $i++) {
+      $path = "public://example_$i.png";
+      file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', $path);
+      $image = File::create(['uri' => $path]);
+      $image->save();
+      $files[] = $image;
+    }
+
+    // Email type.
+    $value = $this->randomString();
+    $typed_data = $this->createTypedData(['type' => 'email'], $value);
+    $this->assertTrue($typed_data instanceof StringInterface, 'Typed data object is an instance of StringInterface.');
+    $this->assertIdentical($typed_data->getValue(), $value, 'Email value was fetched.');
+    $new_value = 'test@example.com';
+    $typed_data->setValue($new_value);
+    $this->assertIdentical($typed_data->getValue(), $new_value, 'Email value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Email value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Email wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalidATexample.com');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // Binary type.
+    $typed_data = $this->createTypedData(['type' => 'binary'], $files[0]->getFileUri());
+    $this->assertTrue($typed_data instanceof BinaryInterface, 'Typed data object is an instance of BinaryInterface.');
+    $this->assertTrue(is_resource($typed_data->getValue()), 'Binary value was fetched.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    // Try setting by URI.
+    $typed_data->setValue($files[1]->getFileUri());
+    $this->assertEqual(fgets($typed_data->getValue()), fgets(fopen($files[1]->getFileUri(), 'r')), 'Binary value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Binary value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    // Try setting by resource.
+    $typed_data->setValue(fopen($files[2]->getFileUri(), 'r'));
+    $this->assertEqual(fgets($typed_data->getValue()), fgets(fopen($files[2]->getFileUri(), 'r')), 'Binary value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Binary value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Binary wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue('invalid');
+    $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
+
+    // Any type.
+    $value = ['foo'];
+    $typed_data = $this->createTypedData(['type' => 'any'], $value);
+    $this->assertIdentical($typed_data->getValue(), $value, 'Any value was fetched.');
+    $new_value = 'test@example.com';
+    $typed_data->setValue($new_value);
+    $this->assertIdentical($typed_data->getValue(), $new_value, 'Any value was changed.');
+    $this->assertTrue(is_string($typed_data->getString()), 'Any value was converted to string');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue(), 'Any wrapper is null-able.');
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    // We cannot test invalid values as everything is valid for the any type,
+    // but make sure an array or object value passes validation also.
+    $typed_data->setValue(['entry']);
+    $this->assertEqual($typed_data->validate()->count(), 0);
+    $typed_data->setValue((object) ['entry']);
+    $this->assertEqual($typed_data->validate()->count(), 0);
+  }
+
+  /**
+   * Tests using typed data lists.
+   */
+  public function testTypedDataLists() {
+    // Test working with an existing list of strings.
+    $value = ['one', 'two', 'three'];
+    $typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
+    $this->assertEqual($typed_data->getValue(), $value, 'List value has been set.');
+    // Test iterating.
+    $count = 0;
+    foreach ($typed_data as $item) {
+      $this->assertTrue($item instanceof TypedDataInterface);
+      $count++;
+    }
+    $this->assertEqual($count, 3);
+
+    // Test getting the string representation.
+    $this->assertEqual($typed_data->getString(), 'one, two, three');
+    $typed_data[1] = '';
+    $this->assertEqual($typed_data->getString(), 'one, three');
+
+    // Test using array access.
+    $this->assertEqual($typed_data[0]->getValue(), 'one');
+    $typed_data[] = 'four';
+    $this->assertEqual($typed_data[3]->getValue(), 'four');
+    $this->assertEqual($typed_data->count(), 4);
+    $this->assertTrue(isset($typed_data[0]));
+    $this->assertTrue(!isset($typed_data[6]));
+
+    // Test isEmpty and cloning.
+    $this->assertFalse($typed_data->isEmpty());
+    $clone = clone $typed_data;
+    $this->assertTrue($typed_data->getValue() === $clone->getValue());
+    $this->assertTrue($typed_data[0] !== $clone[0]);
+    $clone->setValue([]);
+    $this->assertTrue($clone->isEmpty());
+
+    // Make sure that resetting the value using NULL results in an empty array.
+    $clone->setValue([]);
+    $typed_data->setValue(NULL);
+    $this->assertIdentical($typed_data->getValue(), []);
+    $this->assertIdentical($clone->getValue(), []);
+
+    // Test dealing with NULL items.
+    $typed_data[] = NULL;
+    $this->assertTrue($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 1);
+    $typed_data[] = '';
+    $this->assertFalse($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 2);
+    $typed_data[] = 'three';
+    $this->assertFalse($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 3);
+
+    $this->assertEqual($typed_data->getValue(), [NULL, '', 'three']);
+    // Test unsetting.
+    unset($typed_data[1]);
+    $this->assertEqual(count($typed_data), 2);
+    // Check that items were shifted.
+    $this->assertEqual($typed_data[1]->getValue(), 'three');
+
+    // Getting a not set list item returns NULL, and does not create a new item.
+    $this->assertNull($typed_data[2]);
+    $this->assertEqual(count($typed_data), 2);
+
+    // Test setting the list with less values.
+    $typed_data->setValue(['one']);
+    $this->assertEqual($typed_data->count(), 1);
+
+    // Test setting invalid values.
+    try {
+      $typed_data->setValue('string');
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests the filter() method on typed data lists.
+   */
+  public function testTypedDataListsFilter() {
+    // Check that an all-pass filter leaves the list untouched.
+    $value = ['zero', 'one'];
+    $typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
+    $typed_data->filter(function(TypedDataInterface $item) {
+      return TRUE;
+    });
+    $this->assertEqual($typed_data->count(), 2);
+    $this->assertEqual($typed_data[0]->getValue(), 'zero');
+    $this->assertEqual($typed_data[0]->getName(), 0);
+    $this->assertEqual($typed_data[1]->getValue(), 'one');
+    $this->assertEqual($typed_data[1]->getName(), 1);
+
+    // Check that a none-pass filter empties the list.
+    $value = ['zero', 'one'];
+    $typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
+    $typed_data->filter(function(TypedDataInterface $item) {
+      return FALSE;
+    });
+    $this->assertEqual($typed_data->count(), 0);
+
+    // Check that filtering correctly renumbers elements.
+    $value = ['zero', 'one', 'two'];
+    $typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
+    $typed_data->filter(function(TypedDataInterface $item) {
+      return $item->getValue() !== 'one';
+    });
+    $this->assertEqual($typed_data->count(), 2);
+    $this->assertEqual($typed_data[0]->getValue(), 'zero');
+    $this->assertEqual($typed_data[0]->getName(), 0);
+    $this->assertEqual($typed_data[1]->getValue(), 'two');
+    $this->assertEqual($typed_data[1]->getName(), 1);
+  }
+
+  /**
+   * Tests using a typed data map.
+   */
+  public function testTypedDataMaps() {
+    // Test working with a simple map.
+    $value = [
+      'one' => 'eins',
+      'two' => 'zwei',
+      'three' => 'drei',
+    ];
+    $definition = MapDataDefinition::create()
+      ->setPropertyDefinition('one', DataDefinition::create('string'))
+      ->setPropertyDefinition('two', DataDefinition::create('string'))
+      ->setPropertyDefinition('three', DataDefinition::create('string'));
+
+    $typed_data = $this->createTypedData($definition, $value);
+
+    // Test iterating.
+    $count = 0;
+    foreach ($typed_data as $item) {
+      $this->assertTrue($item instanceof TypedDataInterface);
+      $count++;
+    }
+    $this->assertEqual($count, 3);
+
+    // Test retrieving metadata.
+    $this->assertEqual(array_keys($typed_data->getDataDefinition()->getPropertyDefinitions()), array_keys($value));
+    $definition = $typed_data->getDataDefinition()->getPropertyDefinition('one');
+    $this->assertEqual($definition->getDataType(), 'string');
+    $this->assertNull($typed_data->getDataDefinition()->getPropertyDefinition('invalid'));
+
+    // Test getting and setting properties.
+    $this->assertEqual($typed_data->get('one')->getValue(), 'eins');
+    $this->assertEqual($typed_data->toArray(), $value);
+    $typed_data->set('one', 'uno');
+    $this->assertEqual($typed_data->get('one')->getValue(), 'uno');
+    // Make sure the update is reflected in the value of the map also.
+    $value = $typed_data->getValue();
+    $this->assertEqual($value, [
+      'one' => 'uno',
+      'two' => 'zwei',
+      'three' => 'drei'
+    ]);
+
+    $properties = $typed_data->getProperties();
+    $this->assertEqual(array_keys($properties), array_keys($value));
+    $this->assertIdentical($properties['one'], $typed_data->get('one'), 'Properties are identical.');
+
+    // Test setting a not defined property. It shouldn't show up in the
+    // properties, but be kept in the values.
+    $typed_data->setValue(['foo' => 'bar']);
+    $this->assertEqual(array_keys($typed_data->getProperties()), ['one', 'two', 'three']);
+    $this->assertEqual(array_keys($typed_data->getValue()), ['foo', 'one', 'two', 'three']);
+
+    // Test getting the string representation.
+    $typed_data->setValue(['one' => 'eins', 'two' => '', 'three' => 'drei']);
+    $this->assertEqual($typed_data->getString(), 'eins, drei');
+
+    // Test isEmpty and cloning.
+    $this->assertFalse($typed_data->isEmpty());
+    $clone = clone $typed_data;
+    $this->assertTrue($typed_data->getValue() === $clone->getValue());
+    $this->assertTrue($typed_data->get('one') !== $clone->get('one'));
+    $clone->setValue([]);
+    $this->assertTrue($clone->isEmpty());
+
+    // Make sure the difference between NULL (not set) and an empty array is
+    // kept.
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue());
+    $typed_data->setValue([]);
+    $value = $typed_data->getValue();
+    $this->assertTrue(isset($value) && is_array($value));
+
+    // Test accessing invalid properties.
+    $typed_data->setValue($value);
+    try {
+      $typed_data->get('invalid');
+      $this->fail('No exception has been thrown when getting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+
+    // Test setting invalid values.
+    try {
+      $typed_data->setValue('invalid');
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+
+    // Test adding a new property to the map.
+    $typed_data->getDataDefinition()->setPropertyDefinition('zero', DataDefinition::create('any'));
+    $typed_data->set('zero', 'null');
+    $this->assertEqual($typed_data->get('zero')->getValue(), 'null');
+    $definition = $typed_data->get('zero')->getDataDefinition();
+    $this->assertEqual($definition->getDataType(), 'any', 'Definition for a new map entry returned.');
+  }
+
+  /**
+   * Tests typed data validation.
+   */
+  public function testTypedDataValidation() {
+    $definition = DataDefinition::create('integer')
+      ->setConstraints([
+        'Range' => ['min' => 5],
+      ]);
+    $violations = $this->typedDataManager->create($definition, 10)->validate();
+    $this->assertEqual($violations->count(), 0);
+
+    $integer = $this->typedDataManager->create($definition, 1);
+    $violations = $integer->validate();
+    $this->assertEqual($violations->count(), 1);
+
+    // Test translating violation messages.
+    $message = t('This value should be %limit or more.', ['%limit' => 5]);
+    $this->assertEqual($violations[0]->getMessage(), $message, 'Translated violation message retrieved.');
+    $this->assertEqual($violations[0]->getPropertyPath(), '');
+    $this->assertIdentical($violations[0]->getRoot(), $integer, 'Root object returned.');
+
+    // Test translating violation messages when pluralization is used.
+    $definition = DataDefinition::create('string')
+      ->setConstraints([
+        'Length' => ['min' => 10],
+      ]);
+    $violations = $this->typedDataManager->create($definition, "short")->validate();
+    $this->assertEqual($violations->count(), 1);
+    $message = t('This value is too short. It should have %limit characters or more.', ['%limit' => 10]);
+    $this->assertEqual($violations[0]->getMessage(), $message, 'Translated violation message retrieved.');
+
+    // Test having multiple violations.
+    $definition = DataDefinition::create('integer')
+      ->setConstraints([
+        'Range' => ['min' => 5],
+        'Null' => [],
+      ]);
+    $violations = $this->typedDataManager->create($definition, 10)->validate();
+    $this->assertEqual($violations->count(), 1);
+    $violations = $this->typedDataManager->create($definition, 1)->validate();
+    $this->assertEqual($violations->count(), 2);
+
+    // Test validating property containers and make sure the NotNull and Null
+    // constraints work with typed data containers.
+    $definition = BaseFieldDefinition::create('integer')
+      ->setConstraints(['NotNull' => []]);
+    $field_item = $this->typedDataManager->create($definition, ['value' => 10]);
+    $violations = $field_item->validate();
+    $this->assertEqual($violations->count(), 0);
+
+    $field_item = $this->typedDataManager->create($definition, ['value' => 'no integer']);
+    $violations = $field_item->validate();
+    $this->assertEqual($violations->count(), 1);
+    $this->assertEqual($violations[0]->getPropertyPath(), '0.value');
+
+    // Test that the field item may not be empty.
+    $field_item = $this->typedDataManager->create($definition);
+    $violations = $field_item->validate();
+    $this->assertEqual($violations->count(), 1);
+
+    // Test the Null constraint with typed data containers.
+    $definition = BaseFieldDefinition::create('float')
+      ->setConstraints(['Null' => []]);
+    $field_item = $this->typedDataManager->create($definition, ['value' => 11.5]);
+    $violations = $field_item->validate();
+    $this->assertEqual($violations->count(), 1);
+    $field_item = $this->typedDataManager->create($definition);
+    $violations = $field_item->validate();
+    $this->assertEqual($violations->count(), 0);
+
+    // Test getting constraint definitions by type.
+    $definitions = $this->typedDataManager->getValidationConstraintManager()->getDefinitionsByType('entity');
+    $this->assertTrue(isset($definitions['EntityType']), 'Constraint plugin found for type entity.');
+    $this->assertTrue(isset($definitions['Null']), 'Constraint plugin found for type entity.');
+    $this->assertTrue(isset($definitions['NotNull']), 'Constraint plugin found for type entity.');
+
+    $definitions = $this->typedDataManager->getValidationConstraintManager()->getDefinitionsByType('string');
+    $this->assertFalse(isset($definitions['EntityType']), 'Constraint plugin not found for type string.');
+    $this->assertTrue(isset($definitions['Null']), 'Constraint plugin found for type string.');
+    $this->assertTrue(isset($definitions['NotNull']), 'Constraint plugin found for type string.');
+
+    // Test automatic 'required' validation.
+    $definition = DataDefinition::create('integer')
+      ->setRequired(TRUE);
+    $violations = $this->typedDataManager->create($definition)->validate();
+    $this->assertEqual($violations->count(), 1);
+    $violations = $this->typedDataManager->create($definition, 0)->validate();
+    $this->assertEqual($violations->count(), 0);
+
+    // Test validating a list of a values and make sure property paths starting
+    // with "0" are created.
+    $definition = BaseFieldDefinition::create('integer');
+    $violations = $this->typedDataManager->create($definition, [['value' => 10]])->validate();
+    $this->assertEqual($violations->count(), 0);
+    $violations = $this->typedDataManager->create($definition, [['value' => 'string']])->validate();
+    $this->assertEqual($violations->count(), 1);
+
+    $this->assertEqual($violations[0]->getInvalidValue(), 'string');
+    $this->assertIdentical($violations[0]->getPropertyPath(), '0.value');
+  }
+
+}