d94b8a9386c15d6b35963b9d991b9254e5e1a910
[yaffs-website] / web / core / modules / rest / tests / src / Functional / Views / StyleSerializerTest.php
1 <?php
2
3 namespace Drupal\Tests\rest\Functional\Views;
4
5 use Drupal\Component\Serialization\Json;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\entity_test\Entity\EntityTest;
10 use Drupal\field\Entity\FieldConfig;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\language\Entity\ConfigurableLanguage;
13 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
14 use Drupal\Tests\views\Functional\ViewTestBase;
15 use Drupal\views\Entity\View;
16 use Drupal\views\Plugin\views\display\DisplayPluginBase;
17 use Drupal\views\Views;
18 use Drupal\views\Tests\ViewTestData;
19 use Symfony\Component\HttpFoundation\Request;
20
21 /**
22  * Tests the serializer style plugin.
23  *
24  * @group rest
25  * @see \Drupal\rest\Plugin\views\display\RestExport
26  * @see \Drupal\rest\Plugin\views\style\Serializer
27  * @see \Drupal\rest\Plugin\views\row\DataEntityRow
28  * @see \Drupal\rest\Plugin\views\row\DataFieldRow
29  */
30 class StyleSerializerTest extends ViewTestBase {
31
32   use AssertPageCacheContextsAndTagsTrait;
33
34   /**
35    * Modules to install.
36    *
37    * @var array
38    */
39   public static $modules = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth'];
40
41   /**
42    * Views used by this test.
43    *
44    * @var array
45    */
46   public static $testViews = ['test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter'];
47
48   /**
49    * A user with administrative privileges to look at test entity and configure views.
50    */
51   protected $adminUser;
52
53   protected function setUp($import_test_views = TRUE) {
54     parent::setUp($import_test_views);
55
56     ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
57
58     $this->adminUser = $this->drupalCreateUser(['administer views', 'administer entity_test content', 'access user profiles', 'view test entity']);
59
60     // Save some entity_test entities.
61     for ($i = 1; $i <= 10; $i++) {
62       EntityTest::create(['name' => 'test_' . $i, 'user_id' => $this->adminUser->id()])->save();
63     }
64
65     $this->enableViewsTestModule();
66   }
67
68   /**
69    * Checks that the auth options restricts access to a REST views display.
70    */
71   public function testRestViewsAuthentication() {
72     // Assume the view is hidden behind a permission.
73     $this->drupalGet('test/serialize/auth_with_perm', ['query' => ['_format' => 'json']]);
74     $this->assertResponse(401);
75
76     // Not even logging in would make it possible to see the view, because then
77     // we are denied based on authentication method (cookie).
78     $this->drupalLogin($this->adminUser);
79     $this->drupalGet('test/serialize/auth_with_perm', ['query' => ['_format' => 'json']]);
80     $this->assertResponse(403);
81     $this->drupalLogout();
82
83     // But if we use the basic auth authentication strategy, we should be able
84     // to see the page.
85     $url = $this->buildUrl('test/serialize/auth_with_perm');
86     $response = \Drupal::httpClient()->get($url, [
87       'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
88     ]);
89
90     // Ensure that any changes to variables in the other thread are picked up.
91     $this->refreshVariables();
92
93     $this->assertResponse(200);
94   }
95
96   /**
97    * Checks the behavior of the Serializer callback paths and row plugins.
98    */
99   public function testSerializerResponses() {
100     // Test the serialize callback.
101     $view = Views::getView('test_serializer_display_field');
102     $view->initDisplay();
103     $this->executeView($view);
104
105     $actual_json = $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
106     $this->assertResponse(200);
107     $this->assertCacheTags($view->getCacheTags());
108     $this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
109     // @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
110     // propagation of cache max-age.
111
112     // Test the http Content-type.
113     $headers = $this->drupalGetHeaders();
114     $this->assertSame(['application/json'], $headers['Content-Type']);
115
116     $expected = [];
117     foreach ($view->result as $row) {
118       $expected_row = [];
119       foreach ($view->field as $id => $field) {
120         $expected_row[$id] = $field->render($row);
121       }
122       $expected[] = $expected_row;
123     }
124
125     $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
126
127     // Test that the rendered output and the preview output are the same.
128     $view->destroy();
129     $view->setDisplay('rest_export_1');
130     // Mock the request content type by setting it on the display handler.
131     $view->display_handler->setContentType('json');
132     $output = $view->preview();
133     $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
134
135     // Test a 403 callback.
136     $this->drupalGet('test/serialize/denied');
137     $this->assertResponse(403);
138
139     // Test the entity rows.
140     $view = Views::getView('test_serializer_display_entity');
141     $view->initDisplay();
142     $this->executeView($view);
143
144     // Get the serializer service.
145     $serializer = $this->container->get('serializer');
146
147     $entities = [];
148     foreach ($view->result as $row) {
149       $entities[] = $row->_entity;
150     }
151
152     $expected = $serializer->serialize($entities, 'json');
153
154     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
155     $this->assertResponse(200);
156     $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
157     $expected_cache_tags = $view->getCacheTags();
158     $expected_cache_tags[] = 'entity_test_list';
159     /** @var \Drupal\Core\Entity\EntityInterface $entity */
160     foreach ($entities as $entity) {
161       $expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
162     }
163     $this->assertCacheTags($expected_cache_tags);
164     $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
165
166     $expected = $serializer->serialize($entities, 'hal_json');
167     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'hal_json']]);
168     $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
169     $this->assertCacheTags($expected_cache_tags);
170
171     // Change the default format to xml.
172     $view->setDisplay('rest_export_1');
173     $view->getDisplay()->setOption('style', [
174       'type' => 'serializer',
175       'options' => [
176         'uses_fields' => FALSE,
177         'formats' => [
178           'xml' => 'xml',
179         ],
180       ],
181     ]);
182     $view->save();
183     $expected = $serializer->serialize($entities, 'xml');
184     $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
185     $this->assertSame(trim($expected), $actual_xml);
186     $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
187
188     // Allow multiple formats.
189     $view->setDisplay('rest_export_1');
190     $view->getDisplay()->setOption('style', [
191       'type' => 'serializer',
192       'options' => [
193         'uses_fields' => FALSE,
194         'formats' => [
195           'xml' => 'xml',
196           'json' => 'json',
197         ],
198       ],
199     ]);
200     $view->save();
201     $expected = $serializer->serialize($entities, 'json');
202     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
203     $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
204     $expected = $serializer->serialize($entities, 'xml');
205     $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
206     $this->assertSame(trim($expected), $actual_xml);
207   }
208
209   /**
210    * Verifies site maintenance mode functionality.
211    */
212   public function testSiteMaintenance() {
213     $view = Views::getView('test_serializer_display_field');
214     $view->initDisplay();
215     $this->executeView($view);
216
217     // Set the site to maintenance mode.
218     $this->container->get('state')->set('system.maintenance_mode', TRUE);
219
220     $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
221     // Verify that the endpoint is unavailable for anonymous users.
222     $this->assertResponse(503);
223   }
224
225   /**
226    * Sets up a request on the request stack with a specified format.
227    *
228    * @param string $format
229    *   The new request format.
230    */
231   protected function addRequestWithFormat($format) {
232     $request = \Drupal::request();
233     $request = clone $request;
234     $request->setRequestFormat($format);
235
236     \Drupal::requestStack()->push($request);
237   }
238
239   /**
240    * Tests REST export with views render caching enabled.
241    */
242   public function testRestRenderCaching() {
243     $this->drupalLogin($this->adminUser);
244     /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
245     $render_cache = \Drupal::service('render_cache');
246
247     // Enable render caching for the views.
248     /** @var \Drupal\views\ViewEntityInterface $storage */
249     $storage = View::load('test_serializer_display_entity');
250     $options = &$storage->getDisplay('default');
251     $options['display_options']['cache'] = [
252       'type' => 'tag',
253     ];
254     $storage->save();
255
256     $original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
257
258     // Ensure that there is no corresponding render cache item yet.
259     $original['#cache'] += ['contexts' => []];
260     $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
261
262     $cache_tags = [
263       'config:views.view.test_serializer_display_entity',
264       'entity_test:1',
265       'entity_test:10',
266       'entity_test:2',
267       'entity_test:3',
268       'entity_test:4',
269       'entity_test:5',
270       'entity_test:6',
271       'entity_test:7',
272       'entity_test:8',
273       'entity_test:9',
274       'entity_test_list'
275     ];
276     $cache_contexts = [
277       'entity_test_view_grants',
278       'languages:language_interface',
279       'theme',
280       'request_format',
281     ];
282
283     $this->assertFalse($render_cache->get($original));
284
285     // Request the page, once in XML and once in JSON to ensure that the caching
286     // varies by it.
287     $result1 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
288     $this->addRequestWithFormat('json');
289     $this->assertHeader('content-type', 'application/json');
290     $this->assertCacheContexts($cache_contexts);
291     $this->assertCacheTags($cache_tags);
292     $this->assertTrue($render_cache->get($original));
293
294     $result_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
295     $this->addRequestWithFormat('xml');
296     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
297     $this->assertCacheContexts($cache_contexts);
298     $this->assertCacheTags($cache_tags);
299     $this->assertTrue($render_cache->get($original));
300
301     // Ensure that the XML output is different from the JSON one.
302     $this->assertNotEqual($result1, $result_xml);
303
304     // Ensure that the cached page works.
305     $result2 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
306     $this->addRequestWithFormat('json');
307     $this->assertHeader('content-type', 'application/json');
308     $this->assertEqual($result2, $result1);
309     $this->assertCacheContexts($cache_contexts);
310     $this->assertCacheTags($cache_tags);
311     $this->assertTrue($render_cache->get($original));
312
313     // Create a new entity and ensure that the cache tags are taken over.
314     EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
315     $result3 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
316     $this->addRequestWithFormat('json');
317     $this->assertHeader('content-type', 'application/json');
318     $this->assertNotEqual($result3, $result2);
319
320     // Add the new entity cache tag and remove the first one, because we just
321     // show 10 items in total.
322     $cache_tags[] = 'entity_test:11';
323     unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
324
325     $this->assertCacheContexts($cache_contexts);
326     $this->assertCacheTags($cache_tags);
327     $this->assertTrue($render_cache->get($original));
328   }
329
330   /**
331    * Tests the response format configuration.
332    */
333   public function testResponseFormatConfiguration() {
334     $this->drupalLogin($this->adminUser);
335
336     $style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
337
338     // Select only 'xml' as an accepted format.
339     $this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply'));
340     $this->drupalPostForm(NULL, [], t('Save'));
341
342     // Should return a 406.
343     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
344     $this->assertHeader('content-type', 'application/json');
345     $this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
346     // Should return a 200.
347     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
348     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
349     $this->assertResponse(200, 'A 200 response was returned when XML was requested.');
350
351     // Add 'json' as an accepted format, so we have multiple.
352     $this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply'));
353     $this->drupalPostForm(NULL, [], t('Save'));
354
355     // Should return a 200.
356     // @todo This should be fixed when we have better content negotiation.
357     $this->drupalGet('test/serialize/field');
358     $this->assertHeader('content-type', 'application/json');
359     $this->assertResponse(200, 'A 200 response was returned when any format was requested.');
360
361     // Should return a 200. Emulates a sample Firefox header.
362     $this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
363     $this->assertHeader('content-type', 'application/json');
364     $this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
365
366     // Should return a 200.
367     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
368     $this->assertHeader('content-type', 'application/json');
369     $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
370     $headers = $this->drupalGetHeaders();
371     $this->assertEqual($headers['Content-Type'], ['application/json'], 'The header Content-type is correct.');
372     // Should return a 200.
373     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
374     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
375     $this->assertResponse(200, 'A 200 response was returned when XML was requested');
376     $headers = $this->drupalGetHeaders();
377     $this->assertSame(['text/xml; charset=UTF-8'], $headers['Content-Type']);
378     // Should return a 406.
379     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
380     // We want to show the first format by default, see
381     // \Drupal\rest\Plugin\views\style\Serializer::render.
382     $this->assertHeader('content-type', 'application/json');
383     $this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
384
385     // Now configure now format, so all of them should be allowed.
386     $this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply'));
387
388     // Should return a 200.
389     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
390     $this->assertHeader('content-type', 'application/json');
391     $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
392     // Should return a 200.
393     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
394     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
395     $this->assertResponse(200, 'A 200 response was returned when XML was requested');
396     // Should return a 200.
397     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
398     // We want to show the first format by default, see
399     // \Drupal\rest\Plugin\views\style\Serializer::render.
400     $this->assertHeader('content-type', 'application/json');
401     $this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
402   }
403
404   /**
405    * Test the field ID alias functionality of the DataFieldRow plugin.
406    */
407   public function testUIFieldAlias() {
408     $this->drupalLogin($this->adminUser);
409
410     // Test the UI settings for adding field ID aliases.
411     $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
412     $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
413     $this->assertLinkByHref($row_options);
414
415     // Test an empty string for an alias, this should not be used. This also
416     // tests that the form can be submitted with no aliases.
417     $this->drupalPostForm($row_options, ['row_options[field_options][name][alias]' => ''], t('Apply'));
418     $this->drupalPostForm(NULL, [], t('Save'));
419
420     $view = Views::getView('test_serializer_display_field');
421     $view->setDisplay('rest_export_1');
422     $this->executeView($view);
423
424     $expected = [];
425     foreach ($view->result as $row) {
426       $expected_row = [];
427       foreach ($view->field as $id => $field) {
428         $expected_row[$id] = $field->render($row);
429       }
430       $expected[] = $expected_row;
431     }
432
433     $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
434
435     // Test a random aliases for fields, they should be replaced.
436     $alias_map = [
437       'name' => $this->randomMachineName(),
438       // Use # to produce an invalid character for the validation.
439       'nothing' => '#' . $this->randomMachineName(),
440       'created' => 'created',
441     ];
442
443     $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
444     $this->drupalPostForm($row_options, $edit, t('Apply'));
445     $this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
446
447     // Change the map alias value to a valid one.
448     $alias_map['nothing'] = $this->randomMachineName();
449
450     $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
451     $this->drupalPostForm($row_options, $edit, t('Apply'));
452
453     $this->drupalPostForm(NULL, [], t('Save'));
454
455     $view = Views::getView('test_serializer_display_field');
456     $view->setDisplay('rest_export_1');
457     $this->executeView($view);
458
459     $expected = [];
460     foreach ($view->result as $row) {
461       $expected_row = [];
462       foreach ($view->field as $id => $field) {
463         $expected_row[$alias_map[$id]] = $field->render($row);
464       }
465       $expected[] = $expected_row;
466     }
467
468     $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
469   }
470
471   /**
472    * Tests the raw output options for row field rendering.
473    */
474   public function testFieldRawOutput() {
475     $this->drupalLogin($this->adminUser);
476
477     // Test the UI settings for adding field ID aliases.
478     $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
479     $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
480     $this->assertLinkByHref($row_options);
481
482     // Test an empty string for an alias, this should not be used. This also
483     // tests that the form can be submitted with no aliases.
484     $values = [
485       'row_options[field_options][created][raw_output]' => '1',
486       'row_options[field_options][name][raw_output]' => '1',
487     ];
488     $this->drupalPostForm($row_options, $values, t('Apply'));
489     $this->drupalPostForm(NULL, [], t('Save'));
490
491     $view = Views::getView('test_serializer_display_field');
492     $view->setDisplay('rest_export_1');
493     $this->executeView($view);
494
495     $storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
496
497     // Update the name for each to include a script tag.
498     foreach ($storage->loadMultiple() as $entity_test) {
499       $name = $entity_test->name->value;
500       $entity_test->set('name', "<script>$name</script>");
501       $entity_test->save();
502     }
503
504     // Just test the raw 'created' value against each row.
505     foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
506       $this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
507       $this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
508     }
509
510     // Test result with an excluded field.
511     $view->setDisplay('rest_export_1');
512     $view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
513       'name' => [
514         'id' => 'name',
515         'table' => 'views_test_data',
516         'field' => 'name',
517         'relationship' => 'none',
518       ],
519       'created' => [
520         'id' => 'created',
521         'exclude' => TRUE,
522         'table' => 'views_test_data',
523         'field' => 'created',
524         'relationship' => 'none',
525       ],
526     ]);
527     $view->save();
528     $this->executeView($view);
529     foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
530       $this->assertTrue(!isset($values['created']), 'Excluded value not found.');
531     }
532     // Test that the excluded field is not shown in the row options.
533     $this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
534     $this->assertNoText('created');
535   }
536
537   /**
538    * Tests the live preview output for json output.
539    */
540   public function testLivePreview() {
541     // We set up a request so it looks like an request in the live preview.
542     $request = new Request();
543     $request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
544     /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
545     $request_stack = \Drupal::service('request_stack');
546     $request_stack->push($request);
547
548     $view = Views::getView('test_serializer_display_entity');
549     $view->setDisplay('rest_export_1');
550     $this->executeView($view);
551
552     // Get the serializer service.
553     $serializer = $this->container->get('serializer');
554
555     $entities = [];
556     foreach ($view->result as $row) {
557       $entities[] = $row->_entity;
558     }
559
560     $expected = $serializer->serialize($entities, 'json');
561
562     $view->live_preview = TRUE;
563
564     $build = $view->preview();
565     $rendered_json = $build['#plain_text'];
566     $this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
567     $view->destroy();
568
569     $expected = $serializer->serialize($entities, 'xml');
570
571     // Change the request format to xml.
572     $view->setDisplay('rest_export_1');
573     $view->getDisplay()->setOption('style', [
574       'type' => 'serializer',
575       'options' => [
576         'uses_fields' => FALSE,
577         'formats' => [
578           'xml' => 'xml',
579         ],
580       ],
581     ]);
582
583     $this->executeView($view);
584     $build = $view->preview();
585     $rendered_xml = $build['#plain_text'];
586     $this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
587   }
588
589   /**
590    * Tests the views interface for REST export displays.
591    */
592   public function testSerializerViewsUI() {
593     $this->drupalLogin($this->adminUser);
594     // Click the "Update preview button".
595     $this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = [], t('Update preview'));
596     $this->assertResponse(200);
597     // Check if we receive the expected result.
598     $result = $this->xpath('//div[@id="views-live-preview"]/pre');
599     $json_preview = $result[0]->getText();
600     $this->assertSame($json_preview, $this->drupalGet('test/serialize/field'), 'The expected JSON preview output was found.');
601   }
602
603   /**
604    * Tests the field row style using fieldapi fields.
605    */
606   public function testFieldapiField() {
607     $this->drupalCreateContentType(['type' => 'page']);
608     $node = $this->drupalCreateNode();
609
610     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
611     $this->assertEqual($result[0]['nid'], $node->id());
612     $this->assertEqual($result[0]['body'], $node->body->processed);
613
614     // Make sure that serialized fields are not exposed to XSS.
615     $node = $this->drupalCreateNode();
616     $node->body = [
617       'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
618       'format' => filter_default_format(),
619     ];
620     $node->save();
621     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
622     $this->assertEqual($result[1]['nid'], $node->id());
623     $this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
624
625     $this->drupalLogin($this->adminUser);
626
627     // Add an alias and make the output raw.
628     $row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
629
630     // Test an empty string for an alias, this should not be used. This also
631     // tests that the form can be submitted with no aliases.
632     $this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
633     $this->drupalPostForm(NULL, [], t('Save'));
634
635     $view = Views::getView('test_serializer_node_display_field');
636     $view->setDisplay('rest_export_1');
637     $this->executeView($view);
638
639     // Test the raw 'created' value against each row.
640     foreach (Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']])) as $index => $values) {
641       $this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
642     }
643
644     // Test that multiple raw body fields are shown.
645     // Make the body field unlimited cardinatlity.
646     $storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
647     $storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
648     $storage_definition->save();
649
650     $this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
651     $this->drupalPostForm(NULL, [], t('Save'));
652
653     $node = $this->drupalCreateNode();
654
655     $body = [
656       'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
657       'format' => filter_default_format(),
658     ];
659     // Add two body items.
660     $node->body = [$body, $body];
661     $node->save();
662
663     $view = Views::getView('test_serializer_node_display_field');
664     $view->setDisplay('rest_export_1');
665     $this->executeView($view);
666
667     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
668     $this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
669     $this->assertEqual($result[2]['body'], array_map(function ($item) {
670       return $item['value'];
671     }, $node->body->getValue()), 'Expected raw body values found.');
672   }
673
674   /**
675    * Tests the "Grouped rows" functionality.
676    */
677   public function testGroupRows() {
678     /** @var \Drupal\Core\Render\RendererInterface $renderer */
679     $renderer = $this->container->get('renderer');
680     $this->drupalCreateContentType(['type' => 'page']);
681     // Create a text field with cardinality set to unlimited.
682     $field_name = 'field_group_rows';
683     $field_storage = FieldStorageConfig::create([
684       'field_name' => $field_name,
685       'entity_type' => 'node',
686       'type' => 'string',
687       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
688     ]);
689     $field_storage->save();
690     // Create an instance of the text field on the content type.
691     $field = FieldConfig::create([
692       'field_storage' => $field_storage,
693       'bundle' => 'page',
694     ]);
695     $field->save();
696     $grouped_field_values = ['a', 'b', 'c'];
697     $edit = [
698       'title' => $this->randomMachineName(),
699       $field_name => $grouped_field_values,
700     ];
701     $this->drupalCreateNode($edit);
702     $view = Views::getView('test_serializer_node_display_field');
703     $view->setDisplay('rest_export_1');
704     // Override the view's fields to include the field_group_rows field, set the
705     // group_rows setting to true.
706     $fields = [
707       $field_name => [
708         'id' => $field_name,
709         'table' => 'node__' . $field_name,
710         'field' => $field_name,
711         'type' => 'string',
712         'group_rows' => TRUE,
713       ],
714     ];
715     $view->displayHandlers->get('default')->overrideOption('fields', $fields);
716     $build = $view->preview();
717     // Get the serializer service.
718     $serializer = $this->container->get('serializer');
719     // Check if the field_group_rows field is grouped.
720     $expected = [];
721     $expected[] = [$field_name => implode(', ', $grouped_field_values)];
722     $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
723     // Set the group rows setting to false.
724     $view = Views::getView('test_serializer_node_display_field');
725     $view->setDisplay('rest_export_1');
726     $fields[$field_name]['group_rows'] = FALSE;
727     $view->displayHandlers->get('default')->overrideOption('fields', $fields);
728     $build = $view->preview();
729     // Check if the field_group_rows field is ungrouped and displayed per row.
730     $expected = [];
731     foreach ($grouped_field_values as $grouped_field_value) {
732       $expected[] = [$field_name => $grouped_field_value];
733     }
734     $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
735   }
736
737   /**
738    * Tests the exposed filter works.
739    *
740    * There is an exposed filter on the title field which takes a title query
741    * parameter. This is set to filter nodes by those whose title starts with
742    * the value provided.
743    */
744   public function testRestViewExposedFilter() {
745     $this->drupalCreateContentType(['type' => 'page']);
746     $node0 = $this->drupalCreateNode(['title' => 'Node 1']);
747     $node1 = $this->drupalCreateNode(['title' => 'Node 11']);
748     $node2 = $this->drupalCreateNode(['title' => 'Node 111']);
749
750     // Test that no filter brings back all three nodes.
751     $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json']]));
752
753     $expected = [
754       0 => [
755         'nid' => $node0->id(),
756         'body' => $node0->body->processed,
757       ],
758       1 => [
759         'nid' => $node1->id(),
760         'body' => $node1->body->processed,
761       ],
762       2 => [
763         'nid' => $node2->id(),
764         'body' => $node2->body->processed,
765       ],
766     ];
767
768     $this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
769
770     // Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
771     $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json', 'title' => 'Node 11']]));
772
773     $expected = [
774       0 => [
775         'nid' => $node1->id(),
776         'body' => $node1->body->processed,
777       ],
778       1 => [
779         'nid' => $node2->id(),
780         'body' => $node2->body->processed,
781       ],
782     ];
783
784     $cache_contexts = [
785       'languages:language_content',
786       'languages:language_interface',
787       'theme',
788       'request_format',
789       'user.node_grants:view',
790       'url',
791     ];
792
793     $this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
794     $this->assertCacheContexts($cache_contexts);
795   }
796
797   /**
798    * Test multilingual entity rows.
799    */
800   public function testMulEntityRows() {
801     // Create some languages.
802     ConfigurableLanguage::createFromLangcode('l1')->save();
803     ConfigurableLanguage::createFromLangcode('l2')->save();
804
805     // Create an entity with no translations.
806     $storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
807     $storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
808
809     // Create some entities with translations.
810     $entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
811     $entity->save();
812     $entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
813     $entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
814     $entity->save();
815     $entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
816
817     // Get the names of the output.
818     $json = $this->drupalGet('test/serialize/translated_entity', ['query' => ['_format' => 'json']]);
819     $decoded = $this->container->get('serializer')->decode($json, 'hal_json');
820     $names = [];
821     foreach ($decoded as $item) {
822       $names[] = $item['name'][0]['value'];
823     }
824     sort($names);
825
826     // Check that the names are correct.
827     $expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
828     $this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');
829   }
830
831 }