3 namespace Drupal\Tests\views\Functional\Handler;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\UrlHelper;
7 use Drupal\Core\Render\RenderContext;
9 use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
10 use Drupal\Tests\views\Functional\ViewTestBase;
11 use Drupal\views\Views;
14 * Tests fields from within a UI.
17 * @see \Drupal\views\Plugin\views\field\FieldPluginBase
19 class FieldWebTest extends ViewTestBase {
21 use AssertPageCacheContextsAndTagsTrait;
24 * Views used by this test.
28 public static $testViews = ['test_view', 'test_field_classes', 'test_field_output', 'test_click_sort'];
33 public static $modules = ['node'];
36 * Maps between the key in the expected result and the query result.
40 protected $columnMap = [
41 'views_test_data_name' => 'name',
44 protected function setUp($import_test_views = TRUE) {
45 parent::setUp($import_test_views);
47 $this->enableViewsTestModule();
53 protected function viewsData() {
54 $data = parent::viewsData();
55 $data['views_test_data']['job']['field']['id'] = 'test_field';
60 * Tests the click sorting functionality.
62 public function testClickSorting() {
63 $this->drupalGet('test_click_sort');
64 $this->assertResponse(200);
66 // Only the id and name should be click sortable, but not the name.
67 $this->assertLinkByHref(\Drupal::url('<none>', [], ['query' => ['order' => 'id', 'sort' => 'asc']]));
68 $this->assertLinkByHref(\Drupal::url('<none>', [], ['query' => ['order' => 'name', 'sort' => 'desc']]));
69 $this->assertNoLinkByHref(\Drupal::url('<none>', [], ['query' => ['order' => 'created']]));
71 // Check that the view returns the click sorting cache contexts.
72 $expected_contexts = [
73 'languages:language_interface',
77 $this->assertCacheContexts($expected_contexts);
79 // Clicking a click sort should change the order.
80 $this->clickLink(t('ID'));
81 $this->assertLinkByHref(\Drupal::url('<none>', [], ['query' => ['order' => 'id', 'sort' => 'desc']]));
82 // Check that the output has the expected order (asc).
83 $ids = $this->clickSortLoadIdsFromOutput();
84 $this->assertEqual($ids, range(1, 5));
86 $this->clickLink(t('ID Sort descending'));
87 // Check that the output has the expected order (desc).
88 $ids = $this->clickSortLoadIdsFromOutput();
89 $this->assertEqual($ids, range(5, 1, -1));
93 * Small helper function to get all ids in the output.
96 * A list of beatle ids.
98 protected function clickSortLoadIdsFromOutput() {
99 $fields = $this->xpath("//td[contains(@class, 'views-field-id')]");
101 foreach ($fields as $field) {
102 $ids[] = (int) $field->getText();
108 * Assertion helper which checks whether a string is part of another string.
110 * @param string $haystack
111 * The value to search in.
112 * @param string $needle
113 * The value to search for.
114 * @param string $message
115 * The message to display along with the assertion.
116 * @param string $group
117 * The type of assertion - examples are "Browser", "PHP".
119 * TRUE if the assertion succeeded, FALSE otherwise.
121 protected function assertSubString($haystack, $needle, $message = '', $group = 'Other') {
122 return $this->assertTrue(strpos($haystack, $needle) !== FALSE, $message, $group);
126 * Assertion helper which checks whether a string is not part of another string.
128 * @param string $haystack
129 * The value to search in.
130 * @param string $needle
131 * The value to search for.
132 * @param string $message
133 * The message to display along with the assertion.
134 * @param string $group
135 * The type of assertion - examples are "Browser", "PHP".
137 * TRUE if the assertion succeeded, FALSE otherwise.
139 protected function assertNotSubString($haystack, $needle, $message = '', $group = 'Other') {
140 return $this->assertTrue(strpos($haystack, $needle) === FALSE, $message, $group);
144 * Parse a content and return the html element.
146 * @param string $content
150 * An array containing simplexml objects.
152 protected function parseContent($content) {
153 $htmlDom = new \DOMDocument();
154 @$htmlDom->loadHTML('<?xml encoding="UTF-8">' . $content);
155 $elements = simplexml_import_dom($htmlDom);
161 * Performs an xpath search on a certain content.
163 * The search is relative to the root element of the $content variable.
165 * @param string $content
167 * @param string $xpath
168 * The xpath string to use in the search.
169 * @param array $arguments
170 * Some arguments for the xpath.
172 * @return array|false
173 * The return value of the xpath search. For details on the xpath string
174 * format and return values see the SimpleXML documentation,
175 * http://php.net/manual/function.simplexml-element-xpath.php.
177 protected function xpathContent($content, $xpath, array $arguments = []) {
178 if ($elements = $this->parseContent($content)) {
179 $xpath = $this->buildXPathQuery($xpath, $arguments);
180 $result = $elements->xpath($xpath);
181 // Some combinations of PHP / libxml versions return an empty array
182 // instead of the documented FALSE. Forcefully convert any falsish values
183 // to an empty array to allow foreach(...) constructions.
184 return $result ? $result : [];
192 * Tests rewriting the output to a link.
194 public function testAlterUrl() {
195 /** @var \Drupal\Core\Render\RendererInterface $renderer */
196 $renderer = \Drupal::service('renderer');
198 $view = Views::getView('test_view');
200 $view->initHandlers();
201 $this->executeView($view);
202 $row = $view->result[0];
203 $id_field = $view->field['id'];
205 // Setup the general settings required to build a link.
206 $id_field->options['alter']['make_link'] = TRUE;
207 $id_field->options['alter']['path'] = $path = $this->randomMachineName();
209 // Tests that the suffix/prefix appears on the output.
210 $id_field->options['alter']['prefix'] = $prefix = $this->randomMachineName();
211 $id_field->options['alter']['suffix'] = $suffix = $this->randomMachineName();
212 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
213 return $id_field->theme($row);
215 $this->assertSubString($output, $prefix);
216 $this->assertSubString($output, $suffix);
217 unset($id_field->options['alter']['prefix']);
218 unset($id_field->options['alter']['suffix']);
220 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
221 return $id_field->theme($row);
223 $this->assertSubString($output, $path, 'Make sure that the path is part of the output');
225 // Some generic test code adapted from the UrlTest class, which tests
226 // mostly the different options for the path.
227 foreach ([FALSE, TRUE] as $absolute) {
228 $alter = &$id_field->options['alter'];
229 $alter['path'] = 'node/123';
231 $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['absolute' => $absolute]);
232 $alter['absolute'] = $absolute;
233 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
234 return $id_field->theme($row);
236 $this->assertSubString($result, $expected_result);
238 $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['fragment' => 'foo', 'absolute' => $absolute]);
239 $alter['path'] = 'node/123#foo';
240 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
241 return $id_field->theme($row);
243 $this->assertSubString($result, $expected_result);
245 $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'absolute' => $absolute]);
246 $alter['path'] = 'node/123?foo';
247 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
248 return $id_field->theme($row);
250 $this->assertSubString($result, $expected_result);
252 $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => 'bar', 'bar' => 'baz'], 'absolute' => $absolute]);
253 $alter['path'] = 'node/123?foo=bar&bar=baz';
254 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
255 return $id_field->theme($row);
257 $this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
259 // @todo The route-based URL generator strips out NULL attributes.
260 // $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'fragment' => 'bar', 'absolute' => $absolute]);
261 $expected_result = Url::fromUserInput('/node/123', ['query' => ['foo' => NULL], 'fragment' => 'bar', 'absolute' => $absolute])->toString();
262 $alter['path'] = 'node/123?foo#bar';
263 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
264 return $id_field->theme($row);
266 $this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
268 $expected_result = \Drupal::url('<front>', [], ['absolute' => $absolute]);
269 $alter['path'] = '<front>';
270 $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
271 return $id_field->theme($row);
273 $this->assertSubString($result, $expected_result);
276 // Tests the replace spaces with dashes feature.
277 $id_field->options['alter']['replace_spaces'] = TRUE;
278 $id_field->options['alter']['path'] = $path = $this->randomMachineName() . ' ' . $this->randomMachineName();
279 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
280 return $id_field->theme($row);
282 $this->assertSubString($output, str_replace(' ', '-', $path));
283 $id_field->options['alter']['replace_spaces'] = FALSE;
284 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
285 return $id_field->theme($row);
287 // The url has a space in it, so to check we have to decode the url output.
288 $this->assertSubString(urldecode($output), $path);
290 // Tests the external flag.
291 // Switch on the external flag should output an external url as well.
292 $id_field->options['alter']['external'] = TRUE;
293 $id_field->options['alter']['path'] = $path = 'www.drupal.org';
294 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
295 return $id_field->theme($row);
297 $this->assertSubString($output, 'http://www.drupal.org');
299 // Setup a not external url, which shouldn't lead to an external url.
300 $id_field->options['alter']['external'] = FALSE;
301 $id_field->options['alter']['path'] = $path = 'www.drupal.org';
302 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
303 return $id_field->theme($row);
305 $this->assertNotSubString($output, 'http://www.drupal.org');
307 // Tests the transforming of the case setting.
308 $id_field->options['alter']['path'] = $path = $this->randomMachineName();
309 $id_field->options['alter']['path_case'] = 'none';
310 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
311 return $id_field->theme($row);
313 $this->assertSubString($output, $path);
315 // Switch to uppercase and lowercase.
316 $id_field->options['alter']['path_case'] = 'upper';
317 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
318 return $id_field->theme($row);
320 $this->assertSubString($output, strtoupper($path));
321 $id_field->options['alter']['path_case'] = 'lower';
322 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
323 return $id_field->theme($row);
325 $this->assertSubString($output, strtolower($path));
327 // Switch to ucfirst and ucwords.
328 $id_field->options['alter']['path_case'] = 'ucfirst';
329 $id_field->options['alter']['path'] = 'drupal has a great community';
330 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
331 return $id_field->theme($row);
333 $this->assertSubString($output, UrlHelper::encodePath('Drupal has a great community'));
335 $id_field->options['alter']['path_case'] = 'ucwords';
336 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
337 return $id_field->theme($row);
339 $this->assertSubString($output, UrlHelper::encodePath('Drupal Has A Great Community'));
340 unset($id_field->options['alter']['path_case']);
342 // Tests the link_class setting and see whether it actually exists in the
344 $id_field->options['alter']['link_class'] = $class = $this->randomMachineName();
345 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
346 return $id_field->theme($row);
348 $elements = $this->xpathContent($output, '//a[contains(@class, :class)]', [':class' => $class]);
349 $this->assertTrue($elements);
350 // @fixme link_class, alt, rel cannot be unset, which should be fixed.
351 $id_field->options['alter']['link_class'] = '';
353 // Tests the alt setting.
354 $id_field->options['alter']['alt'] = $rel = $this->randomMachineName();
355 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
356 return $id_field->theme($row);
358 $elements = $this->xpathContent($output, '//a[contains(@title, :alt)]', [':alt' => $rel]);
359 $this->assertTrue($elements);
360 $id_field->options['alter']['alt'] = '';
362 // Tests the rel setting.
363 $id_field->options['alter']['rel'] = $rel = $this->randomMachineName();
364 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
365 return $id_field->theme($row);
367 $elements = $this->xpathContent($output, '//a[contains(@rel, :rel)]', [':rel' => $rel]);
368 $this->assertTrue($elements);
369 $id_field->options['alter']['rel'] = '';
371 // Tests the target setting.
372 $id_field->options['alter']['target'] = $target = $this->randomMachineName();
373 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
374 return $id_field->theme($row);
376 $elements = $this->xpathContent($output, '//a[contains(@target, :target)]', [':target' => $target]);
377 $this->assertTrue($elements);
378 unset($id_field->options['alter']['target']);
382 * Tests the field/label/wrapper classes.
384 public function testFieldClasses() {
385 /** @var \Drupal\Core\Render\RendererInterface $renderer */
386 $renderer = $this->container->get('renderer');
387 $view = Views::getView('test_field_classes');
388 $view->initHandlers();
390 // Tests whether the default field classes are added.
391 $id_field = $view->field['id'];
393 $id_field->options['element_default_classes'] = FALSE;
394 // Setup some kind of label by default.
395 $id_field->options['label'] = $this->randomMachineName();
396 $output = $view->preview();
397 $output = $renderer->renderRoot($output);
398 $this->assertFalse($this->xpathContent($output, '//div[contains(@class, :class)]', [':class' => 'field-content']));
399 $this->assertFalse($this->xpathContent($output, '//div[contains(@class, :class)]', [':class' => 'field__label']));
401 $id_field->options['element_default_classes'] = TRUE;
402 $output = $view->preview();
403 $output = $renderer->renderRoot($output);
404 // Per default the label and the element of the field are spans.
405 $this->assertTrue($this->xpathContent($output, '//span[contains(@class, :class)]', [':class' => 'field-content']));
406 $this->assertTrue($this->xpathContent($output, '//span[contains(@class, :class)]', [':class' => 'views-label']));
407 $this->assertTrue($this->xpathContent($output, '//div[contains(@class, :class)]', [':class' => 'views-field']));
409 // Tests the element wrapper classes/element.
410 $random_class = $this->randomMachineName();
412 // Set some common wrapper element types and see whether they appear with and without a custom class set.
413 foreach (['h1', 'span', 'p', 'div'] as $element_type) {
414 $id_field->options['element_wrapper_type'] = $element_type;
416 // Set a custom wrapper element css class.
417 $id_field->options['element_wrapper_class'] = $random_class;
418 $output = $view->preview();
419 $output = $renderer->renderRoot($output);
420 $this->assertTrue($this->xpathContent($output, "//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
422 // Set no custom css class.
423 $id_field->options['element_wrapper_class'] = '';
424 $output = $view->preview();
425 $output = $renderer->renderRoot($output);
426 $this->assertFalse($this->xpathContent($output, "//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
427 $this->assertTrue($this->xpathContent($output, "//li[contains(@class, views-row)]/{$element_type}"));
430 // Tests the label class/element.
432 // Set some common label element types and see whether they appear with and without a custom class set.
433 foreach (['h1', 'span', 'p', 'div'] as $element_type) {
434 $id_field->options['element_label_type'] = $element_type;
436 // Set a custom label element css class.
437 $id_field->options['element_label_class'] = $random_class;
438 $output = $view->preview();
439 $output = $renderer->renderRoot($output);
440 $this->assertTrue($this->xpathContent($output, "//li[contains(@class, views-row)]//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
442 // Set no custom css class.
443 $id_field->options['element_label_class'] = '';
444 $output = $view->preview();
445 $output = $renderer->renderRoot($output);
446 $this->assertFalse($this->xpathContent($output, "//li[contains(@class, views-row)]//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
447 $this->assertTrue($this->xpathContent($output, "//li[contains(@class, views-row)]//{$element_type}"));
450 // Tests the element classes/element.
452 // Set some common element element types and see whether they appear with and without a custom class set.
453 foreach (['h1', 'span', 'p', 'div'] as $element_type) {
454 $id_field->options['element_type'] = $element_type;
456 // Set a custom label element css class.
457 $id_field->options['element_class'] = $random_class;
458 $output = $view->preview();
459 $output = $renderer->renderRoot($output);
460 $this->assertTrue($this->xpathContent($output, "//li[contains(@class, views-row)]//div[contains(@class, views-field)]//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
462 // Set no custom css class.
463 $id_field->options['element_class'] = '';
464 $output = $view->preview();
465 $output = $renderer->renderRoot($output);
466 $this->assertFalse($this->xpathContent($output, "//li[contains(@class, views-row)]//div[contains(@class, views-field)]//{$element_type}[contains(@class, :class)]", [':class' => $random_class]));
467 $this->assertTrue($this->xpathContent($output, "//li[contains(@class, views-row)]//div[contains(@class, views-field)]//{$element_type}"));
470 // Tests the available html elements.
471 $element_types = $id_field->getElements();
472 $expected_elements = [
489 $this->assertEqual(array_keys($element_types), $expected_elements);
493 * Tests trimming/read-more/ellipses.
495 public function testTextRendering() {
496 /** @var \Drupal\Core\Render\RendererInterface $renderer */
497 $renderer = \Drupal::service('renderer');
499 $view = Views::getView('test_field_output');
500 $view->initHandlers();
501 $name_field = $view->field['name'];
503 // Tests stripping of html elements.
504 $this->executeView($view);
505 $random_text = $this->randomMachineName();
506 $name_field->options['alter']['alter_text'] = TRUE;
507 $name_field->options['alter']['text'] = $html_text = '<div class="views-test">' . $random_text . '</div>';
508 $row = $view->result[0];
510 $name_field->options['alter']['strip_tags'] = TRUE;
511 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
512 return $name_field->advancedRender($row);
514 $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled.');
515 $this->assertNotSubString($output, $html_text, 'Find no text with the html if stripping of views field output is enabled.');
517 // Tests preserving of html tags.
518 $name_field->options['alter']['preserve_tags'] = '<div>';
519 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
520 return $name_field->advancedRender($row);
522 $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled but a div is allowed.');
523 $this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is enabled but a div is allowed.');
525 $name_field->options['alter']['strip_tags'] = FALSE;
526 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
527 return $name_field->advancedRender($row);
529 $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is disabled.');
530 $this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is disabled.');
532 // Tests for removing whitespace and the beginning and the end.
533 $name_field->options['alter']['alter_text'] = FALSE;
534 $views_test_data_name = $row->views_test_data_name;
535 $row->views_test_data_name = ' ' . $views_test_data_name . ' ';
536 $name_field->options['alter']['trim_whitespace'] = TRUE;
537 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
538 return $name_field->advancedRender($row);
541 $this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is enabled.');
542 $this->assertNotSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found if trimming is enabled.');
544 $name_field->options['alter']['trim_whitespace'] = FALSE;
545 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
546 return $name_field->advancedRender($row);
548 $this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is disabled.');
549 $this->assertSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found if trimming is disabled.');
551 // Tests for trimming to a maximum length.
552 $name_field->options['alter']['trim'] = TRUE;
553 $name_field->options['alter']['word_boundary'] = FALSE;
555 // Tests for simple trimming by string length.
556 $row->views_test_data_name = $this->randomMachineName(8);
557 $name_field->options['alter']['max_length'] = 5;
558 $trimmed_name = mb_substr($row->views_test_data_name, 0, 5);
560 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
561 return $name_field->advancedRender($row);
563 $this->assertSubString($output, $trimmed_name, format_string('Make sure the trimmed output (@trimmed) appears in the rendered output (@output).', ['@trimmed' => $trimmed_name, '@output' => $output]));
564 $this->assertNotSubString($output, $row->views_test_data_name, format_string("Make sure the untrimmed value (@untrimmed) shouldn't appear in the rendered output (@output).", ['@untrimmed' => $row->views_test_data_name, '@output' => $output]));
566 $name_field->options['alter']['max_length'] = 9;
567 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
568 return $name_field->advancedRender($row);
570 $this->assertSubString($output, $trimmed_name, format_string('Make sure the untrimmed (@untrimmed) output appears in the rendered output (@output).', ['@trimmed' => $trimmed_name, '@output' => $output]));
572 // Take word_boundary into account for the tests.
573 $name_field->options['alter']['max_length'] = 5;
574 $name_field->options['alter']['word_boundary'] = TRUE;
575 $random_text_2 = $this->randomMachineName(2);
576 $random_text_4 = $this->randomMachineName(4);
577 $random_text_8 = $this->randomMachineName(8);
579 // Create one string which doesn't fit at all into the limit.
581 'value' => $random_text_8,
582 'trimmed_value' => '',
585 // Create one string with two words which doesn't fit both into the limit.
587 'value' => $random_text_8 . ' ' . $random_text_8,
588 'trimmed_value' => '',
591 // Create one string which contains of two words, of which only the first
592 // fits into the limit.
594 'value' => $random_text_4 . ' ' . $random_text_8,
595 'trimmed_value' => $random_text_4,
598 // Create one string which contains of two words, of which both fits into
601 'value' => $random_text_2 . ' ' . $random_text_2,
602 'trimmed_value' => $random_text_2 . ' ' . $random_text_2,
607 foreach ($tuples as $tuple) {
608 $row->views_test_data_name = $tuple['value'];
609 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
610 return $name_field->advancedRender($row);
613 if ($tuple['trimmed']) {
614 $this->assertNotSubString($output, $tuple['value'], format_string('The untrimmed value (@untrimmed) should not appear in the trimmed output (@output).', ['@untrimmed' => $tuple['value'], '@output' => $output]));
616 if (!empty($tuple['trimmed_value'])) {
617 $this->assertSubString($output, $tuple['trimmed_value'], format_string('The trimmed value (@trimmed) should appear in the trimmed output (@output).', ['@trimmed' => $tuple['trimmed_value'], '@output' => $output]));
621 // Tests for displaying a readmore link when the output got trimmed.
622 $row->views_test_data_name = $this->randomMachineName(8);
623 $name_field->options['alter']['max_length'] = 5;
624 $name_field->options['alter']['more_link'] = TRUE;
625 $name_field->options['alter']['more_link_text'] = $more_text = $this->randomMachineName();
626 $name_field->options['alter']['more_link_path'] = $more_path = $this->randomMachineName();
628 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
629 return $name_field->advancedRender($row);
631 $this->assertSubString($output, $more_text, 'Make sure a read more text is displayed if the output got trimmed');
632 $this->assertTrue($this->xpathContent($output, '//a[contains(@href, :path)]', [':path' => $more_path]), 'Make sure the read more link points to the right destination.');
634 $name_field->options['alter']['more_link'] = FALSE;
635 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
636 return $name_field->advancedRender($row);
638 $this->assertNotSubString($output, $more_text, 'Make sure no read more text appears.');
639 $this->assertFalse($this->xpathContent($output, '//a[contains(@href, :path)]', [':path' => $more_path]), 'Make sure no read more link appears.');
641 // Check for the ellipses.
642 $row->views_test_data_name = $this->randomMachineName(8);
643 $name_field->options['alter']['max_length'] = 5;
644 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
645 return $name_field->advancedRender($row);
647 $this->assertSubString($output, '…', 'An ellipsis should appear if the output is trimmed');
648 $name_field->options['alter']['max_length'] = 10;
649 $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
650 return $name_field->advancedRender($row);
652 $this->assertNotSubString($output, '…', 'No ellipsis should appear if the output is not trimmed');