LinkFieldTest.php 33.1 KB
Newer Older
1 2
<?php

3
namespace Drupal\Tests\link\Functional;
4

5
use Drupal\Component\Utility\Html;
6
use Drupal\Component\Utility\Unicode;
7
use Drupal\Core\Entity\Entity\EntityViewDisplay;
8
use Drupal\Core\Link;
9
use Drupal\Core\Url;
10
use Drupal\entity_test\Entity\EntityTest;
11
use Drupal\field\Entity\FieldConfig;
12
use Drupal\link\LinkItemInterface;
13
use Drupal\node\NodeInterface;
14
use Drupal\Tests\BrowserTestBase;
15
use Drupal\field\Entity\FieldStorageConfig;
16
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
17 18 19

/**
 * Tests link field widgets and formatters.
20 21
 *
 * @group link
22
 */
23
class LinkFieldTest extends BrowserTestBase {
24

25 26
  use PathAliasTestTrait;

27 28 29 30 31
  /**
   * Modules to enable.
   *
   * @var array
   */
32
  public static $modules = [
33 34 35 36 37
    'entity_test',
    'link',
    'node',
    'link_test_base_field',
  ];
38

39 40 41
  /**
   * A field to use in this test class.
   *
42
   * @var \Drupal\field\Entity\FieldStorageConfig
43
   */
44
  protected $fieldStorage;
45 46 47 48

  /**
   * The instance used in this test class.
   *
49
   * @var \Drupal\field\Entity\FieldConfig
50
   */
51
  protected $field;
52

53
  protected function setUp() {
54 55
    parent::setUp();

56
    $this->drupalLogin($this->drupalCreateUser([
57 58
      'view test entity',
      'administer entity_test content',
59
      'link to any page',
60
    ]));
61 62 63 64 65
  }

  /**
   * Tests link field URL validation.
   */
66
  public function testURLValidation() {
67
    $field_name = mb_strtolower($this->randomMachineName());
68
    // Create a field with settings to validate.
69
    $this->fieldStorage = FieldStorageConfig::create([
70
      'field_name' => $field_name,
71
      'entity_type' => 'entity_test',
72
      'type' => 'link',
73
    ]);
74
    $this->fieldStorage->save();
75
    $this->field = FieldConfig::create([
76
      'field_storage' => $this->fieldStorage,
77
      'bundle' => 'entity_test',
78
      'settings' => [
79
        'title' => DRUPAL_DISABLED,
80
        'link_type' => LinkItemInterface::LINK_GENERIC,
81
      ],
82
    ]);
83
    $this->field->save();
84 85 86
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $display_repository->getFormDisplay('entity_test', 'entity_test')
87
      ->setComponent($field_name, [
88
        'type' => 'link_default',
89
        'settings' => [
90
          'placeholder_url' => 'http://example.com',
91 92
        ],
      ])
93
      ->save();
94
    $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
95
      ->setComponent($field_name, [
96
        'type' => 'link',
97
      ])
98 99
      ->save();

100
    // Display creation form.
101
    $this->drupalGet('entity_test/add');
102
    $this->assertFieldByName("{$field_name}[0][uri]", '', 'Link URL field is displayed');
103
    $this->assertRaw('placeholder="http://example.com"');
104

105
    // Create a path alias.
106
    $this->createPathAlias('/admin', '/a/path/alias');
107 108 109 110

    // Create a node to test the link widget.
    $node = $this->drupalCreateNode();

111
    $restricted_node = $this->drupalCreateNode(['status' => NodeInterface::NOT_PUBLISHED]);
112

113 114
    // Define some valid URLs (keys are the entered values, values are the
    // strings displayed to the user).
115
    $valid_external_entries = [
116
      'http://www.example.com/' => 'http://www.example.com/',
117 118 119 120
      // Strings within parenthesis without leading space char.
      'http://www.example.com/strings_(string_within_parenthesis)' => 'http://www.example.com/strings_(string_within_parenthesis)',
      // Numbers within parenthesis without leading space char.
      'http://www.example.com/numbers_(9999)' => 'http://www.example.com/numbers_(9999)',
121 122
    ];
    $valid_internal_entries = [
123 124 125 126 127 128 129 130
      '/entity_test/add' => '/entity_test/add',
      '/a/path/alias' => '/a/path/alias',

      // Front page, with query string and fragment.
      '/' => '&lt;front&gt;',
      '/?example=llama' => '&lt;front&gt;?example=llama',
      '/#example' => '&lt;front&gt;#example',

131 132 133 134
      // Trailing spaces should be ignored.
      '/ ' => '&lt;front&gt;',
      '/path with spaces ' => '/path with spaces',

135 136 137 138
      // @todo '<front>' is valid input for BC reasons, may be removed by
      //   https://www.drupal.org/node/2421941
      '<front>' => '&lt;front&gt;',
      '<front>#example' => '&lt;front&gt;#example',
139
      '<front>?example=llama' => '&lt;front&gt;?example=llama',
140

141 142 143 144 145
      // Text-only links.
      '<nolink>' => '&lt;nolink&gt;',
      'route:<nolink>' => '&lt;nolink&gt;',
      '<none>' => '&lt;none&gt;',

146 147 148 149 150 151 152 153 154
      // Query string and fragment.
      '?example=llama' => '?example=llama',
      '#example' => '#example',

      // Entity reference autocomplete value.
      $node->label() . ' (1)' => $node->label() . ' (1)',
      // Entity URI displayed as ER autocomplete value when displayed in a form.
      'entity:node/1' => $node->label() . ' (1)',
      // URI for an entity that exists, but is not accessible by the user.
155
      'entity:node/' . $restricted_node->id() => '- Restricted access - (' . $restricted_node->id() . ')',
156 157
      // URI for an entity that doesn't exist, but with a valid ID.
      'entity:user/999999' => 'entity:user/999999',
158
    ];
159

160
    // Define some invalid URLs.
161
    $validation_error_1 = "The path '@link_path' is invalid.";
162
    $validation_error_2 = 'Manually entered paths should start with one of the following characters: / ? #';
163
    $validation_error_3 = "The path '@link_path' is inaccessible.";
164
    $invalid_external_entries = [
165
      // Invalid protocol
166
      'invalid://not-a-valid-protocol' => $validation_error_1,
167
      // Missing host name
168
      'http://' => $validation_error_1,
169 170
    ];
    $invalid_internal_entries = [
171 172
      'no-leading-slash' => $validation_error_2,
      'entity:non_existing_entity_type/yar' => $validation_error_1,
173 174
      // URI for an entity that doesn't exist, with an invalid ID.
      'entity:user/invalid-parameter' => $validation_error_1,
175
    ];
176 177 178 179 180 181

    // Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
    $this->assertValidEntries($field_name, $valid_external_entries + $valid_internal_entries);
    $this->assertInvalidEntries($field_name, $invalid_external_entries + $invalid_internal_entries);

    // Test external URLs for 'link_type' = LinkItemInterface::LINK_EXTERNAL.
182
    $this->field->setSetting('link_type', LinkItemInterface::LINK_EXTERNAL);
183
    $this->field->save();
184 185 186 187
    $this->assertValidEntries($field_name, $valid_external_entries);
    $this->assertInvalidEntries($field_name, $valid_internal_entries + $invalid_external_entries);

    // Test external URLs for 'link_type' = LinkItemInterface::LINK_INTERNAL.
188
    $this->field->setSetting('link_type', LinkItemInterface::LINK_INTERNAL);
189
    $this->field->save();
190 191
    $this->assertValidEntries($field_name, $valid_internal_entries);
    $this->assertInvalidEntries($field_name, $valid_external_entries + $invalid_internal_entries);
192 193 194 195 196 197 198 199

    // Ensure that users with 'link to any page', don't apply access checking.
    $this->drupalLogin($this->drupalCreateUser([
      'view test entity',
      'administer entity_test content',
    ]));
    $this->assertValidEntries($field_name, ['/entity_test/add' => '/entity_test/add']);
    $this->assertInValidEntries($field_name, ['/admin' => $validation_error_3]);
200 201 202 203 204 205 206 207 208 209 210
  }

  /**
   * Asserts that valid URLs can be submitted.
   *
   * @param string $field_name
   *   The field name.
   * @param array $valid_entries
   *   An array of valid URL entries.
   */
  protected function assertValidEntries($field_name, array $valid_entries) {
211
    foreach ($valid_entries as $uri => $string) {
212
      $edit = [
213
        "{$field_name}[0][uri]" => $uri,
214
      ];
215
      $this->drupalPostForm('entity_test/add', $edit, t('Save'));
216
      preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
217
      $id = $match[1];
218
      $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
219
      $this->assertRaw('"' . $string . '"');
220 221 222 223 224 225 226 227 228 229 230 231
    }
  }

  /**
   * Asserts that invalid URLs cannot be submitted.
   *
   * @param string $field_name
   *   The field name.
   * @param array $invalid_entries
   *   An array of invalid URL entries.
   */
  protected function assertInvalidEntries($field_name, array $invalid_entries) {
232
    foreach ($invalid_entries as $invalid_value => $error_message) {
233
      $edit = [
234
        "{$field_name}[0][uri]" => $invalid_value,
235
      ];
236
      $this->drupalPostForm('entity_test/add', $edit, t('Save'));
237
      $this->assertText(t($error_message, ['@link_path' => $invalid_value]));
238 239 240 241
    }
  }

  /**
242
   * Tests the link title settings of a link field.
243
   */
244
  public function testLinkTitle() {
245
    $field_name = mb_strtolower($this->randomMachineName());
246
    // Create a field with settings to validate.
247
    $this->fieldStorage = FieldStorageConfig::create([
248
      'field_name' => $field_name,
249
      'entity_type' => 'entity_test',
250
      'type' => 'link',
251
    ]);
252
    $this->fieldStorage->save();
253
    $this->field = FieldConfig::create([
254
      'field_storage' => $this->fieldStorage,
255
      'bundle' => 'entity_test',
256
      'label' => 'Read more about this entity',
257
      'settings' => [
258
        'title' => DRUPAL_OPTIONAL,
259
        'link_type' => LinkItemInterface::LINK_GENERIC,
260
      ],
261
    ]);
262
    $this->field->save();
263 264 265
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $display_repository->getFormDisplay('entity_test', 'entity_test')
266
      ->setComponent($field_name, [
267
        'type' => 'link_default',
268
        'settings' => [
269
          'placeholder_url' => 'http://example.com',
270
          'placeholder_title' => 'Enter the text for this link',
271 272
        ],
      ])
273
      ->save();
274
    $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
275
      ->setComponent($field_name, [
276 277
        'type' => 'link',
        'label' => 'hidden',
278
      ])
279 280
      ->save();

281
    // Verify that the link text field works according to the field setting.
282
    foreach ([DRUPAL_DISABLED, DRUPAL_REQUIRED, DRUPAL_OPTIONAL] as $title_setting) {
283
      // Update the link title field setting.
284
      $this->field->setSetting('title', $title_setting);
285
      $this->field->save();
286 287

      // Display creation form.
288
      $this->drupalGet('entity_test/add');
289 290
      // Assert label is shown.
      $this->assertText('Read more about this entity');
291
      $this->assertFieldByName("{$field_name}[0][uri]", '', 'URL field found.');
292
      $this->assertRaw('placeholder="http://example.com"');
293 294

      if ($title_setting === DRUPAL_DISABLED) {
295
        $this->assertNoFieldByName("{$field_name}[0][title]", '', 'Link text field not found.');
296
        $this->assertNoRaw('placeholder="Enter the text for this link"');
297 298
      }
      else {
299
        $this->assertRaw('placeholder="Enter the text for this link"');
300

301
        $this->assertFieldByName("{$field_name}[0][title]", '', 'Link text field found.');
302 303 304 305 306 307 308 309
        if ($title_setting === DRUPAL_OPTIONAL) {
          // Verify that the URL is required, if the link text is non-empty.
          $edit = [
            "{$field_name}[0][title]" => 'Example',
          ];
          $this->drupalPostForm(NULL, $edit, t('Save'));
          $this->assertText(t('The URL field is required when the @title field is specified.', ['@title' => t('Link text')]));
        }
310
        if ($title_setting === DRUPAL_REQUIRED) {
311
          // Verify that the link text is required, if the URL is non-empty.
312
          $edit = [
313
            "{$field_name}[0][uri]" => 'http://www.example.com',
314
          ];
315
          $this->drupalPostForm(NULL, $edit, t('Save'));
316
          $this->assertText(t('@title field is required if there is @uri input.', ['@title' => t('Link text'), '@uri' => t('URL')]));
317

318
          // Verify that the link text is not required, if the URL is empty.
319
          $edit = [
320
            "{$field_name}[0][uri]" => '',
321
          ];
322
          $this->drupalPostForm(NULL, $edit, t('Save'));
323
          $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
324

325
          // Verify that a URL and link text meets requirements.
326
          $this->drupalGet('entity_test/add');
327
          $edit = [
328
            "{$field_name}[0][uri]" => 'http://www.example.com',
329
            "{$field_name}[0][title]" => 'Example',
330
          ];
331
          $this->drupalPostForm(NULL, $edit, t('Save'));
332
          $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
333 334 335 336
        }
      }
    }

337
    // Verify that a link without link text is rendered using the URL as text.
338
    $value = 'http://www.example.com/';
339
    $edit = [
340
      "{$field_name}[0][uri]" => $value,
341
      "{$field_name}[0][title]" => '',
342
    ];
343
    $this->drupalPostForm(NULL, $edit, t('Save'));
344
    preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
345
    $id = $match[1];
346
    $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
347

348
    $output = $this->renderTestEntity($id);
349
    $expected_link = (string) Link::fromTextAndUrl($value, Url::fromUri($value))->toString();
350
    $this->assertContains($expected_link, $output);
351

352
    // Verify that a link with text is rendered using the link text.
353
    $title = $this->randomMachineName();
354
    $edit = [
355
      "{$field_name}[0][title]" => $title,
356
    ];
357
    $this->drupalPostForm("entity_test/manage/$id/edit", $edit, t('Save'));
358
    $this->assertText(t('entity_test @id has been updated.', ['@id' => $id]));
359

360
    $output = $this->renderTestEntity($id);
361
    $expected_link = (string) Link::fromTextAndUrl($title, Url::fromUri($value))->toString();
362
    $this->assertContains($expected_link, $output);
363 364 365 366 367
  }

  /**
   * Tests the default 'link' formatter.
   */
368
  public function testLinkFormatter() {
369
    $field_name = mb_strtolower($this->randomMachineName());
370
    // Create a field with settings to validate.
371
    $this->fieldStorage = FieldStorageConfig::create([
372
      'field_name' => $field_name,
373
      'entity_type' => 'entity_test',
374
      'type' => 'link',
375
      'cardinality' => 3,
376
    ]);
377
    $this->fieldStorage->save();
378
    FieldConfig::create([
379
      'field_storage' => $this->fieldStorage,
380
      'label' => 'Read more about this entity',
381
      'bundle' => 'entity_test',
382
      'settings' => [
383
        'title' => DRUPAL_OPTIONAL,
384
        'link_type' => LinkItemInterface::LINK_GENERIC,
385
      ],
386
    ])->save();
387 388 389
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $display_repository->getFormDisplay('entity_test', 'entity_test')
390
      ->setComponent($field_name, [
391
        'type' => 'link_default',
392
      ])
393
      ->save();
394
    $display_options = [
395 396
      'type' => 'link',
      'label' => 'hidden',
397
    ];
398
    $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
399
      ->setComponent($field_name, $display_options)
400 401
      ->save();

402
    // Create an entity with three link field values:
403
    // - The first field item uses a URL only.
404
    // - The second field item uses a URL and link text.
405
    // - The third field item uses a fragment-only URL with text.
406 407
    // For consistency in assertion code below, the URL is assigned to the title
    // variable for the first field.
408
    $this->drupalGet('entity_test/add');
409 410
    $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
    $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
411
    $url3 = '#net';
412 413 414
    $title1 = $url1;
    // Intentionally contains an ampersand that needs sanitization on output.
    $title2 = 'A very long & strange example title that could break the nice layout of the site';
415
    $title3 = 'Fragment only';
416
    $edit = [
417
      "{$field_name}[0][uri]" => $url1,
418
      // Note that $title1 is not submitted.
419
      "{$field_name}[0][title]" => '',
420
      "{$field_name}[1][uri]" => $url2,
421
      "{$field_name}[1][title]" => $title2,
422 423
      "{$field_name}[2][uri]" => $url3,
      "{$field_name}[2][title]" => $title3,
424
    ];
425 426
    // Assert label is shown.
    $this->assertText('Read more about this entity');
427
    $this->drupalPostForm(NULL, $edit, t('Save'));
428
    preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
429
    $id = $match[1];
430
    $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
431 432 433 434

    // Verify that the link is output according to the formatter settings.
    // Not using generatePermutations(), since that leads to 32 cases, which
    // would not test actual link field formatter functionality but rather
435 436
    // the link generator and options/attributes. Only 'url_plain' has a
    // dependency on 'url_only', so we have a total of ~10 cases.
437 438 439 440 441 442 443 444 445 446 447
    $options = [
      'trim_length' => [NULL, 6],
      'rel' => [NULL, 'nofollow'],
      'target' => [NULL, '_blank'],
      'url_only' => [
        ['url_only' => FALSE],
        ['url_only' => FALSE, 'url_plain' => TRUE],
        ['url_only' => TRUE],
        ['url_only' => TRUE, 'url_plain' => TRUE],
      ],
    ];
448 449 450 451
    foreach ($options as $setting => $values) {
      foreach ($values as $new_value) {
        // Update the field formatter settings.
        if (!is_array($new_value)) {
452
          $display_options['settings'] = [$setting => $new_value];
453 454
        }
        else {
455
          $display_options['settings'] = $new_value;
456
        }
457
        $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
458
          ->setComponent($field_name, $display_options)
459
          ->save();
460

461
        $output = $this->renderTestEntity($id);
462 463 464
        switch ($setting) {
          case 'trim_length':
            $url = $url1;
465
            $title = isset($new_value) ? Unicode::truncate($title1, $new_value, FALSE, TRUE) : $title1;
466
            $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
467 468

            $url = $url2;
469
            $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
470
            $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
471 472 473

            $url = $url3;
            $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
474
            $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
475 476 477 478
            break;

          case 'rel':
            $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
479 480 481
            $this->assertContains('<a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($title1) . '</a>', $output);
            $this->assertContains('<a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($title2) . '</a>', $output);
            $this->assertContains('<a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($title3) . '</a>', $output);
482 483 484 485
            break;

          case 'target':
            $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
486 487 488
            $this->assertContains('<a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($title1) . '</a>', $output);
            $this->assertContains('<a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($title2) . '</a>', $output);
            $this->assertContains('<a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($title3) . '</a>', $output);
489 490 491 492 493
            break;

          case 'url_only':
            // In this case, $new_value is an array.
            if (!$new_value['url_only']) {
494 495 496
              $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($title1) . '</a>', $output);
              $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($title2) . '</a>', $output);
              $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($title3) . '</a>', $output);
497 498 499
            }
            else {
              if (empty($new_value['url_plain'])) {
500 501 502
                $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
                $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
                $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
503 504
              }
              else {
505 506 507 508 509 510
                $this->assertNotContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
                $this->assertNotContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
                $this->assertNotContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
                $this->assertContains(Html::escape($url1), $output);
                $this->assertContains(Html::escape($url2), $output);
                $this->assertContains(Html::escape($url3), $output);
511 512 513 514 515 516 517 518 519 520 521 522 523 524
              }
            }
            break;
        }
      }
    }
  }

  /**
   * Tests the 'link_separate' formatter.
   *
   * This test is mostly the same as testLinkFormatter(), but they cannot be
   * merged, since they involve different configuration and output.
   */
525
  public function testLinkSeparateFormatter() {
526
    $field_name = mb_strtolower($this->randomMachineName());
527
    // Create a field with settings to validate.
528
    $this->fieldStorage = FieldStorageConfig::create([
529
      'field_name' => $field_name,
530
      'entity_type' => 'entity_test',
531
      'type' => 'link',
532
      'cardinality' => 3,
533
    ]);
534
    $this->fieldStorage->save();
535
    FieldConfig::create([
536
      'field_storage' => $this->fieldStorage,
537
      'bundle' => 'entity_test',
538
      'settings' => [
539
        'title' => DRUPAL_OPTIONAL,
540
        'link_type' => LinkItemInterface::LINK_GENERIC,
541
      ],
542
    ])->save();
543
    $display_options = [
544 545
      'type' => 'link_separate',
      'label' => 'hidden',
546
    ];
547 548 549
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $display_repository->getFormDisplay('entity_test', 'entity_test')
550
      ->setComponent($field_name, [
551
        'type' => 'link_default',
552
      ])
553
      ->save();
554
    $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
555
      ->setComponent($field_name, $display_options)
556 557
      ->save();

558
    // Create an entity with three link field values:
559
    // - The first field item uses a URL only.
560
    // - The second field item uses a URL and link text.
561
    // - The third field item uses a fragment-only URL with text.
562 563
    // For consistency in assertion code below, the URL is assigned to the title
    // variable for the first field.
564
    $this->drupalGet('entity_test/add');
565 566
    $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
    $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
567
    $url3 = '#net';
568 569
    // Intentionally contains an ampersand that needs sanitization on output.
    $title2 = 'A very long & strange example title that could break the nice layout of the site';
570
    $title3 = 'Fragment only';
571
    $edit = [
572 573
      "{$field_name}[0][uri]" => $url1,
      "{$field_name}[1][uri]" => $url2,
574
      "{$field_name}[1][title]" => $title2,
575 576
      "{$field_name}[2][uri]" => $url3,
      "{$field_name}[2][title]" => $title3,
577
    ];
578
    $this->drupalPostForm(NULL, $edit, t('Save'));
579
    preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
580
    $id = $match[1];
581
    $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
582 583

    // Verify that the link is output according to the formatter settings.
584 585 586 587 588
    $options = [
      'trim_length' => [NULL, 6],
      'rel' => [NULL, 'nofollow'],
      'target' => [NULL, '_blank'],
    ];
589 590 591
    foreach ($options as $setting => $values) {
      foreach ($values as $new_value) {
        // Update the field formatter settings.
592
        $display_options['settings'] = [$setting => $new_value];
593
        $display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
594
          ->setComponent($field_name, $display_options)
595
          ->save();
596

597
        $output = $this->renderTestEntity($id);
598 599 600
        switch ($setting) {
          case 'trim_length':
            $url = $url1;
601
            $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
602
            $expected = '<div class="link-item">';
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 607

            $url = $url2;
608 609
            $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
            $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
610
            $expected = '<div class="link-item">';
611 612
            $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
            $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
613
            $expected .= '</div>';
614
            $this->assertContains($expected, $output);
615 616 617 618 619 620 621 622

            $url = $url3;
            $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
            $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
            $expected = '<div class="link-item">';
            $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
            $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
            $expected .= '</div>';
623
            $this->assertContains($expected, $output);
624 625 626 627
            break;

          case 'rel':
            $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
628 629 630
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($url1) . '</a></div>', $output);
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($url2) . '</a></div>', $output);
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($url3) . '</a></div>', $output);
631 632 633 634
            break;

          case 'target':
            $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
635 636 637
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($url1) . '</a></div>', $output);
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($url2) . '</a></div>', $output);
            $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($url3) . '</a></div>', $output);
638 639 640 641 642 643
            break;
        }
      }
    }
  }

644 645 646 647 648 649 650 651 652 653 654
  /**
   * Test '#link_type' property exists on 'link_default' widget.
   *
   * Make sure the 'link_default' widget exposes a '#link_type' property on
   * its element. Modules can use it to understand if a text form element is
   * a link and also which LinkItemInterface::LINK_* is (EXTERNAL, GENERIC,
   * INTERNAL).
   */
  public function testLinkTypeOnLinkWidget() {

    $link_type = LinkItemInterface::LINK_EXTERNAL;
655
    $field_name = mb_strtolower($this->randomMachineName());
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674

    // Create a field with settings to validate.
    $this->fieldStorage = FieldStorageConfig::create([
      'field_name' => $field_name,
      'entity_type' => 'entity_test',
      'type' => 'link',
      'cardinality' => 1,
    ]);
    $this->fieldStorage->save();
    FieldConfig::create([
      'field_storage' => $this->fieldStorage,
      'label' => 'Read more about this entity',
      'bundle' => 'entity_test',
      'settings' => [
        'title' => DRUPAL_OPTIONAL,
        'link_type' => $link_type,
      ],
    ])->save();

675
    $this->container->get('entity_type.manager')
676 677 678 679 680 681 682 683 684 685 686
      ->getStorage('entity_form_display')
      ->load('entity_test.entity_test.default')
      ->setComponent($field_name, [
        'type' => 'link_default',
      ])
      ->save();

    $form = \Drupal::service('entity.form_builder')->getForm(EntityTest::create());
    $this->assertEqual($form[$field_name]['widget'][0]['uri']['#link_type'], $link_type);
  }

687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716