Chris@0: doTestBasicTranslation(); Chris@0: $this->doTestTranslationOverview(); Chris@0: $this->doTestOutdatedStatus(); Chris@0: $this->doTestPublishedStatus(); Chris@0: $this->doTestAuthoringInfo(); Chris@0: $this->doTestTranslationEdit(); Chris@0: $this->doTestTranslationChanged(); Chris@0: $this->doTestChangedTimeAfterSaveWithoutChanges(); Chris@0: $this->doTestTranslationDeletion(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the basic translation workflow. Chris@0: */ Chris@0: protected function doTestBasicTranslation() { Chris@0: // Create a new test entity with original values in the default language. Chris@0: $default_langcode = $this->langcodes[0]; Chris@0: $values[$default_langcode] = $this->getNewEntityValues($default_langcode); Chris@0: // Create the entity with the editor as owner, so that afterwards a new Chris@0: // translation is created by the translator and the translation author is Chris@0: // tested. Chris@0: $this->drupalLogin($this->editor); Chris@0: $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode); Chris@0: $this->drupalLogin($this->translator); Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertTrue($entity, 'Entity found in the database.'); Chris@18: $this->drupalGet($entity->toUrl()); Chris@0: $this->assertResponse(200, 'Entity URL is valid.'); Chris@0: Chris@0: // Ensure that the content language cache context is not yet added to the Chris@0: // page. Chris@0: $this->assertCacheContexts($this->defaultCacheContexts); Chris@0: Chris@18: $this->drupalGet($entity->toUrl('drupal:content-translation-overview')); Chris@0: $this->assertNoText('Source language', 'Source language column correctly hidden.'); Chris@0: Chris@0: $translation = $this->getTranslation($entity, $default_langcode); Chris@0: foreach ($values[$default_langcode] as $property => $value) { Chris@0: $stored_value = $this->getValue($translation, $property, $default_langcode); Chris@0: $value = is_array($value) ? $value[0]['value'] : $value; Chris@0: $message = format_string('@property correctly stored in the default language.', ['@property' => $property]); Chris@0: $this->assertEqual($stored_value, $value, $message); Chris@0: } Chris@0: Chris@0: // Add a content translation. Chris@0: $langcode = 'it'; Chris@0: $language = ConfigurableLanguage::load($langcode); Chris@0: $values[$langcode] = $this->getNewEntityValues($langcode); Chris@0: Chris@0: $entity_type_id = $entity->getEntityTypeId(); Chris@0: $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ Chris@0: $entity->getEntityTypeId() => $entity->id(), Chris@0: 'source' => $default_langcode, Chris@17: 'target' => $langcode, Chris@0: ], ['language' => $language]); Chris@0: $this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode)); Chris@0: Chris@17: // Assert that HTML is not escaped unexpectedly. Chris@0: if ($this->testHTMLEscapeForAllLanguages) { Chris@0: $this->assertNoRaw('<span class="translation-entity-all-languages">(all languages)</span>'); Chris@0: $this->assertRaw('(all languages)'); Chris@0: } Chris@0: Chris@0: // Ensure that the content language cache context is not yet added to the Chris@0: // page. Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@18: $this->drupalGet($entity->toUrl()); Chris@0: $this->assertCacheContexts(Cache::mergeContexts(['languages:language_content'], $this->defaultCacheContexts)); Chris@0: Chris@0: // Reset the cache of the entity, so that the new translation gets the Chris@0: // updated values. Chris@0: $metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode)); Chris@0: $metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode)); Chris@0: Chris@0: $author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid'; Chris@0: if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) { Chris@0: $this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(), Chris@17: new FormattableMarkup('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode])); Chris@0: Chris@0: $this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(), Chris@17: new FormattableMarkup('Author of the target translation @target different from the author of the source translation @source for translatable owner field.', Chris@0: ['@target' => $langcode, '@source' => $default_langcode])); Chris@0: } Chris@0: else { Chris@0: $this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->editor->id(), 'Author of the entity remained untouched after translation for non translatable owner field.'); Chris@0: } Chris@0: Chris@0: $created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created'; Chris@0: if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) { Chris@0: $this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(), Chris@17: new FormattableMarkup('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.', Chris@0: ['@target' => $langcode, '@source' => $default_langcode])); Chris@0: } Chris@0: else { Chris@0: $this->assertEqual($metadata_target_translation->getCreatedTime(), $metadata_source_translation->getCreatedTime(), 'Creation timestamp of the entity remained untouched after translation for non translatable created field.'); Chris@0: } Chris@0: Chris@0: if ($this->testLanguageSelector) { Chris@0: $this->assertNoFieldByXPath('//select[@id="edit-langcode-0-value"]', NULL, 'Language selector correctly disabled on translations.'); Chris@0: } Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@18: $this->drupalGet($entity->toUrl('drupal:content-translation-overview')); Chris@0: $this->assertNoText('Source language', 'Source language column correctly hidden.'); Chris@0: Chris@0: // Switch the source language. Chris@0: $langcode = 'fr'; Chris@0: $language = ConfigurableLanguage::load($langcode); Chris@0: $source_langcode = 'it'; Chris@0: $edit = ['source_langcode[source]' => $source_langcode]; Chris@0: $entity_type_id = $entity->getEntityTypeId(); Chris@0: $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ Chris@0: $entity->getEntityTypeId() => $entity->id(), Chris@0: 'source' => $default_langcode, Chris@17: 'target' => $langcode, Chris@0: ], ['language' => $language]); Chris@0: // This does not save anything, it merely reloads the form and fills in the Chris@0: // fields with the values from the different source language. Chris@0: $this->drupalPostForm($add_url, $edit, t('Change')); Chris@0: $this->assertFieldByXPath("//input[@name=\"{$this->fieldName}[0][value]\"]", $values[$source_langcode][$this->fieldName][0]['value'], 'Source language correctly switched.'); Chris@0: Chris@0: // Add another translation and mark the other ones as outdated. Chris@0: $values[$langcode] = $this->getNewEntityValues($langcode); Chris@0: $edit = $this->getEditValues($values, $langcode) + ['content_translation[retranslate]' => TRUE]; Chris@0: $entity_type_id = $entity->getEntityTypeId(); Chris@0: $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [ Chris@0: $entity->getEntityTypeId() => $entity->id(), Chris@0: 'source' => $source_langcode, Chris@17: 'target' => $langcode, Chris@0: ], ['language' => $language]); Chris@0: $this->drupalPostForm($add_url, $edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode)); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@18: $this->drupalGet($entity->toUrl('drupal:content-translation-overview')); Chris@0: $this->assertText('Source language', 'Source language column correctly shown.'); Chris@0: Chris@0: // Check that the entered values have been correctly stored. Chris@0: foreach ($values as $langcode => $property_values) { Chris@0: $translation = $this->getTranslation($entity, $langcode); Chris@0: foreach ($property_values as $property => $value) { Chris@0: $stored_value = $this->getValue($translation, $property, $langcode); Chris@0: $value = is_array($value) ? $value[0]['value'] : $value; Chris@0: $message = format_string('%property correctly stored with language %language.', ['%property' => $property, '%language' => $langcode]); Chris@0: $this->assertEqual($stored_value, $value, $message); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests that the translation overview shows the correct values. Chris@0: */ Chris@0: protected function doTestTranslationOverview() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@18: $translate_url = $entity->toUrl('drupal:content-translation-overview'); Chris@0: $this->drupalGet($translate_url); Chris@0: $translate_url->setAbsolute(FALSE); Chris@0: Chris@0: foreach ($this->langcodes as $langcode) { Chris@0: if ($entity->hasTranslation($langcode)) { Chris@0: $language = new Language(['id' => $langcode]); Chris@18: $view_url = $entity->toUrl('canonical', ['language' => $language])->toString(); Chris@0: $elements = $this->xpath('//table//a[@href=:href]', [':href' => $view_url]); Chris@0: $this->assertEqual((string) $elements[0], $entity->getTranslation($langcode)->label(), format_string('Label correctly shown for %language translation.', ['%language' => $langcode])); Chris@18: $edit_path = $entity->toUrl('edit-form', ['language' => $language])->toString(); Chris@0: $elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a[@href=:href]', [':href' => $edit_path]); Chris@0: $this->assertEqual((string) $elements[0], t('Edit'), format_string('Edit link correct for %language translation.', ['%language' => $langcode])); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests up-to-date status tracking. Chris@0: */ Chris@0: protected function doTestOutdatedStatus() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $langcode = 'fr'; Chris@0: $languages = \Drupal::languageManager()->getLanguages(); Chris@0: Chris@0: // Mark translations as outdated. Chris@0: $edit = ['content_translation[retranslate]' => TRUE]; Chris@18: $edit_path = $entity->toUrl('edit-form', ['language' => $languages[$langcode]]); Chris@0: $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode)); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: Chris@0: // Check that every translation has the correct "outdated" status, and that Chris@0: // the Translation fieldset is open if the translation is "outdated". Chris@0: foreach ($this->langcodes as $added_langcode) { Chris@18: $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($added_langcode)]); Chris@0: $this->drupalGet($url); Chris@0: if ($added_langcode == $langcode) { Chris@0: $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.'); Chris@0: $this->assertFalse($this->xpath('//details[@id="edit-content-translation" and @open="open"]'), 'The translation tab should be collapsed by default.'); Chris@0: } Chris@0: else { Chris@0: $this->assertFieldByXPath('//input[@name="content_translation[outdated]"]', TRUE, 'The translate flag is checked by default.'); Chris@0: $this->assertTrue($this->xpath('//details[@id="edit-content-translation" and @open="open"]'), 'The translation tab is correctly expanded when the translation is outdated.'); Chris@0: $edit = ['content_translation[outdated]' => FALSE]; Chris@0: $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $added_langcode)); Chris@0: $this->drupalGet($url); Chris@0: $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.'); Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.'); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the translation publishing status. Chris@0: */ Chris@0: protected function doTestPublishedStatus() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: Chris@0: // Unpublish translations. Chris@0: foreach ($this->langcodes as $index => $langcode) { Chris@0: if ($index > 0) { Chris@18: $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]); Chris@0: $edit = ['content_translation[status]' => FALSE]; Chris@0: $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode)); Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.'); Chris@0: } Chris@0: } Chris@0: Chris@0: // Check that the last published translation cannot be unpublished. Chris@18: $this->drupalGet($entity->toUrl('edit-form')); Chris@0: $this->assertFieldByXPath('//input[@name="content_translation[status]" and @disabled="disabled"]', TRUE, 'The last translation is published and cannot be unpublished.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the translation authoring information. Chris@0: */ Chris@0: protected function doTestAuthoringInfo() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $values = []; Chris@0: Chris@0: // Post different authoring information for each translation. Chris@0: foreach ($this->langcodes as $index => $langcode) { Chris@0: $user = $this->drupalCreateUser(); Chris@0: $values[$langcode] = [ Chris@0: 'uid' => $user->id(), Chris@0: 'created' => REQUEST_TIME - mt_rand(0, 1000), Chris@0: ]; Chris@0: $edit = [ Chris@18: 'content_translation[uid]' => $user->getAccountName(), Chris@18: 'content_translation[created]' => $this->container->get('date.formatter')->format($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'), Chris@0: ]; Chris@18: $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]); Chris@0: $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode)); Chris@0: } Chris@0: Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: foreach ($this->langcodes as $langcode) { Chris@0: $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode)); Chris@0: $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.'); Chris@0: $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.'); Chris@0: } Chris@0: Chris@0: // Try to post non valid values and check that they are rejected. Chris@0: $langcode = 'en'; Chris@0: $edit = [ Chris@0: // User names have by default length 8. Chris@0: 'content_translation[uid]' => $this->randomMachineName(12), Chris@0: 'content_translation[created]' => '19/11/1978', Chris@0: ]; Chris@18: $this->drupalPostForm($entity->toUrl('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode)); Chris@0: $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.'); Chris@0: $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode)); Chris@0: $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.'); Chris@0: $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests translation deletion. Chris@0: */ Chris@0: protected function doTestTranslationDeletion() { Chris@0: // Confirm and delete a translation. Chris@0: $this->drupalLogin($this->translator); Chris@0: $langcode = 'fr'; Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $language = ConfigurableLanguage::load($langcode); Chris@18: $url = $entity->toUrl('edit-form', ['language' => $language]); Chris@0: $this->drupalPostForm($url, [], t('Delete translation')); Chris@0: $this->drupalPostForm(NULL, [], t('Delete @language translation', ['@language' => $language->getName()])); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId, TRUE); Chris@0: if ($this->assertTrue(is_object($entity), 'Entity found')) { Chris@0: $translations = $entity->getTranslationLanguages(); Chris@0: $this->assertTrue(count($translations) == 2 && empty($translations[$langcode]), 'Translation successfully deleted.'); Chris@0: } Chris@0: Chris@0: // Check that the translator cannot delete the original translation. Chris@0: $args = [$this->entityTypeId => $entity->id(), 'language' => 'en']; Chris@0: $this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args)); Chris@0: $this->assertResponse(403); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an array of entity field values to be tested. Chris@0: */ Chris@0: protected function getNewEntityValues($langcode) { Chris@0: return [$this->fieldName => [['value' => $this->randomMachineName(16)]]]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an edit array containing the values to be posted. Chris@0: */ Chris@0: protected function getEditValues($values, $langcode, $new = FALSE) { Chris@0: $edit = $values[$langcode]; Chris@0: $langcode = $new ? LanguageInterface::LANGCODE_NOT_SPECIFIED : $langcode; Chris@0: foreach ($values[$langcode] as $property => $value) { Chris@0: if (is_array($value)) { Chris@0: $edit["{$property}[0][value]"] = $value[0]['value']; Chris@0: unset($edit[$property]); Chris@0: } Chris@0: } Chris@0: return $edit; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the form action value when submitting a new translation. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being tested. Chris@0: * @param string $langcode Chris@0: * Language code for the form. Chris@0: * Chris@0: * @return string Chris@0: * Name of the button to hit. Chris@0: */ Chris@0: protected function getFormSubmitActionForNewTranslation(EntityInterface $entity, $langcode) { Chris@0: $entity->addTranslation($langcode, $entity->toArray()); Chris@0: return $this->getFormSubmitAction($entity, $langcode); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the form action value to be used to submit the entity form. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being tested. Chris@0: * @param string $langcode Chris@0: * Language code for the form. Chris@0: * Chris@0: * @return string Chris@0: * Name of the button to hit. Chris@0: */ Chris@0: protected function getFormSubmitAction(EntityInterface $entity, $langcode) { Chris@0: return t('Save') . $this->getFormSubmitSuffix($entity, $langcode); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns appropriate submit button suffix based on translatability. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being tested. Chris@0: * @param string $langcode Chris@0: * Language code for the form. Chris@0: * Chris@0: * @return string Chris@0: * Submit button suffix based on translatability. Chris@0: */ Chris@0: protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) { Chris@0: return ''; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the translation object to use to retrieve the translated values. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being tested. Chris@0: * @param string $langcode Chris@0: * The language code identifying the translation to be retrieved. Chris@0: * Chris@0: * @return \Drupal\Core\TypedData\TranslatableInterface Chris@0: * The translation object to act on. Chris@0: */ Chris@0: protected function getTranslation(EntityInterface $entity, $langcode) { Chris@0: return $entity->getTranslation($langcode); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the value for the specified property in the given language. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $translation Chris@0: * The translation object the property value should be retrieved from. Chris@0: * @param string $property Chris@0: * The property name. Chris@0: * @param string $langcode Chris@0: * The property value. Chris@0: * Chris@0: * @return Chris@0: * The property value. Chris@0: */ Chris@0: protected function getValue(EntityInterface $translation, $property, $langcode) { Chris@0: $key = $property == 'user_id' ? 'target_id' : 'value'; Chris@0: return $translation->get($property)->{$key}; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the name of the field that implements the changed timestamp. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being tested. Chris@0: * Chris@0: * @return string Chris@0: * The field name. Chris@0: */ Chris@0: protected function getChangedFieldName($entity) { Chris@0: return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests edit content translation. Chris@0: */ Chris@0: protected function doTestTranslationEdit() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $languages = $this->container->get('language_manager')->getLanguages(); Chris@0: Chris@0: foreach ($this->langcodes as $langcode) { Chris@0: // We only want to test the title for non-english translations. Chris@0: if ($langcode != 'en') { Chris@0: $options = ['language' => $languages[$langcode]]; Chris@18: $url = $entity->toUrl('edit-form', $options); Chris@0: $this->drupalGet($url); Chris@0: Chris@0: $this->assertRaw($entity->getTranslation($langcode)->label()); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the basic translation workflow. Chris@0: */ Chris@0: protected function doTestTranslationChanged() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $changed_field_name = $this->getChangedFieldName($entity); Chris@0: $definition = $entity->getFieldDefinition($changed_field_name); Chris@0: $config = $definition->getConfig($entity->bundle()); Chris@0: Chris@0: foreach ([FALSE, TRUE] as $translatable_changed_field) { Chris@0: if ($definition->isTranslatable()) { Chris@0: // For entities defining a translatable changed field we want to test Chris@0: // the correct behavior of that field even if the translatability is Chris@0: // revoked. In that case the changed timestamp should be synchronized Chris@0: // across all translations. Chris@0: $config->setTranslatable($translatable_changed_field); Chris@0: $config->save(); Chris@0: } Chris@0: elseif ($translatable_changed_field) { Chris@0: // For entities defining a non-translatable changed field we cannot Chris@0: // declare the field as translatable on the fly by modifying its config Chris@0: // because the schema doesn't support this. Chris@0: break; Chris@0: } Chris@0: Chris@0: foreach ($entity->getTranslationLanguages() as $language) { Chris@0: // Ensure different timestamps. Chris@0: sleep(1); Chris@0: Chris@0: $langcode = $language->getId(); Chris@0: Chris@0: $edit = [ Chris@0: $this->fieldName . '[0][value]' => $this->randomString(), Chris@0: ]; Chris@18: $edit_path = $entity->toUrl('edit-form', ['language' => $language]); Chris@0: $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode)); Chris@0: Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertEqual( Chris@0: $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(), Chris@0: format_string('Changed time for language %language is the latest change over all languages.', ['%language' => $language->getName()]) Chris@0: ); Chris@0: } Chris@0: Chris@0: $timestamps = []; Chris@0: foreach ($entity->getTranslationLanguages() as $language) { Chris@0: $next_timestamp = $entity->getTranslation($language->getId())->getChangedTime(); Chris@0: if (!in_array($next_timestamp, $timestamps)) { Chris@0: $timestamps[] = $next_timestamp; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($translatable_changed_field) { Chris@0: $this->assertEqual( Chris@0: count($timestamps), count($entity->getTranslationLanguages()), Chris@0: 'All timestamps from all languages are different.' Chris@0: ); Chris@0: } Chris@0: else { Chris@0: $this->assertEqual( Chris@0: count($timestamps), 1, Chris@0: 'All timestamps from all languages are identical.' Chris@0: ); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test the changed time after API and FORM save without changes. Chris@0: */ Chris@0: public function doTestChangedTimeAfterSaveWithoutChanges() { Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: // Test only entities, which implement the EntityChangedInterface. Chris@0: if ($entity instanceof EntityChangedInterface) { Chris@0: $changed_timestamp = $entity->getChangedTime(); Chris@0: Chris@0: $entity->save(); Chris@0: $storage = $this->container->get('entity_type.manager') Chris@0: ->getStorage($this->entityTypeId); Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time wasn\'t updated after API save without changes.'); Chris@0: Chris@0: // Ensure different save timestamps. Chris@0: sleep(1); Chris@0: Chris@0: // Save the entity on the regular edit form. Chris@0: $language = $entity->language(); Chris@18: $edit_path = $entity->toUrl('edit-form', ['language' => $language]); Chris@0: $this->drupalPostForm($edit_path, [], $this->getFormSubmitAction($entity, $language->getId())); Chris@0: Chris@0: $storage->resetCache([$this->entityId]); Chris@0: $entity = $storage->load($this->entityId); Chris@0: $this->assertNotEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time was updated after form save without changes.'); Chris@0: } Chris@0: } Chris@0: Chris@0: }