You are here

protected function EntityTranslationTest::doTestEntityTranslationAPI in Drupal 9

Same name and namespace in other branches
  1. 8 core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php \Drupal\KernelTests\Core\Entity\EntityTranslationTest::doTestEntityTranslationAPI()
  2. 10 core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php \Drupal\KernelTests\Core\Entity\EntityTranslationTest::doTestEntityTranslationAPI()

Executes the Entity Translation API tests for the given entity type.

Parameters

string $entity_type: The entity type to run the tests with.

1 call to EntityTranslationTest::doTestEntityTranslationAPI()
EntityTranslationTest::testEntityTranslationAPI in core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php
Tests the Entity Translation API behavior.

File

core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php, line 323

Class

EntityTranslationTest
Tests entity translation functionality.

Namespace

Drupal\KernelTests\Core\Entity

Code

protected function doTestEntityTranslationAPI($entity_type) {
  $default_langcode = $this->langcodes[0];
  $langcode = $this->langcodes[1];
  $langcode_key = $this->entityTypeManager
    ->getDefinition($entity_type)
    ->getKey('langcode');
  $default_langcode_key = $this->entityTypeManager
    ->getDefinition($entity_type)
    ->getKey('default_langcode');

  /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  $entity = $this->entityTypeManager
    ->getStorage($entity_type)
    ->create([
    'name' => $this
      ->randomMachineName(),
    $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  ]);
  $entity
    ->save();
  $hooks = $this
    ->getHooksInfo();
  $this
    ->assertEmpty($hooks, 'No entity translation hooks are fired when creating an entity.');

  // Verify that we obtain the entity object itself when we attempt to
  // retrieve a translation referring to it.
  $translation = $entity
    ->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
  $this
    ->assertFalse($translation
    ->isNewTranslation(), 'Existing translations are not marked as new.');
  $this
    ->assertSame($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
  $entity->{$langcode_key}->value = $default_langcode;
  $translation = $entity
    ->getTranslation($default_langcode);
  $this
    ->assertSame($entity, $translation, 'The translation object corresponding to the default language (explicit) is the entity object itself.');
  $translation = $entity
    ->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
  $this
    ->assertSame($entity, $translation, 'The translation object corresponding to the default language (implicit) is the entity object itself.');
  $this
    ->assertTrue($entity->{$default_langcode_key}->value, 'The translation object is the default one.');

  // Verify that trying to retrieve a translation for a locked language when
  // the entity is language-aware causes an exception to be thrown.
  try {
    $entity
      ->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
    $this
      ->fail('A language-neutral translation cannot be retrieved.');
  } catch (\LogicException $e) {

    // Expected exception; just continue testing.
  }

  // Create a translation and verify that the translation object and the
  // original object behave independently.
  $name = $default_langcode . '_' . $this
    ->randomMachineName();
  $entity->name->value = $name;
  $name_translated = $langcode . '_' . $this
    ->randomMachineName();
  $translation = $entity
    ->addTranslation($langcode);
  $this
    ->assertTrue($translation
    ->isNewTranslation(), 'Newly added translations are marked as new.');
  $this
    ->assertNotSame($entity, $translation, 'The entity and the translation object differ from one another.');
  $this
    ->assertTrue($entity
    ->hasTranslation($langcode), 'The new translation exists.');
  $this
    ->assertEquals($langcode, $translation
    ->language()
    ->getId(), 'The translation language matches the specified one.');
  $this
    ->assertEquals($langcode, $translation->{$langcode_key}->value, 'The translation field language value matches the specified one.');
  $this
    ->assertFalse($translation->{$default_langcode_key}->value, 'The translation object is not the default one.');
  $this
    ->assertEquals($default_langcode, $translation
    ->getUntranslated()
    ->language()
    ->getId(), 'The original language can still be retrieved.');
  $translation->name->value = $name_translated;
  $this
    ->assertEquals($name, $entity->name->value, 'The original name is retained after setting a translated value.');
  $entity->name->value = $name;
  $this
    ->assertEquals($name_translated, $translation->name->value, 'The translated name is retained after setting the original value.');

  // Save the translation and check that the expected hooks are fired.
  $translation
    ->save();
  $hooks = $this
    ->getHooksInfo();
  $this
    ->assertEquals($langcode, $hooks['entity_translation_create'], 'The generic entity translation creation hook has fired.');
  $this
    ->assertEquals($langcode, $hooks[$entity_type . '_translation_create'], 'The entity-type-specific entity translation creation hook has fired.');
  $this
    ->assertEquals($langcode, $hooks['entity_translation_insert'], 'The generic entity translation insertion hook has fired.');
  $this
    ->assertEquals($langcode, $hooks[$entity_type . '_translation_insert'], 'The entity-type-specific entity translation insertion hook has fired.');

  // Verify that changing translation language causes an exception to be
  // thrown.
  try {
    $translation->{$langcode_key}->value = $this->langcodes[2];
    $this
      ->fail('The translation language cannot be changed.');
  } catch (\LogicException $e) {

    // Expected exception; just continue testing.
  }

  // Verify that reassigning the same translation language is allowed.
  try {
    $translation->{$langcode_key}->value = $langcode;
  } catch (\LogicException $e) {
    $this
      ->fail('The translation language can be reassigned the same value.');
  }

  // Verify that changing the default translation flag causes an exception to
  // be thrown.
  foreach ($entity
    ->getTranslationLanguages() as $t_langcode => $language) {
    $translation = $entity
      ->getTranslation($t_langcode);
    $default = $translation
      ->isDefaultTranslation();
    try {
      $translation->{$default_langcode_key}->value = $default;
    } catch (\LogicException $e) {
      $this
        ->fail('The default translation flag can be reassigned the same value.');
    }
    try {
      $translation->{$default_langcode_key}->value = !$default;
      $this
        ->fail('The default translation flag cannot be changed.');
    } catch (\LogicException $e) {

      // Expected exception; just continue testing.
    }
    $this
      ->assertEquals($default, $translation->{$default_langcode_key}->value);
  }

  // Check that after loading an entity the language is the default one.
  $entity = $this
    ->reloadEntity($entity);
  $this
    ->assertEquals($default_langcode, $entity
    ->language()
    ->getId(), 'The loaded entity is the original one.');

  // Add another translation and check that everything works as expected. A
  // new translation object can be obtained also by just specifying a valid
  // language.
  $langcode2 = $this->langcodes[2];
  $translation = $entity
    ->addTranslation($langcode2);
  $value = $entity !== $translation && $translation
    ->language()
    ->getId() == $langcode2 && $entity
    ->hasTranslation($langcode2);
  $this
    ->assertTrue($value, 'A new translation object can be obtained also by specifying a valid language.');
  $this
    ->assertEquals($default_langcode, $entity
    ->language()
    ->getId(), 'The original language has been preserved.');
  $translation
    ->save();
  $hooks = $this
    ->getHooksInfo();
  $this
    ->assertEquals($langcode2, $hooks['entity_translation_create'], 'The generic entity translation creation hook has fired.');
  $this
    ->assertEquals($langcode2, $hooks[$entity_type . '_translation_create'], 'The entity-type-specific entity translation creation hook has fired.');
  $this
    ->assertEquals($langcode2, $hooks['entity_translation_insert'], 'The generic entity translation insertion hook has fired.');
  $this
    ->assertEquals($langcode2, $hooks[$entity_type . '_translation_insert'], 'The entity-type-specific entity translation insertion hook has fired.');

  // Verify that trying to manipulate a translation object referring to a
  // removed translation results in exceptions being thrown.
  $entity = $this
    ->reloadEntity($entity);
  $translation = $entity
    ->getTranslation($langcode2);
  $entity
    ->removeTranslation($langcode2);
  foreach ([
    'get',
    'set',
    '__get',
    '__set',
    'createDuplicate',
  ] as $method) {
    try {
      $translation
        ->{$method}('name', $this
        ->randomMachineName());
      $this
        ->fail("The {$method} method raises an exception when trying to manipulate a removed translation.");
    } catch (\Exception $e) {

      // Expected exception; just continue testing.
    }
  }

  // Verify that deletion hooks are fired when saving an entity with a removed
  // translation.
  $entity
    ->save();
  $hooks = $this
    ->getHooksInfo();
  $this
    ->assertEquals($langcode2, $hooks['entity_translation_delete'], 'The generic entity translation deletion hook has fired.');
  $this
    ->assertEquals($langcode2, $hooks[$entity_type . '_translation_delete'], 'The entity-type-specific entity translation deletion hook has fired.');
  $entity = $this
    ->reloadEntity($entity);
  $this
    ->assertFalse($entity
    ->hasTranslation($langcode2), 'The translation does not appear among available translations after saving the entity.');

  // Check that removing an invalid translation causes an exception to be
  // thrown.
  foreach ([
    $default_langcode,
    LanguageInterface::LANGCODE_DEFAULT,
    $this
      ->randomMachineName(),
  ] as $invalid_langcode) {
    try {
      $entity
        ->removeTranslation($invalid_langcode);
      $this
        ->fail("Removing an invalid translation ({$invalid_langcode}) causes an exception to be thrown.");
    } catch (\Exception $e) {

      // Expected exception; just continue testing.
    }
  }

  // Check that hooks are fired only when actually storing data.
  $entity = $this
    ->reloadEntity($entity);
  $entity
    ->addTranslation($langcode2);
  $entity
    ->removeTranslation($langcode2);
  $entity
    ->save();
  $hooks = $this
    ->getHooksInfo();
  $this
    ->assertTrue(isset($hooks['entity_translation_create']), 'The generic entity translation creation hook is run when adding and removing a translation without storing it.');
  unset($hooks['entity_translation_create']);
  $this
    ->assertTrue(isset($hooks[$entity_type . '_translation_create']), 'The entity-type-specific entity translation creation hook is run when adding and removing a translation without storing it.');
  unset($hooks[$entity_type . '_translation_create']);
  $this
    ->assertEmpty($hooks, 'No other hooks beyond the entity translation creation hooks are run when adding and removing a translation without storing it.');

  // Check that hooks are fired only when actually storing data.
  $entity = $this
    ->reloadEntity($entity);
  $entity
    ->addTranslation($langcode2);
  $entity
    ->save();
  $entity = $this
    ->reloadEntity($entity);
  $this
    ->assertTrue($entity
    ->hasTranslation($langcode2), 'Entity has translation after adding one and saving.');
  $entity
    ->removeTranslation($langcode2);
  $entity
    ->save();
  $entity = $this
    ->reloadEntity($entity);
  $this
    ->assertFalse($entity
    ->hasTranslation($langcode2), 'Entity does not have translation after removing it and saving.');

  // Reset hook firing information.
  $this
    ->getHooksInfo();

  // Verify that entity serialization does not cause stale references to be
  // left around.
  $entity = $this
    ->reloadEntity($entity);
  $translation = $entity
    ->getTranslation($langcode);
  $entity = unserialize(serialize($entity));
  $entity->name->value = $this
    ->randomMachineName();
  $name = $default_langcode . '_' . $this
    ->randomMachineName();
  $entity
    ->getTranslation($default_langcode)->name->value = $name;
  $this
    ->assertEquals($name, $entity->name->value, 'No stale reference for the translation object corresponding to the original language.');
  $translation2 = $entity
    ->getTranslation($langcode);
  $translation2->name->value .= $this
    ->randomMachineName();
  $this
    ->assertNotEquals($translation->name->value, $translation2->name->value, 'No stale reference for the actual translation object.');
  $this
    ->assertEquals($entity, $translation2
    ->getUntranslated(), 'No stale reference in the actual translation object.');

  // Verify that deep-cloning is still available when we are not instantiating
  // a translation object, which instead relies on shallow cloning.
  $entity = $this
    ->reloadEntity($entity);
  $entity
    ->getTranslation($langcode);
  $cloned = clone $entity;
  $translation = $cloned
    ->getTranslation($langcode);
  $this
    ->assertNotSame($entity, $translation
    ->getUntranslated(), 'A cloned entity object has no reference to the original one.');
  $entity
    ->removeTranslation($langcode);
  $this
    ->assertFalse($entity
    ->hasTranslation($langcode));
  $this
    ->assertTrue($cloned
    ->hasTranslation($langcode));

  // Check that untranslatable field references keep working after serializing
  // and cloning the entity.
  $entity = $this
    ->reloadEntity($entity);
  $type = $this
    ->randomMachineName();
  $entity
    ->getTranslation($langcode)->type->value = $type;
  $entity = unserialize(serialize($entity));
  $cloned = clone $entity;
  $translation = $cloned
    ->getTranslation($langcode);
  $translation->type->value = strrev($type);
  $this
    ->assertEquals($cloned->type->value, $translation->type->value, 'Untranslatable field references keep working after serializing and cloning the entity.');

  // Check that per-language defaults are properly populated. The
  // 'entity_test_mul_default_value' entity type is translatable and uses
  // entity_test_field_default_value() as a "default value callback" for its
  // 'description' field.
  $entity = $this->entityTypeManager
    ->getStorage('entity_test_mul_default_value')
    ->create([
    'name' => $this
      ->randomMachineName(),
    'langcode' => $langcode,
  ]);
  $translation = $entity
    ->addTranslation($langcode2);
  $expected = [
    [
      'shape' => "shape:0:description_{$langcode2}",
      'color' => "color:0:description_{$langcode2}",
    ],
    [
      'shape' => "shape:1:description_{$langcode2}",
      'color' => "color:1:description_{$langcode2}",
    ],
  ];
  $this
    ->assertEquals($expected, $translation->description
    ->getValue(), 'Language-aware default values correctly populated.');
  $this
    ->assertEquals($langcode2, $translation->description
    ->getLangcode(), 'Field object has the expected langcode.');

  // Reset hook firing information.
  $this
    ->getHooksInfo();
}