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