annotate core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\content_translation\Tests;
Chris@0 4
Chris@18 5 @trigger_error(__NAMESPACE__ . '\ContentTranslationUITestBase is deprecated for removal before Drupal 9.0.0. Use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase instead. See https://www.drupal.org/node/2999939', E_USER_DEPRECATED);
Chris@18 6
Chris@0 7 use Drupal\Core\Cache\Cache;
Chris@0 8 use Drupal\Core\Entity\EntityChangedInterface;
Chris@0 9 use Drupal\Core\Entity\EntityInterface;
Chris@0 10 use Drupal\Core\Language\Language;
Chris@0 11 use Drupal\Core\Language\LanguageInterface;
Chris@0 12 use Drupal\Core\Url;
Chris@0 13 use Drupal\language\Entity\ConfigurableLanguage;
Chris@17 14 use Drupal\Component\Render\FormattableMarkup;
Chris@0 15 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Tests the Content Translation UI.
Chris@0 19 *
Chris@0 20 * @deprecated Scheduled for removal in Drupal 9.0.0.
Chris@0 21 * Use \Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase instead.
Chris@18 22 *
Chris@18 23 * @see https://www.drupal.org/node/2999939
Chris@0 24 */
Chris@0 25 abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
Chris@0 26
Chris@0 27 use AssertPageCacheContextsAndTagsTrait;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * The id of the entity being translated.
Chris@0 31 *
Chris@0 32 * @var mixed
Chris@0 33 */
Chris@0 34 protected $entityId;
Chris@0 35
Chris@0 36 /**
Chris@0 37 * Whether the behavior of the language selector should be tested.
Chris@0 38 *
Chris@0 39 * @var bool
Chris@0 40 */
Chris@0 41 protected $testLanguageSelector = TRUE;
Chris@0 42
Chris@0 43 /**
Chris@17 44 * Flag to determine if "all languages" rendering is tested.
Chris@0 45 *
Chris@0 46 * @var bool
Chris@0 47 */
Chris@0 48 protected $testHTMLEscapeForAllLanguages = FALSE;
Chris@0 49
Chris@0 50 /**
Chris@0 51 * Default cache contexts expected on a non-translated entity.
Chris@0 52 *
Chris@0 53 * Cache contexts will not be checked if this list is empty.
Chris@0 54 *
Chris@0 55 * @var string[]
Chris@0 56 */
Chris@0 57 protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.query_args:_wrapper_format', 'user.permissions'];
Chris@0 58
Chris@0 59 /**
Chris@0 60 * Tests the basic translation UI.
Chris@0 61 */
Chris@0 62 public function testTranslationUI() {
Chris@0 63 $this->doTestBasicTranslation();
Chris@0 64 $this->doTestTranslationOverview();
Chris@0 65 $this->doTestOutdatedStatus();
Chris@0 66 $this->doTestPublishedStatus();
Chris@0 67 $this->doTestAuthoringInfo();
Chris@0 68 $this->doTestTranslationEdit();
Chris@0 69 $this->doTestTranslationChanged();
Chris@0 70 $this->doTestChangedTimeAfterSaveWithoutChanges();
Chris@0 71 $this->doTestTranslationDeletion();
Chris@0 72 }
Chris@0 73
Chris@0 74 /**
Chris@0 75 * Tests the basic translation workflow.
Chris@0 76 */
Chris@0 77 protected function doTestBasicTranslation() {
Chris@0 78 // Create a new test entity with original values in the default language.
Chris@0 79 $default_langcode = $this->langcodes[0];
Chris@0 80 $values[$default_langcode] = $this->getNewEntityValues($default_langcode);
Chris@0 81 // Create the entity with the editor as owner, so that afterwards a new
Chris@0 82 // translation is created by the translator and the translation author is
Chris@0 83 // tested.
Chris@0 84 $this->drupalLogin($this->editor);
Chris@0 85 $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
Chris@0 86 $this->drupalLogin($this->translator);
Chris@0 87 $storage = $this->container->get('entity_type.manager')
Chris@0 88 ->getStorage($this->entityTypeId);
Chris@0 89 $storage->resetCache([$this->entityId]);
Chris@0 90 $entity = $storage->load($this->entityId);
Chris@0 91 $this->assertTrue($entity, 'Entity found in the database.');
Chris@18 92 $this->drupalGet($entity->toUrl());
Chris@0 93 $this->assertResponse(200, 'Entity URL is valid.');
Chris@0 94
Chris@0 95 // Ensure that the content language cache context is not yet added to the
Chris@0 96 // page.
Chris@0 97 $this->assertCacheContexts($this->defaultCacheContexts);
Chris@0 98
Chris@18 99 $this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
Chris@0 100 $this->assertNoText('Source language', 'Source language column correctly hidden.');
Chris@0 101
Chris@0 102 $translation = $this->getTranslation($entity, $default_langcode);
Chris@0 103 foreach ($values[$default_langcode] as $property => $value) {
Chris@0 104 $stored_value = $this->getValue($translation, $property, $default_langcode);
Chris@0 105 $value = is_array($value) ? $value[0]['value'] : $value;
Chris@0 106 $message = format_string('@property correctly stored in the default language.', ['@property' => $property]);
Chris@0 107 $this->assertEqual($stored_value, $value, $message);
Chris@0 108 }
Chris@0 109
Chris@0 110 // Add a content translation.
Chris@0 111 $langcode = 'it';
Chris@0 112 $language = ConfigurableLanguage::load($langcode);
Chris@0 113 $values[$langcode] = $this->getNewEntityValues($langcode);
Chris@0 114
Chris@0 115 $entity_type_id = $entity->getEntityTypeId();
Chris@0 116 $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
Chris@0 117 $entity->getEntityTypeId() => $entity->id(),
Chris@0 118 'source' => $default_langcode,
Chris@17 119 'target' => $langcode,
Chris@0 120 ], ['language' => $language]);
Chris@0 121 $this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
Chris@0 122
Chris@17 123 // Assert that HTML is not escaped unexpectedly.
Chris@0 124 if ($this->testHTMLEscapeForAllLanguages) {
Chris@0 125 $this->assertNoRaw('&lt;span class=&quot;translation-entity-all-languages&quot;&gt;(all languages)&lt;/span&gt;');
Chris@0 126 $this->assertRaw('<span class="translation-entity-all-languages">(all languages)</span>');
Chris@0 127 }
Chris@0 128
Chris@0 129 // Ensure that the content language cache context is not yet added to the
Chris@0 130 // page.
Chris@0 131 $storage = $this->container->get('entity_type.manager')
Chris@0 132 ->getStorage($this->entityTypeId);
Chris@0 133 $storage->resetCache([$this->entityId]);
Chris@0 134 $entity = $storage->load($this->entityId);
Chris@18 135 $this->drupalGet($entity->toUrl());
Chris@0 136 $this->assertCacheContexts(Cache::mergeContexts(['languages:language_content'], $this->defaultCacheContexts));
Chris@0 137
Chris@0 138 // Reset the cache of the entity, so that the new translation gets the
Chris@0 139 // updated values.
Chris@0 140 $metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
Chris@0 141 $metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
Chris@0 142
Chris@0 143 $author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
Chris@0 144 if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
Chris@0 145 $this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(),
Chris@17 146 new FormattableMarkup('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode]));
Chris@0 147
Chris@0 148 $this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
Chris@17 149 new FormattableMarkup('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
Chris@0 150 ['@target' => $langcode, '@source' => $default_langcode]));
Chris@0 151 }
Chris@0 152 else {
Chris@0 153 $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 154 }
Chris@0 155
Chris@0 156 $created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
Chris@0 157 if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
Chris@0 158 $this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(),
Chris@17 159 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 160 ['@target' => $langcode, '@source' => $default_langcode]));
Chris@0 161 }
Chris@0 162 else {
Chris@0 163 $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 164 }
Chris@0 165
Chris@0 166 if ($this->testLanguageSelector) {
Chris@0 167 $this->assertNoFieldByXPath('//select[@id="edit-langcode-0-value"]', NULL, 'Language selector correctly disabled on translations.');
Chris@0 168 }
Chris@0 169 $storage->resetCache([$this->entityId]);
Chris@0 170 $entity = $storage->load($this->entityId);
Chris@18 171 $this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
Chris@0 172 $this->assertNoText('Source language', 'Source language column correctly hidden.');
Chris@0 173
Chris@0 174 // Switch the source language.
Chris@0 175 $langcode = 'fr';
Chris@0 176 $language = ConfigurableLanguage::load($langcode);
Chris@0 177 $source_langcode = 'it';
Chris@0 178 $edit = ['source_langcode[source]' => $source_langcode];
Chris@0 179 $entity_type_id = $entity->getEntityTypeId();
Chris@0 180 $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
Chris@0 181 $entity->getEntityTypeId() => $entity->id(),
Chris@0 182 'source' => $default_langcode,
Chris@17 183 'target' => $langcode,
Chris@0 184 ], ['language' => $language]);
Chris@0 185 // This does not save anything, it merely reloads the form and fills in the
Chris@0 186 // fields with the values from the different source language.
Chris@0 187 $this->drupalPostForm($add_url, $edit, t('Change'));
Chris@0 188 $this->assertFieldByXPath("//input[@name=\"{$this->fieldName}[0][value]\"]", $values[$source_langcode][$this->fieldName][0]['value'], 'Source language correctly switched.');
Chris@0 189
Chris@0 190 // Add another translation and mark the other ones as outdated.
Chris@0 191 $values[$langcode] = $this->getNewEntityValues($langcode);
Chris@0 192 $edit = $this->getEditValues($values, $langcode) + ['content_translation[retranslate]' => TRUE];
Chris@0 193 $entity_type_id = $entity->getEntityTypeId();
Chris@0 194 $add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
Chris@0 195 $entity->getEntityTypeId() => $entity->id(),
Chris@0 196 'source' => $source_langcode,
Chris@17 197 'target' => $langcode,
Chris@0 198 ], ['language' => $language]);
Chris@0 199 $this->drupalPostForm($add_url, $edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode));
Chris@0 200 $storage->resetCache([$this->entityId]);
Chris@0 201 $entity = $storage->load($this->entityId);
Chris@18 202 $this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
Chris@0 203 $this->assertText('Source language', 'Source language column correctly shown.');
Chris@0 204
Chris@0 205 // Check that the entered values have been correctly stored.
Chris@0 206 foreach ($values as $langcode => $property_values) {
Chris@0 207 $translation = $this->getTranslation($entity, $langcode);
Chris@0 208 foreach ($property_values as $property => $value) {
Chris@0 209 $stored_value = $this->getValue($translation, $property, $langcode);
Chris@0 210 $value = is_array($value) ? $value[0]['value'] : $value;
Chris@0 211 $message = format_string('%property correctly stored with language %language.', ['%property' => $property, '%language' => $langcode]);
Chris@0 212 $this->assertEqual($stored_value, $value, $message);
Chris@0 213 }
Chris@0 214 }
Chris@0 215 }
Chris@0 216
Chris@0 217 /**
Chris@0 218 * Tests that the translation overview shows the correct values.
Chris@0 219 */
Chris@0 220 protected function doTestTranslationOverview() {
Chris@0 221 $storage = $this->container->get('entity_type.manager')
Chris@0 222 ->getStorage($this->entityTypeId);
Chris@0 223 $storage->resetCache([$this->entityId]);
Chris@0 224 $entity = $storage->load($this->entityId);
Chris@18 225 $translate_url = $entity->toUrl('drupal:content-translation-overview');
Chris@0 226 $this->drupalGet($translate_url);
Chris@0 227 $translate_url->setAbsolute(FALSE);
Chris@0 228
Chris@0 229 foreach ($this->langcodes as $langcode) {
Chris@0 230 if ($entity->hasTranslation($langcode)) {
Chris@0 231 $language = new Language(['id' => $langcode]);
Chris@18 232 $view_url = $entity->toUrl('canonical', ['language' => $language])->toString();
Chris@0 233 $elements = $this->xpath('//table//a[@href=:href]', [':href' => $view_url]);
Chris@0 234 $this->assertEqual((string) $elements[0], $entity->getTranslation($langcode)->label(), format_string('Label correctly shown for %language translation.', ['%language' => $langcode]));
Chris@18 235 $edit_path = $entity->toUrl('edit-form', ['language' => $language])->toString();
Chris@0 236 $elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a[@href=:href]', [':href' => $edit_path]);
Chris@0 237 $this->assertEqual((string) $elements[0], t('Edit'), format_string('Edit link correct for %language translation.', ['%language' => $langcode]));
Chris@0 238 }
Chris@0 239 }
Chris@0 240 }
Chris@0 241
Chris@0 242 /**
Chris@0 243 * Tests up-to-date status tracking.
Chris@0 244 */
Chris@0 245 protected function doTestOutdatedStatus() {
Chris@0 246 $storage = $this->container->get('entity_type.manager')
Chris@0 247 ->getStorage($this->entityTypeId);
Chris@0 248 $storage->resetCache([$this->entityId]);
Chris@0 249 $entity = $storage->load($this->entityId);
Chris@0 250 $langcode = 'fr';
Chris@0 251 $languages = \Drupal::languageManager()->getLanguages();
Chris@0 252
Chris@0 253 // Mark translations as outdated.
Chris@0 254 $edit = ['content_translation[retranslate]' => TRUE];
Chris@18 255 $edit_path = $entity->toUrl('edit-form', ['language' => $languages[$langcode]]);
Chris@0 256 $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
Chris@0 257 $storage->resetCache([$this->entityId]);
Chris@0 258 $entity = $storage->load($this->entityId);
Chris@0 259
Chris@0 260 // Check that every translation has the correct "outdated" status, and that
Chris@0 261 // the Translation fieldset is open if the translation is "outdated".
Chris@0 262 foreach ($this->langcodes as $added_langcode) {
Chris@18 263 $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($added_langcode)]);
Chris@0 264 $this->drupalGet($url);
Chris@0 265 if ($added_langcode == $langcode) {
Chris@0 266 $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
Chris@0 267 $this->assertFalse($this->xpath('//details[@id="edit-content-translation" and @open="open"]'), 'The translation tab should be collapsed by default.');
Chris@0 268 }
Chris@0 269 else {
Chris@0 270 $this->assertFieldByXPath('//input[@name="content_translation[outdated]"]', TRUE, 'The translate flag is checked by default.');
Chris@0 271 $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 272 $edit = ['content_translation[outdated]' => FALSE];
Chris@0 273 $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $added_langcode));
Chris@0 274 $this->drupalGet($url);
Chris@0 275 $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
Chris@0 276 $storage = $this->container->get('entity_type.manager')
Chris@0 277 ->getStorage($this->entityTypeId);
Chris@0 278 $storage->resetCache([$this->entityId]);
Chris@0 279 $entity = $storage->load($this->entityId);
Chris@0 280 $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.');
Chris@0 281 }
Chris@0 282 }
Chris@0 283 }
Chris@0 284
Chris@0 285 /**
Chris@0 286 * Tests the translation publishing status.
Chris@0 287 */
Chris@0 288 protected function doTestPublishedStatus() {
Chris@0 289 $storage = $this->container->get('entity_type.manager')
Chris@0 290 ->getStorage($this->entityTypeId);
Chris@0 291 $storage->resetCache([$this->entityId]);
Chris@0 292 $entity = $storage->load($this->entityId);
Chris@0 293
Chris@0 294 // Unpublish translations.
Chris@0 295 foreach ($this->langcodes as $index => $langcode) {
Chris@0 296 if ($index > 0) {
Chris@18 297 $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]);
Chris@0 298 $edit = ['content_translation[status]' => FALSE];
Chris@0 299 $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
Chris@0 300 $storage = $this->container->get('entity_type.manager')
Chris@0 301 ->getStorage($this->entityTypeId);
Chris@0 302 $storage->resetCache([$this->entityId]);
Chris@0 303 $entity = $storage->load($this->entityId);
Chris@0 304 $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
Chris@0 305 }
Chris@0 306 }
Chris@0 307
Chris@0 308 // Check that the last published translation cannot be unpublished.
Chris@18 309 $this->drupalGet($entity->toUrl('edit-form'));
Chris@0 310 $this->assertFieldByXPath('//input[@name="content_translation[status]" and @disabled="disabled"]', TRUE, 'The last translation is published and cannot be unpublished.');
Chris@0 311 }
Chris@0 312
Chris@0 313 /**
Chris@0 314 * Tests the translation authoring information.
Chris@0 315 */
Chris@0 316 protected function doTestAuthoringInfo() {
Chris@0 317 $storage = $this->container->get('entity_type.manager')
Chris@0 318 ->getStorage($this->entityTypeId);
Chris@0 319 $storage->resetCache([$this->entityId]);
Chris@0 320 $entity = $storage->load($this->entityId);
Chris@0 321 $values = [];
Chris@0 322
Chris@0 323 // Post different authoring information for each translation.
Chris@0 324 foreach ($this->langcodes as $index => $langcode) {
Chris@0 325 $user = $this->drupalCreateUser();
Chris@0 326 $values[$langcode] = [
Chris@0 327 'uid' => $user->id(),
Chris@0 328 'created' => REQUEST_TIME - mt_rand(0, 1000),
Chris@0 329 ];
Chris@0 330 $edit = [
Chris@18 331 'content_translation[uid]' => $user->getAccountName(),
Chris@18 332 'content_translation[created]' => $this->container->get('date.formatter')->format($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
Chris@0 333 ];
Chris@18 334 $url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]);
Chris@0 335 $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
Chris@0 336 }
Chris@0 337
Chris@0 338 $storage = $this->container->get('entity_type.manager')
Chris@0 339 ->getStorage($this->entityTypeId);
Chris@0 340 $storage->resetCache([$this->entityId]);
Chris@0 341 $entity = $storage->load($this->entityId);
Chris@0 342 foreach ($this->langcodes as $langcode) {
Chris@0 343 $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
Chris@0 344 $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
Chris@0 345 $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
Chris@0 346 }
Chris@0 347
Chris@0 348 // Try to post non valid values and check that they are rejected.
Chris@0 349 $langcode = 'en';
Chris@0 350 $edit = [
Chris@0 351 // User names have by default length 8.
Chris@0 352 'content_translation[uid]' => $this->randomMachineName(12),
Chris@0 353 'content_translation[created]' => '19/11/1978',
Chris@0 354 ];
Chris@18 355 $this->drupalPostForm($entity->toUrl('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode));
Chris@0 356 $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.');
Chris@0 357 $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
Chris@0 358 $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
Chris@0 359 $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
Chris@0 360 }
Chris@0 361
Chris@0 362 /**
Chris@0 363 * Tests translation deletion.
Chris@0 364 */
Chris@0 365 protected function doTestTranslationDeletion() {
Chris@0 366 // Confirm and delete a translation.
Chris@0 367 $this->drupalLogin($this->translator);
Chris@0 368 $langcode = 'fr';
Chris@0 369 $storage = $this->container->get('entity_type.manager')
Chris@0 370 ->getStorage($this->entityTypeId);
Chris@0 371 $storage->resetCache([$this->entityId]);
Chris@0 372 $entity = $storage->load($this->entityId);
Chris@0 373 $language = ConfigurableLanguage::load($langcode);
Chris@18 374 $url = $entity->toUrl('edit-form', ['language' => $language]);
Chris@0 375 $this->drupalPostForm($url, [], t('Delete translation'));
Chris@0 376 $this->drupalPostForm(NULL, [], t('Delete @language translation', ['@language' => $language->getName()]));
Chris@0 377 $storage->resetCache([$this->entityId]);
Chris@0 378 $entity = $storage->load($this->entityId, TRUE);
Chris@0 379 if ($this->assertTrue(is_object($entity), 'Entity found')) {
Chris@0 380 $translations = $entity->getTranslationLanguages();
Chris@0 381 $this->assertTrue(count($translations) == 2 && empty($translations[$langcode]), 'Translation successfully deleted.');
Chris@0 382 }
Chris@0 383
Chris@0 384 // Check that the translator cannot delete the original translation.
Chris@0 385 $args = [$this->entityTypeId => $entity->id(), 'language' => 'en'];
Chris@0 386 $this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args));
Chris@0 387 $this->assertResponse(403);
Chris@0 388 }
Chris@0 389
Chris@0 390 /**
Chris@0 391 * Returns an array of entity field values to be tested.
Chris@0 392 */
Chris@0 393 protected function getNewEntityValues($langcode) {
Chris@0 394 return [$this->fieldName => [['value' => $this->randomMachineName(16)]]];
Chris@0 395 }
Chris@0 396
Chris@0 397 /**
Chris@0 398 * Returns an edit array containing the values to be posted.
Chris@0 399 */
Chris@0 400 protected function getEditValues($values, $langcode, $new = FALSE) {
Chris@0 401 $edit = $values[$langcode];
Chris@0 402 $langcode = $new ? LanguageInterface::LANGCODE_NOT_SPECIFIED : $langcode;
Chris@0 403 foreach ($values[$langcode] as $property => $value) {
Chris@0 404 if (is_array($value)) {
Chris@0 405 $edit["{$property}[0][value]"] = $value[0]['value'];
Chris@0 406 unset($edit[$property]);
Chris@0 407 }
Chris@0 408 }
Chris@0 409 return $edit;
Chris@0 410 }
Chris@0 411
Chris@0 412 /**
Chris@0 413 * Returns the form action value when submitting a new translation.
Chris@0 414 *
Chris@0 415 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 416 * The entity being tested.
Chris@0 417 * @param string $langcode
Chris@0 418 * Language code for the form.
Chris@0 419 *
Chris@0 420 * @return string
Chris@0 421 * Name of the button to hit.
Chris@0 422 */
Chris@0 423 protected function getFormSubmitActionForNewTranslation(EntityInterface $entity, $langcode) {
Chris@0 424 $entity->addTranslation($langcode, $entity->toArray());
Chris@0 425 return $this->getFormSubmitAction($entity, $langcode);
Chris@0 426 }
Chris@0 427
Chris@0 428 /**
Chris@0 429 * Returns the form action value to be used to submit the entity form.
Chris@0 430 *
Chris@0 431 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 432 * The entity being tested.
Chris@0 433 * @param string $langcode
Chris@0 434 * Language code for the form.
Chris@0 435 *
Chris@0 436 * @return string
Chris@0 437 * Name of the button to hit.
Chris@0 438 */
Chris@0 439 protected function getFormSubmitAction(EntityInterface $entity, $langcode) {
Chris@0 440 return t('Save') . $this->getFormSubmitSuffix($entity, $langcode);
Chris@0 441 }
Chris@0 442
Chris@0 443 /**
Chris@0 444 * Returns appropriate submit button suffix based on translatability.
Chris@0 445 *
Chris@0 446 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 447 * The entity being tested.
Chris@0 448 * @param string $langcode
Chris@0 449 * Language code for the form.
Chris@0 450 *
Chris@0 451 * @return string
Chris@0 452 * Submit button suffix based on translatability.
Chris@0 453 */
Chris@0 454 protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) {
Chris@0 455 return '';
Chris@0 456 }
Chris@0 457
Chris@0 458 /**
Chris@0 459 * Returns the translation object to use to retrieve the translated values.
Chris@0 460 *
Chris@0 461 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 462 * The entity being tested.
Chris@0 463 * @param string $langcode
Chris@0 464 * The language code identifying the translation to be retrieved.
Chris@0 465 *
Chris@0 466 * @return \Drupal\Core\TypedData\TranslatableInterface
Chris@0 467 * The translation object to act on.
Chris@0 468 */
Chris@0 469 protected function getTranslation(EntityInterface $entity, $langcode) {
Chris@0 470 return $entity->getTranslation($langcode);
Chris@0 471 }
Chris@0 472
Chris@0 473 /**
Chris@0 474 * Returns the value for the specified property in the given language.
Chris@0 475 *
Chris@0 476 * @param \Drupal\Core\Entity\EntityInterface $translation
Chris@0 477 * The translation object the property value should be retrieved from.
Chris@0 478 * @param string $property
Chris@0 479 * The property name.
Chris@0 480 * @param string $langcode
Chris@0 481 * The property value.
Chris@0 482 *
Chris@0 483 * @return
Chris@0 484 * The property value.
Chris@0 485 */
Chris@0 486 protected function getValue(EntityInterface $translation, $property, $langcode) {
Chris@0 487 $key = $property == 'user_id' ? 'target_id' : 'value';
Chris@0 488 return $translation->get($property)->{$key};
Chris@0 489 }
Chris@0 490
Chris@0 491 /**
Chris@0 492 * Returns the name of the field that implements the changed timestamp.
Chris@0 493 *
Chris@0 494 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 495 * The entity being tested.
Chris@0 496 *
Chris@0 497 * @return string
Chris@0 498 * The field name.
Chris@0 499 */
Chris@0 500 protected function getChangedFieldName($entity) {
Chris@0 501 return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
Chris@0 502 }
Chris@0 503
Chris@0 504 /**
Chris@0 505 * Tests edit content translation.
Chris@0 506 */
Chris@0 507 protected function doTestTranslationEdit() {
Chris@0 508 $storage = $this->container->get('entity_type.manager')
Chris@0 509 ->getStorage($this->entityTypeId);
Chris@0 510 $storage->resetCache([$this->entityId]);
Chris@0 511 $entity = $storage->load($this->entityId);
Chris@0 512 $languages = $this->container->get('language_manager')->getLanguages();
Chris@0 513
Chris@0 514 foreach ($this->langcodes as $langcode) {
Chris@0 515 // We only want to test the title for non-english translations.
Chris@0 516 if ($langcode != 'en') {
Chris@0 517 $options = ['language' => $languages[$langcode]];
Chris@18 518 $url = $entity->toUrl('edit-form', $options);
Chris@0 519 $this->drupalGet($url);
Chris@0 520
Chris@0 521 $this->assertRaw($entity->getTranslation($langcode)->label());
Chris@0 522 }
Chris@0 523 }
Chris@0 524 }
Chris@0 525
Chris@0 526 /**
Chris@0 527 * Tests the basic translation workflow.
Chris@0 528 */
Chris@0 529 protected function doTestTranslationChanged() {
Chris@0 530 $storage = $this->container->get('entity_type.manager')
Chris@0 531 ->getStorage($this->entityTypeId);
Chris@0 532 $storage->resetCache([$this->entityId]);
Chris@0 533 $entity = $storage->load($this->entityId);
Chris@0 534 $changed_field_name = $this->getChangedFieldName($entity);
Chris@0 535 $definition = $entity->getFieldDefinition($changed_field_name);
Chris@0 536 $config = $definition->getConfig($entity->bundle());
Chris@0 537
Chris@0 538 foreach ([FALSE, TRUE] as $translatable_changed_field) {
Chris@0 539 if ($definition->isTranslatable()) {
Chris@0 540 // For entities defining a translatable changed field we want to test
Chris@0 541 // the correct behavior of that field even if the translatability is
Chris@0 542 // revoked. In that case the changed timestamp should be synchronized
Chris@0 543 // across all translations.
Chris@0 544 $config->setTranslatable($translatable_changed_field);
Chris@0 545 $config->save();
Chris@0 546 }
Chris@0 547 elseif ($translatable_changed_field) {
Chris@0 548 // For entities defining a non-translatable changed field we cannot
Chris@0 549 // declare the field as translatable on the fly by modifying its config
Chris@0 550 // because the schema doesn't support this.
Chris@0 551 break;
Chris@0 552 }
Chris@0 553
Chris@0 554 foreach ($entity->getTranslationLanguages() as $language) {
Chris@0 555 // Ensure different timestamps.
Chris@0 556 sleep(1);
Chris@0 557
Chris@0 558 $langcode = $language->getId();
Chris@0 559
Chris@0 560 $edit = [
Chris@0 561 $this->fieldName . '[0][value]' => $this->randomString(),
Chris@0 562 ];
Chris@18 563 $edit_path = $entity->toUrl('edit-form', ['language' => $language]);
Chris@0 564 $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
Chris@0 565
Chris@0 566 $storage = $this->container->get('entity_type.manager')
Chris@0 567 ->getStorage($this->entityTypeId);
Chris@0 568 $storage->resetCache([$this->entityId]);
Chris@0 569 $entity = $storage->load($this->entityId);
Chris@0 570 $this->assertEqual(
Chris@0 571 $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
Chris@0 572 format_string('Changed time for language %language is the latest change over all languages.', ['%language' => $language->getName()])
Chris@0 573 );
Chris@0 574 }
Chris@0 575
Chris@0 576 $timestamps = [];
Chris@0 577 foreach ($entity->getTranslationLanguages() as $language) {
Chris@0 578 $next_timestamp = $entity->getTranslation($language->getId())->getChangedTime();
Chris@0 579 if (!in_array($next_timestamp, $timestamps)) {
Chris@0 580 $timestamps[] = $next_timestamp;
Chris@0 581 }
Chris@0 582 }
Chris@0 583
Chris@0 584 if ($translatable_changed_field) {
Chris@0 585 $this->assertEqual(
Chris@0 586 count($timestamps), count($entity->getTranslationLanguages()),
Chris@0 587 'All timestamps from all languages are different.'
Chris@0 588 );
Chris@0 589 }
Chris@0 590 else {
Chris@0 591 $this->assertEqual(
Chris@0 592 count($timestamps), 1,
Chris@0 593 'All timestamps from all languages are identical.'
Chris@0 594 );
Chris@0 595 }
Chris@0 596 }
Chris@0 597 }
Chris@0 598
Chris@0 599 /**
Chris@0 600 * Test the changed time after API and FORM save without changes.
Chris@0 601 */
Chris@0 602 public function doTestChangedTimeAfterSaveWithoutChanges() {
Chris@0 603 $storage = $this->container->get('entity_type.manager')
Chris@0 604 ->getStorage($this->entityTypeId);
Chris@0 605 $storage->resetCache([$this->entityId]);
Chris@0 606 $entity = $storage->load($this->entityId);
Chris@0 607 // Test only entities, which implement the EntityChangedInterface.
Chris@0 608 if ($entity instanceof EntityChangedInterface) {
Chris@0 609 $changed_timestamp = $entity->getChangedTime();
Chris@0 610
Chris@0 611 $entity->save();
Chris@0 612 $storage = $this->container->get('entity_type.manager')
Chris@0 613 ->getStorage($this->entityTypeId);
Chris@0 614 $storage->resetCache([$this->entityId]);
Chris@0 615 $entity = $storage->load($this->entityId);
Chris@0 616 $this->assertEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time wasn\'t updated after API save without changes.');
Chris@0 617
Chris@0 618 // Ensure different save timestamps.
Chris@0 619 sleep(1);
Chris@0 620
Chris@0 621 // Save the entity on the regular edit form.
Chris@0 622 $language = $entity->language();
Chris@18 623 $edit_path = $entity->toUrl('edit-form', ['language' => $language]);
Chris@0 624 $this->drupalPostForm($edit_path, [], $this->getFormSubmitAction($entity, $language->getId()));
Chris@0 625
Chris@0 626 $storage->resetCache([$this->entityId]);
Chris@0 627 $entity = $storage->load($this->entityId);
Chris@0 628 $this->assertNotEqual($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time was updated after form save without changes.');
Chris@0 629 }
Chris@0 630 }
Chris@0 631
Chris@0 632 }