function EntityTranslationTest::doTestEntityTranslationAPI

Same name in other branches
  1. 9 core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php \Drupal\KernelTests\Core\Entity\EntityTranslationTest::doTestEntityTranslationAPI()
  2. 8.9.x core/tests/Drupal/KernelTests/Core/Entity/EntityTranslationTest.php \Drupal\KernelTests\Core\Entity\EntityTranslationTest::doTestEntityTranslationAPI()
  3. 11.x 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 322

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();
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.