Version 1
[yaffs-website] / web / core / modules / views / tests / src / Kernel / Handler / FieldKernelTest.php
1 <?php
2
3 namespace Drupal\Tests\views\Kernel\Handler;
4
5 use Drupal\Core\Render\RenderContext;
6 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
7 use Drupal\views\Plugin\views\field\FieldPluginBase;
8 use Drupal\views\Views;
9
10 /**
11  * Tests the generic field handler.
12  *
13  * @group views
14  * @see \Drupal\views\Plugin\views\field\FieldPluginBase
15  */
16 class FieldKernelTest extends ViewsKernelTestBase {
17
18   public static $modules = ['user'];
19
20   /**
21    * Views used by this test.
22    *
23    * @var array
24    */
25   public static $testViews = ['test_view', 'test_field_tokens', 'test_field_argument_tokens', 'test_field_output'];
26
27   /**
28    * Map column names.
29    *
30    * @var array
31    */
32   protected $columnMap = [
33     'views_test_data_name' => 'name',
34   ];
35
36   /**
37    * {@inheritdoc}
38    */
39   protected function viewsData() {
40     $data = parent::viewsData();
41     $data['views_test_data']['job']['field']['id'] = 'test_field';
42     $data['views_test_data']['job']['field']['click sortable'] = FALSE;
43     $data['views_test_data']['id']['field']['click sortable'] = TRUE;
44     return $data;
45   }
46
47   /**
48    * Tests that the render function is called.
49    */
50   public function testRender() {
51     /** @var \Drupal\Core\Render\RendererInterface $renderer */
52     $renderer = \Drupal::service('renderer');
53
54     $view = Views::getView('test_field_tokens');
55     $this->executeView($view);
56
57     $random_text = $this->randomMachineName();
58     $view->field['job']->setTestValue($random_text);
59     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
60       return $view->field['job']->theme($view->result[0]);
61     });
62     $this->assertEqual($output, $random_text, 'Make sure the render method rendered the manual set value.');
63   }
64
65   /**
66    * Tests all things related to the query.
67    */
68   public function testQuery() {
69     // Tests adding additional fields to the query.
70     $view = Views::getView('test_view');
71     $view->initHandlers();
72
73     $id_field = $view->field['id'];
74     $id_field->additional_fields['job'] = 'job';
75     // Choose also a field alias key which doesn't match to the table field.
76     $id_field->additional_fields['created_test'] = ['table' => 'views_test_data', 'field' => 'created'];
77     $view->build();
78
79     // Make sure the field aliases have the expected value.
80     $this->assertEqual($id_field->aliases['job'], 'views_test_data_job');
81     $this->assertEqual($id_field->aliases['created_test'], 'views_test_data_created');
82
83     $this->executeView($view);
84     // Tests the getValue method with and without a field aliases.
85     foreach ($this->dataSet() as $key => $row) {
86       $id = $key + 1;
87       $result = $view->result[$key];
88       $this->assertEqual($id_field->getValue($result), $id);
89       $this->assertEqual($id_field->getValue($result, 'job'), $row['job']);
90       $this->assertEqual($id_field->getValue($result, 'created_test'), $row['created']);
91     }
92   }
93
94   /**
95    * Asserts that a string is part of another string.
96    *
97    * @param string $haystack
98    *   The value to search in.
99    * @param string $needle
100    *   The value to search for.
101    * @param string $message
102    *   (optional) A message to display with the assertion. Do not translate
103    *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
104    *   variables in the message text, not t(). If left blank, a default message
105    *   will be displayed.
106    * @param string $group
107    *   (optional) The group this message is in, which is displayed in a column
108    *   in test output. Use 'Debug' to indicate this is debugging output. Do not
109    *   translate this string. Defaults to 'Other'; most tests do not override
110    *   this default.
111    *
112    * @return bool
113    *   TRUE if the assertion succeeded, FALSE otherwise.
114    */
115   protected function assertSubString($haystack, $needle, $message = '', $group = 'Other') {
116     return $this->assertTrue(strpos($haystack, $needle) !== FALSE, $message, $group);
117   }
118
119   /**
120    * Asserts that a string is not part of another string.
121    *
122    * @param string $haystack
123    *   The value to search in.
124    * @param string $needle
125    *   The value to search for.
126    * @param string $message
127    *   (optional) A message to display with the assertion. Do not translate
128    *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
129    *   variables in the message text, not t(). If left blank, a default message
130    *   will be displayed.
131    * @param string $group
132    *   (optional) The group this message is in, which is displayed in a column
133    *   in test output. Use 'Debug' to indicate this is debugging output. Do not
134    *   translate this string. Defaults to 'Other'; most tests do not override
135    *   this default.
136    *
137    * @return bool
138    *   TRUE if the assertion succeeded, FALSE otherwise.
139    */
140   protected function assertNotSubString($haystack, $needle, $message = '', $group = 'Other') {
141     return $this->assertTrue(strpos($haystack, $needle) === FALSE, $message, $group);
142   }
143
144   /**
145    * Tests general rewriting of the output.
146    */
147   public function testRewrite() {
148     /** @var \Drupal\Core\Render\RendererInterface $renderer */
149     $renderer = \Drupal::service('renderer');
150
151     $view = Views::getView('test_view');
152     $view->initHandlers();
153     $this->executeView($view);
154     $row = $view->result[0];
155     $id_field = $view->field['id'];
156
157     // Don't check the rewrite checkbox, so the text shouldn't appear.
158     $id_field->options['alter']['text'] = $random_text = $this->randomMachineName();
159     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
160       return $id_field->theme($row);
161     });
162     $this->assertNotSubString($output, $random_text);
163
164     $id_field->options['alter']['alter_text'] = TRUE;
165     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
166       return $id_field->theme($row);
167     });
168     $this->assertSubString($output, $random_text);
169   }
170
171   /**
172    * Tests rewriting of the output with HTML.
173    */
174   public function testRewriteHtmlWithTokens() {
175     /** @var \Drupal\Core\Render\RendererInterface $renderer */
176     $renderer = \Drupal::service('renderer');
177
178     $view = Views::getView('test_view');
179     $view->initHandlers();
180     $this->executeView($view);
181     $row = $view->result[0];
182     $id_field = $view->field['id'];
183
184     $id_field->options['alter']['text'] = '<p>{{ id }}</p>';
185     $id_field->options['alter']['alter_text'] = TRUE;
186     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
187       return $id_field->theme($row);
188     });
189     $this->assertSubString($output, '<p>1</p>');
190
191     // Add a non-safe HTML tag and make sure this gets removed.
192     $id_field->options['alter']['text'] = '<p>{{ id }} <script>alert("Script removed")</script></p>';
193     $id_field->options['alter']['alter_text'] = TRUE;
194     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
195       return $id_field->theme($row);
196     });
197     $this->assertSubString($output, '<p>1 alert("Script removed")</p>');
198   }
199
200   /**
201    * Tests rewriting of the output with HTML and aggregation.
202    */
203   public function testRewriteHtmlWithTokensAndAggregation() {
204     /** @var \Drupal\Core\Render\RendererInterface $renderer */
205     $renderer = \Drupal::service('renderer');
206
207     $view = Views::getView('test_view');
208     $view->setDisplay();
209     $view->displayHandlers->get('default')->options['fields']['id']['group_type'] = 'sum';
210     $view->displayHandlers->get('default')->setOption('group_by', TRUE);
211     $view->initHandlers();
212     $this->executeView($view);
213     $row = $view->result[0];
214     $id_field = $view->field['id'];
215
216     $id_field->options['alter']['text'] = '<p>{{ id }}</p>';
217     $id_field->options['alter']['alter_text'] = TRUE;
218     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
219       return $id_field->theme($row);
220     });
221     $this->assertSubString($output, '<p>1</p>');
222
223     // Add a non-safe HTML tag and make sure this gets removed.
224     $id_field->options['alter']['text'] = '<p>{{ id }} <script>alert("Script removed")</script></p>';
225     $id_field->options['alter']['alter_text'] = TRUE;
226     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
227       return $id_field->theme($row);
228     });
229     $this->assertSubString($output, '<p>1 alert("Script removed")</p>');
230   }
231
232   /**
233    * Tests the arguments tokens on field level.
234    */
235   public function testArgumentTokens() {
236     /** @var \Drupal\Core\Render\RendererInterface $renderer */
237     $renderer = \Drupal::service('renderer');
238
239     $view = Views::getView('test_field_argument_tokens');
240     $this->executeView($view, ['{{ { "#pre_render": ["views_test_data_test_pre_render_function"]} }}']);
241
242     $name_field_0 = $view->field['name'];
243
244     // Test the old style tokens.
245     $name_field_0->options['alter']['alter_text'] = TRUE;
246     $name_field_0->options['alter']['text'] = '%1 !1';
247
248     $row = $view->result[0];
249     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
250       return $name_field_0->advancedRender($row);
251     });
252
253     $this->assertFalse(strpos((string) $output, 'views_test_data_test_pre_render_function executed') !== FALSE, 'Ensure that the pre_render function was not executed');
254     $this->assertEqual('%1 !1', (string) $output, "Ensure that old style placeholders aren't replaced");
255
256     // This time use new style tokens but ensure that we still don't allow
257     // arbitrary code execution.
258     $name_field_0->options['alter']['alter_text'] = TRUE;
259     $name_field_0->options['alter']['text'] = '{{ arguments.null }} {{ raw_arguments.null }}';
260
261     $row = $view->result[0];
262     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
263       return $name_field_0->advancedRender($row);
264     });
265
266     $this->assertFalse(strpos((string) $output, 'views_test_data_test_pre_render_function executed') !== FALSE, 'Ensure that the pre_render function was not executed');
267     $this->assertEqual('{{ { &quot;#pre_render&quot;: [&quot;views_test_data_test_pre_render_function&quot;]} }} {{ { &quot;#pre_render&quot;: [&quot;views_test_data_test_pre_render_function&quot;]} }}', (string) $output, 'Ensure that new style placeholders are replaced');
268   }
269
270   /**
271    * Tests the field tokens, row level and field level.
272    */
273   public function testFieldTokens() {
274     /** @var \Drupal\Core\Render\RendererInterface $renderer */
275     $renderer = \Drupal::service('renderer');
276
277     $view = Views::getView('test_field_tokens');
278     $this->executeView($view);
279     $name_field_0 = $view->field['name'];
280     $name_field_1 = $view->field['name_1'];
281     $name_field_2 = $view->field['name_2'];
282     $row = $view->result[0];
283
284     $name_field_0->options['alter']['alter_text'] = TRUE;
285     $name_field_0->options['alter']['text'] = '{{ name }}';
286
287     $name_field_1->options['alter']['alter_text'] = TRUE;
288     $name_field_1->options['alter']['text'] = '{{ name_1 }} {{ name }}';
289
290     $name_field_2->options['alter']['alter_text'] = TRUE;
291     $name_field_2->options['alter']['text'] = '{% if name_2|length > 3 %}{{ name_2 }} {{ name_1 }}{% endif %}';
292
293     foreach ($view->result as $row) {
294       $expected_output_0 = $row->views_test_data_name;
295       $expected_output_1 = "$row->views_test_data_name $row->views_test_data_name";
296       $expected_output_2 = "$row->views_test_data_name $row->views_test_data_name $row->views_test_data_name";
297
298       $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
299         return $name_field_0->advancedRender($row);
300       });
301       $this->assertEqual($output, $expected_output_0, format_string('Test token replacement: "@token" gave "@output"', [
302         '@token' => $name_field_0->options['alter']['text'],
303         '@output' => $output,
304       ]));
305
306       $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_1, $row) {
307         return $name_field_1->advancedRender($row);
308       });
309       $this->assertEqual($output, $expected_output_1, format_string('Test token replacement: "@token" gave "@output"', [
310         '@token' => $name_field_1->options['alter']['text'],
311         '@output' => $output,
312       ]));
313
314       $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_2, $row) {
315         return $name_field_2->advancedRender($row);
316       });
317       $this->assertEqual($output, $expected_output_2, format_string('Test token replacement: "@token" gave "@output"', [
318         '@token' => $name_field_2->options['alter']['text'],
319         '@output' => $output,
320       ]));
321     }
322
323     $job_field = $view->field['job'];
324     $job_field->options['alter']['alter_text'] = TRUE;
325     $job_field->options['alter']['text'] = '{{ job }}';
326
327     $random_text = $this->randomMachineName();
328     $job_field->setTestValue($random_text);
329     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
330       return $job_field->advancedRender($row);
331     });
332     $this->assertSubString($output, $random_text, format_string('Make sure the self token (@token => @value) appears in the output (@output)', [
333       '@value' => $random_text,
334       '@output' => $output,
335       '@token' => $job_field->options['alter']['text'],
336     ]));
337
338     // Verify the token format used in D7 and earlier does not get substituted.
339     $old_token = '[job]';
340     $job_field->options['alter']['text'] = $old_token;
341     $random_text = $this->randomMachineName();
342     $job_field->setTestValue($random_text);
343     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
344       return $job_field->advancedRender($row);
345     });
346     $this->assertEqual($output, $old_token, format_string('Make sure the old token style (@token => @value) is not changed in the output (@output)', [
347       '@value' => $random_text,
348       '@output' => $output,
349       '@token' => $job_field->options['alter']['text'],
350     ]));
351
352     // Verify HTML tags are allowed in rewrite templates while token
353     // replacements are escaped.
354     $job_field->options['alter']['text'] = '<h1>{{ job }}</h1>';
355     $random_text = $this->randomMachineName();
356     $job_field->setTestValue('<span>' . $random_text . '</span>');
357     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
358       return $job_field->advancedRender($row);
359     });
360     $this->assertEqual($output, '<h1>&lt;span&gt;' . $random_text . '&lt;/span&gt;</h1>', 'Valid tags are allowed in rewrite templates and token replacements.');
361
362     // Verify <script> tags are correctly removed from rewritten text.
363     $rewrite_template = '<script>alert("malicious");</script>';
364     $job_field->options['alter']['text'] = $rewrite_template;
365     $random_text = $this->randomMachineName();
366     $job_field->setTestValue($random_text);
367     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
368       return $job_field->advancedRender($row);
369     });
370     $this->assertNotSubString($output, '<script>', 'Ensure a script tag in the rewrite template is removed.');
371
372     $rewrite_template = '<script>{{ job }}</script>';
373     $job_field->options['alter']['text'] = $rewrite_template;
374     $random_text = $this->randomMachineName();
375     $job_field->setTestValue($random_text);
376     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
377       return $job_field->advancedRender($row);
378     });
379     $this->assertEqual($output, $random_text, format_string('Make sure a script tag in the template (@template) is removed, leaving only the replaced token in the output (@output)', [
380       '@output' => $output,
381       '@template' => $rewrite_template,
382     ]));
383   }
384
385   /**
386    * Tests the exclude setting.
387    */
388   public function testExclude() {
389     /** @var \Drupal\Core\Render\RendererInterface $renderer */
390     $renderer = $this->container->get('renderer');
391     $view = Views::getView('test_field_output');
392     $view->initHandlers();
393     // Hide the field and see whether it's rendered.
394     $view->field['name']->options['exclude'] = TRUE;
395
396     $output = $view->preview();
397     $output = $renderer->renderRoot($output);
398     foreach ($this->dataSet() as $entry) {
399       $this->assertNotSubString($output, $entry['name']);
400     }
401
402     // Show and check the field.
403     $view->field['name']->options['exclude'] = FALSE;
404
405     $output = $view->preview();
406     $output = $renderer->renderRoot($output);
407     foreach ($this->dataSet() as $entry) {
408       $this->assertSubString($output, $entry['name']);
409     }
410   }
411
412   /**
413    * Tests everything related to empty output of a field.
414    */
415   public function testEmpty() {
416     $this->_testHideIfEmpty();
417     $this->_testEmptyText();
418   }
419
420   /**
421    * Tests the hide if empty functionality.
422    *
423    * This tests alters the result to get easier and less coupled results. It is
424    * important that assertIdentical() is used in this test since in PHP 0 == ''.
425    */
426   public function _testHideIfEmpty() {
427     /** @var \Drupal\Core\Render\RendererInterface $renderer */
428     $renderer = \Drupal::service('renderer');
429
430     $view = Views::getView('test_view');
431     $view->initDisplay();
432     $this->executeView($view);
433
434     $column_map_reversed = array_flip($this->columnMap);
435     $view->row_index = 0;
436     $random_name = $this->randomMachineName();
437     $random_value = $this->randomMachineName();
438
439     // Test when results are not rewritten and empty values are not hidden.
440     $view->field['name']->options['hide_alter_empty'] = FALSE;
441     $view->field['name']->options['hide_empty'] = FALSE;
442     $view->field['name']->options['empty_zero'] = FALSE;
443
444     // Test a valid string.
445     $view->result[0]->{$column_map_reversed['name']} = $random_name;
446     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
447       return $view->field['name']->advancedRender($view->result[0]);
448     });
449     $this->assertIdentical((string) $render, $random_name, 'By default, a string should not be treated as empty.');
450
451     // Test an empty string.
452     $view->result[0]->{$column_map_reversed['name']} = "";
453     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
454       return $view->field['name']->advancedRender($view->result[0]);
455     });
456     $this->assertIdentical($render, "", 'By default, "" should not be treated as empty.');
457
458     // Test zero as an integer.
459     $view->result[0]->{$column_map_reversed['name']} = 0;
460     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
461       return $view->field['name']->advancedRender($view->result[0]);
462     });
463     $this->assertIdentical((string) $render, '0', 'By default, 0 should not be treated as empty.');
464
465     // Test zero as a string.
466     $view->result[0]->{$column_map_reversed['name']} = "0";
467     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
468       return $view->field['name']->advancedRender($view->result[0]);
469     });
470     $this->assertIdentical((string) $render, "0", 'By default, "0" should not be treated as empty.');
471
472     // Test when results are not rewritten and non-zero empty values are hidden.
473     $view->field['name']->options['hide_alter_empty'] = TRUE;
474     $view->field['name']->options['hide_empty'] = TRUE;
475     $view->field['name']->options['empty_zero'] = FALSE;
476
477     // Test a valid string.
478     $view->result[0]->{$column_map_reversed['name']} = $random_name;
479     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
480       return $view->field['name']->advancedRender($view->result[0]);
481     });
482     $this->assertIdentical((string) $render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
483
484     // Test an empty string.
485     $view->result[0]->{$column_map_reversed['name']} = "";
486     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
487       return $view->field['name']->advancedRender($view->result[0]);
488     });
489     $this->assertIdentical($render, "", 'If hide_empty is checked, "" should be treated as empty.');
490
491     // Test zero as an integer.
492     $view->result[0]->{$column_map_reversed['name']} = 0;
493     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
494       return $view->field['name']->advancedRender($view->result[0]);
495     });
496     $this->assertIdentical((string) $render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
497
498     // Test zero as a string.
499     $view->result[0]->{$column_map_reversed['name']} = "0";
500     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
501       return $view->field['name']->advancedRender($view->result[0]);
502     });
503     $this->assertIdentical((string) $render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
504
505     // Test when results are not rewritten and all empty values are hidden.
506     $view->field['name']->options['hide_alter_empty'] = TRUE;
507     $view->field['name']->options['hide_empty'] = TRUE;
508     $view->field['name']->options['empty_zero'] = TRUE;
509
510     // Test zero as an integer.
511     $view->result[0]->{$column_map_reversed['name']} = 0;
512     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
513       return $view->field['name']->advancedRender($view->result[0]);
514     });
515     $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, 0 should be treated as empty.');
516
517     // Test zero as a string.
518     $view->result[0]->{$column_map_reversed['name']} = "0";
519     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
520       return $view->field['name']->advancedRender($view->result[0]);
521     });
522     $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, "0" should be treated as empty.');
523
524     // Test when results are rewritten to a valid string and non-zero empty
525     // results are hidden.
526     $view->field['name']->options['hide_alter_empty'] = FALSE;
527     $view->field['name']->options['hide_empty'] = TRUE;
528     $view->field['name']->options['empty_zero'] = FALSE;
529     $view->field['name']->options['alter']['alter_text'] = TRUE;
530     $view->field['name']->options['alter']['text'] = $random_name;
531
532     // Test a valid string.
533     $view->result[0]->{$column_map_reversed['name']} = $random_value;
534     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
535       return $view->field['name']->advancedRender($view->result[0]);
536     });
537     $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
538
539     // Test an empty string.
540     $view->result[0]->{$column_map_reversed['name']} = "";
541     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
542       return $view->field['name']->advancedRender($view->result[0]);
543     });
544     $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
545
546     // Test zero as an integer.
547     $view->result[0]->{$column_map_reversed['name']} = 0;
548     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
549       return $view->field['name']->advancedRender($view->result[0]);
550     });
551     $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
552
553     // Test zero as a string.
554     $view->result[0]->{$column_map_reversed['name']} = "0";
555     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
556       return $view->field['name']->advancedRender($view->result[0]);
557     });
558     $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
559
560     // Test when results are rewritten to an empty string and non-zero empty results are hidden.
561     $view->field['name']->options['hide_alter_empty'] = TRUE;
562     $view->field['name']->options['hide_empty'] = TRUE;
563     $view->field['name']->options['empty_zero'] = FALSE;
564     $view->field['name']->options['alter']['alter_text'] = TRUE;
565     $view->field['name']->options['alter']['text'] = "";
566
567     // Test a valid string.
568     $view->result[0]->{$column_map_reversed['name']} = $random_name;
569     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
570       return $view->field['name']->advancedRender($view->result[0]);
571     });
572     $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
573
574     // Test an empty string.
575     $view->result[0]->{$column_map_reversed['name']} = "";
576     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
577       return $view->field['name']->advancedRender($view->result[0]);
578     });
579     $this->assertIdentical($render, "", 'If the rewritten string is empty, "" should be treated as empty.');
580
581     // Test zero as an integer.
582     $view->result[0]->{$column_map_reversed['name']} = 0;
583     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
584       return $view->field['name']->advancedRender($view->result[0]);
585     });
586     $this->assertIdentical((string) $render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
587
588     // Test zero as a string.
589     $view->result[0]->{$column_map_reversed['name']} = "0";
590     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
591       return $view->field['name']->advancedRender($view->result[0]);
592     });
593     $this->assertIdentical((string) $render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
594
595     // Test when results are rewritten to zero as a string and non-zero empty
596     // results are hidden.
597     $view->field['name']->options['hide_alter_empty'] = FALSE;
598     $view->field['name']->options['hide_empty'] = TRUE;
599     $view->field['name']->options['empty_zero'] = FALSE;
600     $view->field['name']->options['alter']['alter_text'] = TRUE;
601     $view->field['name']->options['alter']['text'] = "0";
602
603     // Test a valid string.
604     $view->result[0]->{$column_map_reversed['name']} = $random_name;
605     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
606       return $view->field['name']->advancedRender($view->result[0]);
607     });
608     $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
609
610     // Test an empty string.
611     $view->result[0]->{$column_map_reversed['name']} = "";
612     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
613       return $view->field['name']->advancedRender($view->result[0]);
614     });
615     $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
616
617     // Test zero as an integer.
618     $view->result[0]->{$column_map_reversed['name']} = 0;
619     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
620       return $view->field['name']->advancedRender($view->result[0]);
621     });
622     $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
623
624     // Test zero as a string.
625     $view->result[0]->{$column_map_reversed['name']} = "0";
626     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
627       return $view->field['name']->advancedRender($view->result[0]);
628     });
629     $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
630
631     // Test when results are rewritten to a valid string and non-zero empty
632     // results are hidden.
633     $view->field['name']->options['hide_alter_empty'] = TRUE;
634     $view->field['name']->options['hide_empty'] = TRUE;
635     $view->field['name']->options['empty_zero'] = FALSE;
636     $view->field['name']->options['alter']['alter_text'] = TRUE;
637     $view->field['name']->options['alter']['text'] = $random_value;
638
639     // Test a valid string.
640     $view->result[0]->{$column_map_reversed['name']} = $random_name;
641     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
642       return $view->field['name']->advancedRender($view->result[0]);
643     });
644     $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
645
646     // Test an empty string.
647     $view->result[0]->{$column_map_reversed['name']} = "";
648     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
649       return $view->field['name']->advancedRender($view->result[0]);
650     });
651     $this->assertIdentical($render, "", 'If either the original or rewritten string is invalid, "" should be treated as empty.');
652
653     // Test zero as an integer.
654     $view->result[0]->{$column_map_reversed['name']} = 0;
655     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
656       return $view->field['name']->advancedRender($view->result[0]);
657     });
658     $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
659
660     // Test zero as a string.
661     $view->result[0]->{$column_map_reversed['name']} = "0";
662     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
663       return $view->field['name']->advancedRender($view->result[0]);
664     });
665     $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
666
667     // Test when results are rewritten to zero as a string and all empty
668     // original values and results are hidden.
669     $view->field['name']->options['hide_alter_empty'] = TRUE;
670     $view->field['name']->options['hide_empty'] = TRUE;
671     $view->field['name']->options['empty_zero'] = TRUE;
672     $view->field['name']->options['alter']['alter_text'] = TRUE;
673     $view->field['name']->options['alter']['text'] = "0";
674
675     // Test a valid string.
676     $view->result[0]->{$column_map_reversed['name']} = $random_name;
677     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
678       return $view->field['name']->advancedRender($view->result[0]);
679     });
680     $this->assertIdentical((string) $render, "", 'If the rewritten string is zero, it should be treated as empty.');
681
682     // Test an empty string.
683     $view->result[0]->{$column_map_reversed['name']} = "";
684     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
685       return $view->field['name']->advancedRender($view->result[0]);
686     });
687     $this->assertIdentical($render, "", 'If the rewritten string is zero, "" should be treated as empty.');
688
689     // Test zero as an integer.
690     $view->result[0]->{$column_map_reversed['name']} = 0;
691     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
692       return $view->field['name']->advancedRender($view->result[0]);
693     });
694     $this->assertIdentical($render, "", 'If the rewritten string is zero, 0 should not be treated as empty.');
695
696     // Test zero as a string.
697     $view->result[0]->{$column_map_reversed['name']} = "0";
698     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
699       return $view->field['name']->advancedRender($view->result[0]);
700     });
701     $this->assertIdentical($render, "", 'If the rewritten string is zero, "0" should not be treated as empty.');
702   }
703
704   /**
705    * Tests the usage of the empty text.
706    */
707   public function _testEmptyText() {
708     /** @var \Drupal\Core\Render\RendererInterface $renderer */
709     $renderer = \Drupal::service('renderer');
710
711     $view = Views::getView('test_view');
712     $view->initDisplay();
713     $this->executeView($view);
714
715     $column_map_reversed = array_flip($this->columnMap);
716     $view->row_index = 0;
717
718     $empty_text = $view->field['name']->options['empty'] = $this->randomMachineName();
719     $view->result[0]->{$column_map_reversed['name']} = "";
720     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
721       return $view->field['name']->advancedRender($view->result[0]);
722     });
723     $this->assertIdentical((string) $render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
724
725     $view->result[0]->{$column_map_reversed['name']} = "0";
726     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
727       return $view->field['name']->advancedRender($view->result[0]);
728     });
729     $this->assertIdentical((string) $render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
730
731     $view->result[0]->{$column_map_reversed['name']} = "0";
732     $view->field['name']->options['empty_zero'] = TRUE;
733     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
734       return $view->field['name']->advancedRender($view->result[0]);
735     });
736     $this->assertIdentical((string) $render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
737
738     $view->result[0]->{$column_map_reversed['name']} = "";
739     $view->field['name']->options['alter']['alter_text'] = TRUE;
740     $alter_text = $view->field['name']->options['alter']['text'] = $this->randomMachineName();
741     $view->field['name']->options['hide_alter_empty'] = FALSE;
742     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
743       return $view->field['name']->advancedRender($view->result[0]);
744     });
745     $this->assertIdentical((string) $render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
746
747     $view->field['name']->options['hide_alter_empty'] = TRUE;
748     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
749       return $view->field['name']->advancedRender($view->result[0]);
750     });
751     $this->assertIdentical((string) $render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
752   }
753
754   /**
755    * Tests views_handler_field::isValueEmpty().
756    */
757   public function testIsValueEmpty() {
758     $view = Views::getView('test_view');
759     $view->initHandlers();
760     $field = $view->field['name'];
761
762     $this->assertFalse($field->isValueEmpty("not empty", TRUE), 'A normal string is not empty.');
763     $this->assertTrue($field->isValueEmpty("not empty", TRUE, FALSE), 'A normal string which skips empty() can be seen as empty.');
764
765     $this->assertTrue($field->isValueEmpty("", TRUE), '"" is considered as empty.');
766
767     $this->assertTrue($field->isValueEmpty('0', TRUE), '"0" is considered as empty if empty_zero is TRUE.');
768     $this->assertTrue($field->isValueEmpty(0, TRUE), '0 is considered as empty if empty_zero is TRUE.');
769     $this->assertFalse($field->isValueEmpty('0', FALSE), '"0" is considered not as empty if empty_zero is FALSE.');
770     $this->assertFalse($field->isValueEmpty(0, FALSE), '0 is considered not as empty if empty_zero is FALSE.');
771
772     $this->assertTrue($field->isValueEmpty(NULL, TRUE, TRUE), 'Null should be always seen as empty, regardless of no_skip_empty.');
773     $this->assertTrue($field->isValueEmpty(NULL, TRUE, FALSE), 'Null should be always seen as empty, regardless of no_skip_empty.');
774   }
775
776   /**
777    * Tests whether the filters are click sortable as expected.
778    */
779   public function testClickSortable() {
780     // Test that clickSortable is TRUE by default.
781     $item = [
782       'table' => 'views_test_data',
783       'field' => 'name',
784     ];
785     $plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
786     $this->assertTrue($plugin->clickSortable(), 'TRUE as a default value is correct.');
787
788     // Test that clickSortable is TRUE by when set TRUE in the data.
789     $item['field'] = 'id';
790     $plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
791     $this->assertTrue($plugin->clickSortable(), 'TRUE as a views data value is correct.');
792
793     // Test that clickSortable is FALSE by when set FALSE in the data.
794     $item['field'] = 'job';
795     $plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
796     $this->assertFalse($plugin->clickSortable(), 'FALSE as a views data value is correct.');
797   }
798
799   /**
800    * Tests the trimText method.
801    */
802   public function testTrimText() {
803     // Test unicode. See https://www.drupal.org/node/513396#comment-2839416.
804     $text = [
805       'Tuy nhiên, những hi vọng',
806       'Giả sử chúng tôi có 3 Apple',
807       'siêu nhỏ này là bộ xử lý',
808       'Di động của nhà sản xuất Phần Lan',
809       'khoảng cách từ đại lí đến',
810       'của hãng bao gồm ba dòng',
811       'сд асд асд ас',
812       'асд асд асд ас'
813     ];
814     // Just test maxlength without word boundary.
815     $alter = [
816       'max_length' => 10,
817     ];
818     $expect = [
819       'Tuy nhiên,',
820       'Giả sử chú',
821       'siêu nhỏ n',
822       'Di động củ',
823       'khoảng các',
824       'của hãng b',
825       'сд асд асд',
826       'асд асд ас',
827     ];
828
829     foreach ($text as $key => $line) {
830       $result_text = FieldPluginBase::trimText($alter, $line);
831       $this->assertEqual($result_text, $expect[$key]);
832     }
833
834     // Test also word_boundary
835     $alter['word_boundary'] = TRUE;
836     $expect = [
837       'Tuy nhiên',
838       'Giả sử',
839       'siêu nhỏ',
840       'Di động',
841       'khoảng',
842       'của hãng',
843       'сд асд',
844       'асд асд',
845     ];
846
847     foreach ($text as $key => $line) {
848       $result_text = FieldPluginBase::trimText($alter, $line);
849       $this->assertEqual($result_text, $expect[$key]);
850     }
851   }
852
853 }