Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / modules / link / tests / src / Functional / LinkFieldTest.php
1 <?php
2
3 namespace Drupal\Tests\link\Functional;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Url;
8 use Drupal\entity_test\Entity\EntityTest;
9 use Drupal\field\Entity\FieldConfig;
10 use Drupal\link\LinkItemInterface;
11 use Drupal\node\NodeInterface;
12 use Drupal\Tests\BrowserTestBase;
13 use Drupal\field\Entity\FieldStorageConfig;
14
15 /**
16  * Tests link field widgets and formatters.
17  *
18  * @group link
19  */
20 class LinkFieldTest extends BrowserTestBase {
21
22   /**
23    * Modules to enable.
24    *
25    * @var array
26    */
27   public static $modules = [
28     'entity_test',
29     'link',
30     'node',
31     'link_test_base_field',
32   ];
33
34   /**
35    * A field to use in this test class.
36    *
37    * @var \Drupal\field\Entity\FieldStorageConfig
38    */
39   protected $fieldStorage;
40
41   /**
42    * The instance used in this test class.
43    *
44    * @var \Drupal\field\Entity\FieldConfig
45    */
46   protected $field;
47
48   protected function setUp() {
49     parent::setUp();
50
51     $this->drupalLogin($this->drupalCreateUser([
52       'view test entity',
53       'administer entity_test content',
54       'link to any page',
55     ]));
56   }
57
58   /**
59    * Tests link field URL validation.
60    */
61   public function testURLValidation() {
62     $field_name = mb_strtolower($this->randomMachineName());
63     // Create a field with settings to validate.
64     $this->fieldStorage = FieldStorageConfig::create([
65       'field_name' => $field_name,
66       'entity_type' => 'entity_test',
67       'type' => 'link',
68     ]);
69     $this->fieldStorage->save();
70     $this->field = FieldConfig::create([
71       'field_storage' => $this->fieldStorage,
72       'bundle' => 'entity_test',
73       'settings' => [
74         'title' => DRUPAL_DISABLED,
75         'link_type' => LinkItemInterface::LINK_GENERIC,
76       ],
77     ]);
78     $this->field->save();
79     entity_get_form_display('entity_test', 'entity_test', 'default')
80       ->setComponent($field_name, [
81         'type' => 'link_default',
82         'settings' => [
83           'placeholder_url' => 'http://example.com',
84         ],
85       ])
86       ->save();
87     entity_get_display('entity_test', 'entity_test', 'full')
88       ->setComponent($field_name, [
89         'type' => 'link',
90       ])
91       ->save();
92
93     // Display creation form.
94     $this->drupalGet('entity_test/add');
95     $this->assertFieldByName("{$field_name}[0][uri]", '', 'Link URL field is displayed');
96     $this->assertRaw('placeholder="http://example.com"');
97
98     // Create a path alias.
99     \Drupal::service('path.alias_storage')->save('/admin', '/a/path/alias');
100
101     // Create a node to test the link widget.
102     $node = $this->drupalCreateNode();
103
104     $restricted_node = $this->drupalCreateNode(['status' => NodeInterface::NOT_PUBLISHED]);
105
106     // Define some valid URLs (keys are the entered values, values are the
107     // strings displayed to the user).
108     $valid_external_entries = [
109       'http://www.example.com/' => 'http://www.example.com/',
110       // Strings within parenthesis without leading space char.
111       'http://www.example.com/strings_(string_within_parenthesis)' => 'http://www.example.com/strings_(string_within_parenthesis)',
112       // Numbers within parenthesis without leading space char.
113       'http://www.example.com/numbers_(9999)' => 'http://www.example.com/numbers_(9999)',
114     ];
115     $valid_internal_entries = [
116       '/entity_test/add' => '/entity_test/add',
117       '/a/path/alias' => '/a/path/alias',
118
119       // Front page, with query string and fragment.
120       '/' => '&lt;front&gt;',
121       '/?example=llama' => '&lt;front&gt;?example=llama',
122       '/#example' => '&lt;front&gt;#example',
123
124       // Trailing spaces should be ignored.
125       '/ ' => '&lt;front&gt;',
126       '/path with spaces ' => '/path with spaces',
127
128       // @todo '<front>' is valid input for BC reasons, may be removed by
129       //   https://www.drupal.org/node/2421941
130       '<front>' => '&lt;front&gt;',
131       '<front>#example' => '&lt;front&gt;#example',
132       '<front>?example=llama' => '&lt;front&gt;?example=llama',
133
134       // Query string and fragment.
135       '?example=llama' => '?example=llama',
136       '#example' => '#example',
137
138       // Entity reference autocomplete value.
139       $node->label() . ' (1)' => $node->label() . ' (1)',
140       // Entity URI displayed as ER autocomplete value when displayed in a form.
141       'entity:node/1' => $node->label() . ' (1)',
142       // URI for an entity that exists, but is not accessible by the user.
143       'entity:node/' . $restricted_node->id() => '- Restricted access - (' . $restricted_node->id() . ')',
144       // URI for an entity that doesn't exist, but with a valid ID.
145       'entity:user/999999' => 'entity:user/999999',
146     ];
147
148     // Define some invalid URLs.
149     $validation_error_1 = "The path '@link_path' is invalid.";
150     $validation_error_2 = 'Manually entered paths should start with /, ? or #.';
151     $validation_error_3 = "The path '@link_path' is inaccessible.";
152     $invalid_external_entries = [
153       // Invalid protocol
154       'invalid://not-a-valid-protocol' => $validation_error_1,
155       // Missing host name
156       'http://' => $validation_error_1,
157     ];
158     $invalid_internal_entries = [
159       'no-leading-slash' => $validation_error_2,
160       'entity:non_existing_entity_type/yar' => $validation_error_1,
161       // URI for an entity that doesn't exist, with an invalid ID.
162       'entity:user/invalid-parameter' => $validation_error_1,
163     ];
164
165     // Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
166     $this->assertValidEntries($field_name, $valid_external_entries + $valid_internal_entries);
167     $this->assertInvalidEntries($field_name, $invalid_external_entries + $invalid_internal_entries);
168
169     // Test external URLs for 'link_type' = LinkItemInterface::LINK_EXTERNAL.
170     $this->field->setSetting('link_type', LinkItemInterface::LINK_EXTERNAL);
171     $this->field->save();
172     $this->assertValidEntries($field_name, $valid_external_entries);
173     $this->assertInvalidEntries($field_name, $valid_internal_entries + $invalid_external_entries);
174
175     // Test external URLs for 'link_type' = LinkItemInterface::LINK_INTERNAL.
176     $this->field->setSetting('link_type', LinkItemInterface::LINK_INTERNAL);
177     $this->field->save();
178     $this->assertValidEntries($field_name, $valid_internal_entries);
179     $this->assertInvalidEntries($field_name, $valid_external_entries + $invalid_internal_entries);
180
181     // Ensure that users with 'link to any page', don't apply access checking.
182     $this->drupalLogin($this->drupalCreateUser([
183       'view test entity',
184       'administer entity_test content',
185     ]));
186     $this->assertValidEntries($field_name, ['/entity_test/add' => '/entity_test/add']);
187     $this->assertInValidEntries($field_name, ['/admin' => $validation_error_3]);
188   }
189
190   /**
191    * Asserts that valid URLs can be submitted.
192    *
193    * @param string $field_name
194    *   The field name.
195    * @param array $valid_entries
196    *   An array of valid URL entries.
197    */
198   protected function assertValidEntries($field_name, array $valid_entries) {
199     foreach ($valid_entries as $uri => $string) {
200       $edit = [
201         "{$field_name}[0][uri]" => $uri,
202       ];
203       $this->drupalPostForm('entity_test/add', $edit, t('Save'));
204       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
205       $id = $match[1];
206       $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
207       $this->assertRaw('"' . $string . '"');
208     }
209   }
210
211   /**
212    * Asserts that invalid URLs cannot be submitted.
213    *
214    * @param string $field_name
215    *   The field name.
216    * @param array $invalid_entries
217    *   An array of invalid URL entries.
218    */
219   protected function assertInvalidEntries($field_name, array $invalid_entries) {
220     foreach ($invalid_entries as $invalid_value => $error_message) {
221       $edit = [
222         "{$field_name}[0][uri]" => $invalid_value,
223       ];
224       $this->drupalPostForm('entity_test/add', $edit, t('Save'));
225       $this->assertText(t($error_message, ['@link_path' => $invalid_value]));
226     }
227   }
228
229   /**
230    * Tests the link title settings of a link field.
231    */
232   public function testLinkTitle() {
233     $field_name = mb_strtolower($this->randomMachineName());
234     // Create a field with settings to validate.
235     $this->fieldStorage = FieldStorageConfig::create([
236       'field_name' => $field_name,
237       'entity_type' => 'entity_test',
238       'type' => 'link',
239     ]);
240     $this->fieldStorage->save();
241     $this->field = FieldConfig::create([
242       'field_storage' => $this->fieldStorage,
243       'bundle' => 'entity_test',
244       'label' => 'Read more about this entity',
245       'settings' => [
246         'title' => DRUPAL_OPTIONAL,
247         'link_type' => LinkItemInterface::LINK_GENERIC,
248       ],
249     ]);
250     $this->field->save();
251     entity_get_form_display('entity_test', 'entity_test', 'default')
252       ->setComponent($field_name, [
253         'type' => 'link_default',
254         'settings' => [
255           'placeholder_url' => 'http://example.com',
256           'placeholder_title' => 'Enter the text for this link',
257         ],
258       ])
259       ->save();
260     entity_get_display('entity_test', 'entity_test', 'full')
261       ->setComponent($field_name, [
262         'type' => 'link',
263         'label' => 'hidden',
264       ])
265       ->save();
266
267     // Verify that the link text field works according to the field setting.
268     foreach ([DRUPAL_DISABLED, DRUPAL_REQUIRED, DRUPAL_OPTIONAL] as $title_setting) {
269       // Update the link title field setting.
270       $this->field->setSetting('title', $title_setting);
271       $this->field->save();
272
273       // Display creation form.
274       $this->drupalGet('entity_test/add');
275       // Assert label is shown.
276       $this->assertText('Read more about this entity');
277       $this->assertFieldByName("{$field_name}[0][uri]", '', 'URL field found.');
278       $this->assertRaw('placeholder="http://example.com"');
279
280       if ($title_setting === DRUPAL_DISABLED) {
281         $this->assertNoFieldByName("{$field_name}[0][title]", '', 'Link text field not found.');
282         $this->assertNoRaw('placeholder="Enter the text for this link"');
283       }
284       else {
285         $this->assertRaw('placeholder="Enter the text for this link"');
286
287         $this->assertFieldByName("{$field_name}[0][title]", '', 'Link text field found.');
288         if ($title_setting === DRUPAL_OPTIONAL) {
289           // Verify that the URL is required, if the link text is non-empty.
290           $edit = [
291             "{$field_name}[0][title]" => 'Example',
292           ];
293           $this->drupalPostForm(NULL, $edit, t('Save'));
294           $this->assertText(t('The URL field is required when the @title field is specified.', ['@title' => t('Link text')]));
295         }
296         if ($title_setting === DRUPAL_REQUIRED) {
297           // Verify that the link text is required, if the URL is non-empty.
298           $edit = [
299             "{$field_name}[0][uri]" => 'http://www.example.com',
300           ];
301           $this->drupalPostForm(NULL, $edit, t('Save'));
302           $this->assertText(t('@title field is required if there is @uri input.', ['@title' => t('Link text'), '@uri' => t('URL')]));
303
304           // Verify that the link text is not required, if the URL is empty.
305           $edit = [
306             "{$field_name}[0][uri]" => '',
307           ];
308           $this->drupalPostForm(NULL, $edit, t('Save'));
309           $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
310
311           // Verify that a URL and link text meets requirements.
312           $this->drupalGet('entity_test/add');
313           $edit = [
314             "{$field_name}[0][uri]" => 'http://www.example.com',
315             "{$field_name}[0][title]" => 'Example',
316           ];
317           $this->drupalPostForm(NULL, $edit, t('Save'));
318           $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
319         }
320       }
321     }
322
323     // Verify that a link without link text is rendered using the URL as text.
324     $value = 'http://www.example.com/';
325     $edit = [
326       "{$field_name}[0][uri]" => $value,
327       "{$field_name}[0][title]" => '',
328     ];
329     $this->drupalPostForm(NULL, $edit, t('Save'));
330     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
331     $id = $match[1];
332     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
333
334     $output = $this->renderTestEntity($id);
335     $expected_link = (string) \Drupal::l($value, Url::fromUri($value));
336     $this->assertContains($expected_link, $output);
337
338     // Verify that a link with text is rendered using the link text.
339     $title = $this->randomMachineName();
340     $edit = [
341       "{$field_name}[0][title]" => $title,
342     ];
343     $this->drupalPostForm("entity_test/manage/$id/edit", $edit, t('Save'));
344     $this->assertText(t('entity_test @id has been updated.', ['@id' => $id]));
345
346     $output = $this->renderTestEntity($id);
347     $expected_link = (string) \Drupal::l($title, Url::fromUri($value));
348     $this->assertContains($expected_link, $output);
349   }
350
351   /**
352    * Tests the default 'link' formatter.
353    */
354   public function testLinkFormatter() {
355     $field_name = mb_strtolower($this->randomMachineName());
356     // Create a field with settings to validate.
357     $this->fieldStorage = FieldStorageConfig::create([
358       'field_name' => $field_name,
359       'entity_type' => 'entity_test',
360       'type' => 'link',
361       'cardinality' => 3,
362     ]);
363     $this->fieldStorage->save();
364     FieldConfig::create([
365       'field_storage' => $this->fieldStorage,
366       'label' => 'Read more about this entity',
367       'bundle' => 'entity_test',
368       'settings' => [
369         'title' => DRUPAL_OPTIONAL,
370         'link_type' => LinkItemInterface::LINK_GENERIC,
371       ],
372     ])->save();
373     entity_get_form_display('entity_test', 'entity_test', 'default')
374       ->setComponent($field_name, [
375         'type' => 'link_default',
376       ])
377       ->save();
378     $display_options = [
379       'type' => 'link',
380       'label' => 'hidden',
381     ];
382     entity_get_display('entity_test', 'entity_test', 'full')
383       ->setComponent($field_name, $display_options)
384       ->save();
385
386     // Create an entity with three link field values:
387     // - The first field item uses a URL only.
388     // - The second field item uses a URL and link text.
389     // - The third field item uses a fragment-only URL with text.
390     // For consistency in assertion code below, the URL is assigned to the title
391     // variable for the first field.
392     $this->drupalGet('entity_test/add');
393     $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
394     $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
395     $url3 = '#net';
396     $title1 = $url1;
397     // Intentionally contains an ampersand that needs sanitization on output.
398     $title2 = 'A very long & strange example title that could break the nice layout of the site';
399     $title3 = 'Fragment only';
400     $edit = [
401       "{$field_name}[0][uri]" => $url1,
402       // Note that $title1 is not submitted.
403       "{$field_name}[0][title]" => '',
404       "{$field_name}[1][uri]" => $url2,
405       "{$field_name}[1][title]" => $title2,
406       "{$field_name}[2][uri]" => $url3,
407       "{$field_name}[2][title]" => $title3,
408     ];
409     // Assert label is shown.
410     $this->assertText('Read more about this entity');
411     $this->drupalPostForm(NULL, $edit, t('Save'));
412     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
413     $id = $match[1];
414     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
415
416     // Verify that the link is output according to the formatter settings.
417     // Not using generatePermutations(), since that leads to 32 cases, which
418     // would not test actual link field formatter functionality but rather
419     // the link generator and options/attributes. Only 'url_plain' has a
420     // dependency on 'url_only', so we have a total of ~10 cases.
421     $options = [
422       'trim_length' => [NULL, 6],
423       'rel' => [NULL, 'nofollow'],
424       'target' => [NULL, '_blank'],
425       'url_only' => [
426         ['url_only' => FALSE],
427         ['url_only' => FALSE, 'url_plain' => TRUE],
428         ['url_only' => TRUE],
429         ['url_only' => TRUE, 'url_plain' => TRUE],
430       ],
431     ];
432     foreach ($options as $setting => $values) {
433       foreach ($values as $new_value) {
434         // Update the field formatter settings.
435         if (!is_array($new_value)) {
436           $display_options['settings'] = [$setting => $new_value];
437         }
438         else {
439           $display_options['settings'] = $new_value;
440         }
441         entity_get_display('entity_test', 'entity_test', 'full')
442           ->setComponent($field_name, $display_options)
443           ->save();
444
445         $output = $this->renderTestEntity($id);
446         switch ($setting) {
447           case 'trim_length':
448             $url = $url1;
449             $title = isset($new_value) ? Unicode::truncate($title1, $new_value, FALSE, TRUE) : $title1;
450             $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
451
452             $url = $url2;
453             $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
454             $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
455
456             $url = $url3;
457             $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
458             $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
459             break;
460
461           case 'rel':
462             $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
463             $this->assertContains('<a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($title1) . '</a>', $output);
464             $this->assertContains('<a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($title2) . '</a>', $output);
465             $this->assertContains('<a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($title3) . '</a>', $output);
466             break;
467
468           case 'target':
469             $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
470             $this->assertContains('<a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($title1) . '</a>', $output);
471             $this->assertContains('<a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($title2) . '</a>', $output);
472             $this->assertContains('<a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($title3) . '</a>', $output);
473             break;
474
475           case 'url_only':
476             // In this case, $new_value is an array.
477             if (!$new_value['url_only']) {
478               $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($title1) . '</a>', $output);
479               $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($title2) . '</a>', $output);
480               $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($title3) . '</a>', $output);
481             }
482             else {
483               if (empty($new_value['url_plain'])) {
484                 $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
485                 $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
486                 $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
487               }
488               else {
489                 $this->assertNotContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
490                 $this->assertNotContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
491                 $this->assertNotContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
492                 $this->assertContains(Html::escape($url1), $output);
493                 $this->assertContains(Html::escape($url2), $output);
494                 $this->assertContains(Html::escape($url3), $output);
495               }
496             }
497             break;
498         }
499       }
500     }
501   }
502
503   /**
504    * Tests the 'link_separate' formatter.
505    *
506    * This test is mostly the same as testLinkFormatter(), but they cannot be
507    * merged, since they involve different configuration and output.
508    */
509   public function testLinkSeparateFormatter() {
510     $field_name = mb_strtolower($this->randomMachineName());
511     // Create a field with settings to validate.
512     $this->fieldStorage = FieldStorageConfig::create([
513       'field_name' => $field_name,
514       'entity_type' => 'entity_test',
515       'type' => 'link',
516       'cardinality' => 3,
517     ]);
518     $this->fieldStorage->save();
519     FieldConfig::create([
520       'field_storage' => $this->fieldStorage,
521       'bundle' => 'entity_test',
522       'settings' => [
523         'title' => DRUPAL_OPTIONAL,
524         'link_type' => LinkItemInterface::LINK_GENERIC,
525       ],
526     ])->save();
527     $display_options = [
528       'type' => 'link_separate',
529       'label' => 'hidden',
530     ];
531     entity_get_form_display('entity_test', 'entity_test', 'default')
532       ->setComponent($field_name, [
533         'type' => 'link_default',
534       ])
535       ->save();
536     entity_get_display('entity_test', 'entity_test', 'full')
537       ->setComponent($field_name, $display_options)
538       ->save();
539
540     // Create an entity with three link field values:
541     // - The first field item uses a URL only.
542     // - The second field item uses a URL and link text.
543     // - The third field item uses a fragment-only URL with text.
544     // For consistency in assertion code below, the URL is assigned to the title
545     // variable for the first field.
546     $this->drupalGet('entity_test/add');
547     $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
548     $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
549     $url3 = '#net';
550     // Intentionally contains an ampersand that needs sanitization on output.
551     $title2 = 'A very long & strange example title that could break the nice layout of the site';
552     $title3 = 'Fragment only';
553     $edit = [
554       "{$field_name}[0][uri]" => $url1,
555       "{$field_name}[1][uri]" => $url2,
556       "{$field_name}[1][title]" => $title2,
557       "{$field_name}[2][uri]" => $url3,
558       "{$field_name}[2][title]" => $title3,
559     ];
560     $this->drupalPostForm(NULL, $edit, t('Save'));
561     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
562     $id = $match[1];
563     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
564
565     // Verify that the link is output according to the formatter settings.
566     $options = [
567       'trim_length' => [NULL, 6],
568       'rel' => [NULL, 'nofollow'],
569       'target' => [NULL, '_blank'],
570     ];
571     foreach ($options as $setting => $values) {
572       foreach ($values as $new_value) {
573         // Update the field formatter settings.
574         $display_options['settings'] = [$setting => $new_value];
575         entity_get_display('entity_test', 'entity_test', 'full')
576           ->setComponent($field_name, $display_options)
577           ->save();
578
579         $output = $this->renderTestEntity($id);
580         switch ($setting) {
581           case 'trim_length':
582             $url = $url1;
583             $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
584             $expected = '<div class="link-item">';
585             $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
586             $expected .= '</div>';
587             $this->assertContains($expected, $output);
588
589             $url = $url2;
590             $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
591             $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
592             $expected = '<div class="link-item">';
593             $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
594             $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
595             $expected .= '</div>';
596             $this->assertContains($expected, $output);
597
598             $url = $url3;
599             $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
600             $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
601             $expected = '<div class="link-item">';
602             $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
603             $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
604             $expected .= '</div>';
605             $this->assertContains($expected, $output);
606             break;
607
608           case 'rel':
609             $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
610             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($url1) . '</a></div>', $output);
611             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($url2) . '</a></div>', $output);
612             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($url3) . '</a></div>', $output);
613             break;
614
615           case 'target':
616             $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
617             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($url1) . '</a></div>', $output);
618             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($url2) . '</a></div>', $output);
619             $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($url3) . '</a></div>', $output);
620             break;
621         }
622       }
623     }
624   }
625
626   /**
627    * Test '#link_type' property exists on 'link_default' widget.
628    *
629    * Make sure the 'link_default' widget exposes a '#link_type' property on
630    * its element. Modules can use it to understand if a text form element is
631    * a link and also which LinkItemInterface::LINK_* is (EXTERNAL, GENERIC,
632    * INTERNAL).
633    */
634   public function testLinkTypeOnLinkWidget() {
635
636     $link_type = LinkItemInterface::LINK_EXTERNAL;
637     $field_name = mb_strtolower($this->randomMachineName());
638
639     // Create a field with settings to validate.
640     $this->fieldStorage = FieldStorageConfig::create([
641       'field_name' => $field_name,
642       'entity_type' => 'entity_test',
643       'type' => 'link',
644       'cardinality' => 1,
645     ]);
646     $this->fieldStorage->save();
647     FieldConfig::create([
648       'field_storage' => $this->fieldStorage,
649       'label' => 'Read more about this entity',
650       'bundle' => 'entity_test',
651       'settings' => [
652         'title' => DRUPAL_OPTIONAL,
653         'link_type' => $link_type,
654       ],
655     ])->save();
656
657     $this->container->get('entity.manager')
658       ->getStorage('entity_form_display')
659       ->load('entity_test.entity_test.default')
660       ->setComponent($field_name, [
661         'type' => 'link_default',
662       ])
663       ->save();
664
665     $form = \Drupal::service('entity.form_builder')->getForm(EntityTest::create());
666     $this->assertEqual($form[$field_name]['widget'][0]['uri']['#link_type'], $link_type);
667   }
668
669   /**
670    * Tests editing a link to a non-node entity.
671    */
672   public function testEditNonNodeEntityLink() {
673
674     $entity_type_manager = \Drupal::entityTypeManager();
675     $entity_test_storage = $entity_type_manager->getStorage('entity_test');
676
677     // Create a field with settings to validate.
678     $this->fieldStorage = FieldStorageConfig::create([
679       'field_name' => 'field_link',
680       'entity_type' => 'entity_test',
681       'type' => 'link',
682       'cardinality' => 1,
683     ]);
684     $this->fieldStorage->save();
685     FieldConfig::create([
686       'field_storage' => $this->fieldStorage,
687       'label' => 'Read more about this entity',
688       'bundle' => 'entity_test',
689       'settings' => [
690         'title' => DRUPAL_OPTIONAL,
691       ],
692     ])->save();
693
694     $entity_type_manager
695       ->getStorage('entity_form_display')
696       ->load('entity_test.entity_test.default')
697       ->setComponent('field_link', [
698         'type' => 'link_default',
699       ])
700       ->save();
701
702     // Create a node and a test entity to have a possibly valid reference for
703     // both. Create another test entity that references the first test entity.
704     $entity_test_link = $entity_test_storage->create(['name' => 'correct link target']);
705     $entity_test_link->save();
706
707     $node = $this->drupalCreateNode(['wrong link target']);
708
709     $correct_link = 'entity:entity_test/' . $entity_test_link->id();
710     $entity_test = $entity_test_storage->create([
711       'name' => 'correct link target',
712       'field_link' => $correct_link,
713     ]);
714     $entity_test->save();
715
716     // Edit the entity and save it, verify the correct link is kept and not
717     // changed to point to a node. Currently, widget does not support non-node
718     // autocomplete and therefore must show the link unaltered.
719     $this->drupalGet($entity_test->toUrl('edit-form'));
720     $this->assertSession()->fieldValueEquals('field_link[0][uri]', $correct_link);
721     $this->drupalPostForm(NULL, [], 'Save');
722
723     $entity_test_storage->resetCache();
724     $entity_test = $entity_test_storage->load($entity_test->id());
725
726     $this->assertEquals($correct_link, $entity_test->get('field_link')->uri);
727   }
728
729   /**
730    * Renders a test_entity and returns the output.
731    *
732    * @param int $id
733    *   The test_entity ID to render.
734    * @param string $view_mode
735    *   (optional) The view mode to use for rendering.
736    * @param bool $reset
737    *   (optional) Whether to reset the entity_test storage cache. Defaults to
738    *   TRUE to simplify testing.
739    *
740    * @return string
741    *   The rendered HTML output.
742    */
743   protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
744     if ($reset) {
745       $this->container->get('entity.manager')->getStorage('entity_test')->resetCache([$id]);
746     }
747     $entity = EntityTest::load($id);
748     $display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), $view_mode);
749     $content = $display->build($entity);
750     $output = \Drupal::service('renderer')->renderRoot($content);
751     $output = (string) $output;
752     $this->verbose($output);
753     return $output;
754   }
755
756 }