3 namespace Drupal\content_translation\Tests;
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\user\UserInterface;
11 * Tests the content translation workflows for the test entity.
13 * @group content_translation
15 class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
18 * The entity used for testing.
20 * @var \Drupal\Core\Entity\EntityInterface
29 public static $modules = ['language', 'content_translation', 'entity_test'];
31 protected function setUp() {
39 protected function getTranslatorPermissions() {
40 $permissions = parent::getTranslatorPermissions();
41 $permissions[] = 'view test entity';
49 protected function getEditorPermissions() {
50 return ['administer entity_test content'];
54 * Creates a test entity and translate it.
56 protected function setupEntity() {
57 $default_langcode = $this->langcodes[0];
59 // Create a test entity.
60 $user = $this->drupalCreateUser();
62 'name' => $this->randomMachineName(),
63 'user_id' => $user->id(),
64 $this->fieldName => [['value' => $this->randomMachineName(16)]],
66 $id = $this->createEntity($values, $default_langcode);
67 $storage = $this->container->get('entity_type.manager')
68 ->getStorage($this->entityTypeId);
69 $storage->resetCache([$id]);
70 $this->entity = $storage->load($id);
72 // Create a translation.
73 $this->drupalLogin($this->translator);
74 $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $this->langcodes[2]]);
75 $this->drupalPostForm($add_translation_url, [], t('Save'));
76 $this->rebuildContainer();
80 * Test simple and editorial translation workflows.
82 public function testWorkflows() {
83 // Test workflows for the editor.
88 'add_translation' => 403,
89 'edit_translation' => 403,
90 'delete_translation' => 403,
92 $this->doTestWorkflows($this->editor, $expected_status);
94 // Test workflows for the translator.
99 'add_translation' => 200,
100 'edit_translation' => 200,
101 'delete_translation' => 200,
103 $this->doTestWorkflows($this->translator, $expected_status);
105 // Test workflows for the admin.
110 'add_translation' => 200,
111 'edit_translation' => 403,
112 'delete_translation' => 403,
114 $this->doTestWorkflows($this->administrator, $expected_status);
116 // Check that translation permissions allow the associated operations.
117 $ops = ['create' => t('Add'), 'update' => t('Edit'), 'delete' => t('Delete')];
118 $translations_url = $this->entity->urlInfo('drupal:content-translation-overview');
119 foreach ($ops as $current_op => $item) {
120 $user = $this->drupalCreateUser([$this->getTranslatePermission(), "$current_op content translations", 'view test entity']);
121 $this->drupalLogin($user);
122 $this->drupalGet($translations_url);
124 // Make sure that the user.permissions cache context and the cache tags
125 // for the entity are present.
126 $this->assertCacheContext('user.permissions');
127 foreach ($this->entity->getCacheTags() as $cache_tag) {
128 $this->assertCacheTag($cache_tag);
131 foreach ($ops as $op => $label) {
132 if ($op != $current_op) {
133 $this->assertNoLink($label, format_string('No %op link found.', ['%op' => $label]));
136 $this->assertLink($label, 0, format_string('%op link found.', ['%op' => $label]));
143 * Checks that workflows have the expected behaviors for the given user.
145 * @param \Drupal\user\UserInterface $user
146 * The user to test the workflow behavior against.
147 * @param array $expected_status
148 * The an associative array with the operation name as key and the expected
151 protected function doTestWorkflows(UserInterface $user, $expected_status) {
152 $default_langcode = $this->langcodes[0];
153 $languages = $this->container->get('language_manager')->getLanguages();
154 $args = ['@user_label' => $user->getUsername()];
155 $options = ['language' => $languages[$default_langcode], 'absolute' => TRUE];
156 $this->drupalLogin($user);
158 // Check whether the user is allowed to access the entity form in edit mode.
159 $edit_url = $this->entity->urlInfo('edit-form', $options);
160 $this->drupalGet($edit_url, $options);
161 $this->assertResponse($expected_status['edit'], SafeMarkup::format('The @user_label has the expected edit access.', $args));
163 // Check whether the user is allowed to access the entity delete form.
164 $delete_url = $this->entity->urlInfo('delete-form', $options);
165 $this->drupalGet($delete_url, $options);
166 $this->assertResponse($expected_status['delete'], SafeMarkup::format('The @user_label has the expected delete access.', $args));
168 // Check whether the user is allowed to access the translation overview.
169 $langcode = $this->langcodes[1];
170 $options['language'] = $languages[$langcode];
171 $translations_url = $this->entity->url('drupal:content-translation-overview', $options);
172 $this->drupalGet($translations_url);
173 $this->assertResponse($expected_status['overview'], SafeMarkup::format('The @user_label has the expected translation overview access.', $args));
175 // Check whether the user is allowed to create a translation.
176 $add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options);
177 if ($expected_status['add_translation'] == 200) {
178 $this->clickLink('Add');
179 $this->assertUrl($add_translation_url->toString(), [], 'The translation overview points to the translation form when creating translations.');
180 // Check that the translation form does not contain shared elements for
182 if ($expected_status['edit'] == 403) {
183 $this->assertNoSharedElements();
187 $this->drupalGet($add_translation_url);
189 $this->assertResponse($expected_status['add_translation'], SafeMarkup::format('The @user_label has the expected translation creation access.', $args));
191 // Check whether the user is allowed to edit a translation.
192 $langcode = $this->langcodes[2];
193 $options['language'] = $languages[$langcode];
194 $edit_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_edit", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
195 if ($expected_status['edit_translation'] == 200) {
196 $this->drupalGet($translations_url);
197 $editor = $expected_status['edit'] == 200;
200 $this->clickLink('Edit', 2);
201 // An editor should be pointed to the entity form in multilingual mode.
202 // We need a new expected edit path with a new language.
203 $expected_edit_path = $this->entity->url('edit-form', $options);
204 $this->assertUrl($expected_edit_path, [], 'The translation overview points to the edit form for editors when editing translations.');
207 $this->clickLink('Edit');
208 // While a translator should be pointed to the translation form.
209 $this->assertUrl($edit_translation_url->toString(), [], 'The translation overview points to the translation form for translators when editing translations.');
210 // Check that the translation form does not contain shared elements.
211 $this->assertNoSharedElements();
215 $this->drupalGet($edit_translation_url);
217 $this->assertResponse($expected_status['edit_translation'], SafeMarkup::format('The @user_label has the expected translation edit access.', $args));
219 // Check whether the user is allowed to delete a translation.
220 $langcode = $this->langcodes[2];
221 $options['language'] = $languages[$langcode];
222 $delete_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
223 if ($expected_status['delete_translation'] == 200) {
224 $this->drupalGet($translations_url);
225 $editor = $expected_status['delete'] == 200;
228 $this->clickLink('Delete', 2);
229 // An editor should be pointed to the entity deletion form in
230 // multilingual mode. We need a new expected delete path with a new
232 $expected_delete_path = $this->entity->url('delete-form', $options);
233 $this->assertUrl($expected_delete_path, [], 'The translation overview points to the delete form for editors when deleting translations.');
236 $this->clickLink('Delete');
237 // While a translator should be pointed to the translation deletion
239 $this->assertUrl($delete_translation_url->toString(), [], 'The translation overview points to the translation deletion form for translators when deleting translations.');
243 $this->drupalGet($delete_translation_url);
245 $this->assertResponse($expected_status['delete_translation'], SafeMarkup::format('The @user_label has the expected translation deletion access.', $args));
249 * Assert that the current page does not contain shared form elements.
251 protected function assertNoSharedElements() {
252 $language_none = LanguageInterface::LANGCODE_NOT_SPECIFIED;
253 return $this->assertNoFieldByXPath("//input[@name='field_test_text[$language_none][0][value]']", NULL, 'Shared elements are not available on the translation form.');