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