3 namespace Drupal\rest\Tests\Views;
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
7 use Drupal\Core\Field\FieldStorageDefinitionInterface;
8 use Drupal\entity_test\Entity\EntityTest;
9 use Drupal\field\Entity\FieldConfig;
10 use Drupal\field\Entity\FieldStorageConfig;
11 use Drupal\language\Entity\ConfigurableLanguage;
12 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
13 use Drupal\views\Entity\View;
14 use Drupal\views\Plugin\views\display\DisplayPluginBase;
15 use Drupal\views\Views;
16 use Drupal\views\Tests\Plugin\PluginTestBase;
17 use Drupal\views\Tests\ViewTestData;
18 use Symfony\Component\HttpFoundation\Request;
21 * Tests the serializer style plugin.
24 * @see \Drupal\rest\Plugin\views\display\RestExport
25 * @see \Drupal\rest\Plugin\views\style\Serializer
26 * @see \Drupal\rest\Plugin\views\row\DataEntityRow
27 * @see \Drupal\rest\Plugin\views\row\DataFieldRow
29 class StyleSerializerTest extends PluginTestBase {
31 use AssertPageCacheContextsAndTagsTrait;
36 protected $dumpHeaders = TRUE;
43 public static $modules = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth'];
46 * Views used by this test.
50 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'];
53 * A user with administrative privileges to look at test entity and configure views.
57 protected function setUp() {
60 ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
62 $this->adminUser = $this->drupalCreateUser(['administer views', 'administer entity_test content', 'access user profiles', 'view test entity']);
64 // Save some entity_test entities.
65 for ($i = 1; $i <= 10; $i++) {
66 EntityTest::create(['name' => 'test_' . $i, 'user_id' => $this->adminUser->id()])->save();
69 $this->enableViewsTestModule();
73 * Checks that the auth options restricts access to a REST views display.
75 public function testRestViewsAuthentication() {
76 // Assume the view is hidden behind a permission.
77 $this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
78 $this->assertResponse(401);
80 // Not even logging in would make it possible to see the view, because then
81 // we are denied based on authentication method (cookie).
82 $this->drupalLogin($this->adminUser);
83 $this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
84 $this->assertResponse(403);
85 $this->drupalLogout();
87 // But if we use the basic auth authentication strategy, we should be able
89 $url = $this->buildUrl('test/serialize/auth_with_perm');
90 $response = \Drupal::httpClient()->get($url, [
91 'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
94 // Ensure that any changes to variables in the other thread are picked up.
95 $this->refreshVariables();
97 $headers = $response->getHeaders();
98 $this->verbose('GET request to: ' . $url .
99 '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
100 '<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
101 '<hr />Response body: ' . (string) $response->getBody());
102 $this->assertResponse(200);
106 * Checks the behavior of the Serializer callback paths and row plugins.
108 public function testSerializerResponses() {
109 // Test the serialize callback.
110 $view = Views::getView('test_serializer_display_field');
111 $view->initDisplay();
112 $this->executeView($view);
114 $actual_json = $this->drupalGetWithFormat('test/serialize/field', 'json');
115 $this->assertResponse(200);
116 $this->assertCacheTags($view->getCacheTags());
117 $this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
118 // @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
119 // propagation of cache max-age.
121 // Test the http Content-type.
122 $headers = $this->drupalGetHeaders();
123 $this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
126 foreach ($view->result as $row) {
128 foreach ($view->field as $id => $field) {
129 $expected_row[$id] = $field->render($row);
131 $expected[] = $expected_row;
134 $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
137 // Test that the rendered output and the preview output are the same.
139 $view->setDisplay('rest_export_1');
140 // Mock the request content type by setting it on the display handler.
141 $view->display_handler->setContentType('json');
142 $output = $view->preview();
143 $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
145 // Test a 403 callback.
146 $this->drupalGet('test/serialize/denied');
147 $this->assertResponse(403);
149 // Test the entity rows.
150 $view = Views::getView('test_serializer_display_entity');
151 $view->initDisplay();
152 $this->executeView($view);
154 // Get the serializer service.
155 $serializer = $this->container->get('serializer');
158 foreach ($view->result as $row) {
159 $entities[] = $row->_entity;
162 $expected = $serializer->serialize($entities, 'json');
164 $actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
165 $this->assertResponse(200);
166 $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
167 $expected_cache_tags = $view->getCacheTags();
168 $expected_cache_tags[] = 'entity_test_list';
169 /** @var \Drupal\Core\Entity\EntityInterface $entity */
170 foreach ($entities as $entity) {
171 $expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
173 $this->assertCacheTags($expected_cache_tags);
174 $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
176 $expected = $serializer->serialize($entities, 'hal_json');
177 $actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'hal_json');
178 $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
179 $this->assertCacheTags($expected_cache_tags);
181 // Change the default format to xml.
182 $view->setDisplay('rest_export_1');
183 $view->getDisplay()->setOption('style', [
184 'type' => 'serializer',
186 'uses_fields' => FALSE,
193 $expected = $serializer->serialize($entities, 'xml');
194 $actual_xml = $this->drupalGet('test/serialize/entity');
195 $this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
196 $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
198 // Allow multiple formats.
199 $view->setDisplay('rest_export_1');
200 $view->getDisplay()->setOption('style', [
201 'type' => 'serializer',
203 'uses_fields' => FALSE,
211 $expected = $serializer->serialize($entities, 'json');
212 $actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
213 $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
214 $expected = $serializer->serialize($entities, 'xml');
215 $actual_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
216 $this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
220 * Verifies site maintenance mode functionality.
222 public function testSiteMaintenance() {
223 $view = Views::getView('test_serializer_display_field');
224 $view->initDisplay();
225 $this->executeView($view);
227 // Set the site to maintenance mode.
228 $this->container->get('state')->set('system.maintenance_mode', TRUE);
230 $this->drupalGetWithFormat('test/serialize/entity', 'json');
231 // Verify that the endpoint is unavailable for anonymous users.
232 $this->assertResponse(503);
236 * Sets up a request on the request stack with a specified format.
238 * @param string $format
239 * The new request format.
241 protected function addRequestWithFormat($format) {
242 $request = \Drupal::request();
243 $request = clone $request;
244 $request->setRequestFormat($format);
246 \Drupal::requestStack()->push($request);
250 * Tests REST export with views render caching enabled.
252 public function testRestRenderCaching() {
253 $this->drupalLogin($this->adminUser);
254 /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
255 $render_cache = \Drupal::service('render_cache');
257 // Enable render caching for the views.
258 /** @var \Drupal\views\ViewEntityInterface $storage */
259 $storage = View::load('test_serializer_display_entity');
260 $options = &$storage->getDisplay('default');
261 $options['display_options']['cache'] = [
266 $original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
268 // Ensure that there is no corresponding render cache item yet.
269 $original['#cache'] += ['contexts' => []];
270 $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
273 'config:views.view.test_serializer_display_entity',
287 'entity_test_view_grants',
288 'languages:language_interface',
293 $this->assertFalse($render_cache->get($original));
295 // Request the page, once in XML and once in JSON to ensure that the caching
297 $result1 = $this->drupalGetJSON('test/serialize/entity');
298 $this->addRequestWithFormat('json');
299 $this->assertHeader('content-type', 'application/json');
300 $this->assertCacheContexts($cache_contexts);
301 $this->assertCacheTags($cache_tags);
302 $this->assertTrue($render_cache->get($original));
304 $result_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
305 $this->addRequestWithFormat('xml');
306 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
307 $this->assertCacheContexts($cache_contexts);
308 $this->assertCacheTags($cache_tags);
309 $this->assertTrue($render_cache->get($original));
311 // Ensure that the XML output is different from the JSON one.
312 $this->assertNotEqual($result1, $result_xml);
314 // Ensure that the cached page works.
315 $result2 = $this->drupalGetJSON('test/serialize/entity');
316 $this->addRequestWithFormat('json');
317 $this->assertHeader('content-type', 'application/json');
318 $this->assertEqual($result2, $result1);
319 $this->assertCacheContexts($cache_contexts);
320 $this->assertCacheTags($cache_tags);
321 $this->assertTrue($render_cache->get($original));
323 // Create a new entity and ensure that the cache tags are taken over.
324 EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
325 $result3 = $this->drupalGetJSON('test/serialize/entity');
326 $this->addRequestWithFormat('json');
327 $this->assertHeader('content-type', 'application/json');
328 $this->assertNotEqual($result3, $result2);
330 // Add the new entity cache tag and remove the first one, because we just
331 // show 10 items in total.
332 $cache_tags[] = 'entity_test:11';
333 unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
335 $this->assertCacheContexts($cache_contexts);
336 $this->assertCacheTags($cache_tags);
337 $this->assertTrue($render_cache->get($original));
341 * Tests the response format configuration.
343 public function testResponseFormatConfiguration() {
344 $this->drupalLogin($this->adminUser);
346 $style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
348 // Select only 'xml' as an accepted format.
349 $this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply'));
350 $this->drupalPostForm(NULL, [], t('Save'));
352 // Should return a 406.
353 $this->drupalGetWithFormat('test/serialize/field', 'json');
354 $this->assertHeader('content-type', 'application/json');
355 $this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
356 // Should return a 200.
357 $this->drupalGetWithFormat('test/serialize/field', 'xml');
358 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
359 $this->assertResponse(200, 'A 200 response was returned when XML was requested.');
361 // Add 'json' as an accepted format, so we have multiple.
362 $this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply'));
363 $this->drupalPostForm(NULL, [], t('Save'));
365 // Should return a 200.
366 // @todo This should be fixed when we have better content negotiation.
367 $this->drupalGet('test/serialize/field');
368 $this->assertHeader('content-type', 'application/json');
369 $this->assertResponse(200, 'A 200 response was returned when any format was requested.');
371 // Should return a 200. Emulates a sample Firefox header.
372 $this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
373 $this->assertHeader('content-type', 'application/json');
374 $this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
376 // Should return a 200.
377 $this->drupalGetWithFormat('test/serialize/field', 'json');
378 $this->assertHeader('content-type', 'application/json');
379 $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
380 $headers = $this->drupalGetHeaders();
381 $this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
382 // Should return a 200.
383 $this->drupalGetWithFormat('test/serialize/field', 'xml');
384 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
385 $this->assertResponse(200, 'A 200 response was returned when XML was requested');
386 $headers = $this->drupalGetHeaders();
387 $this->assertTrue(strpos($headers['content-type'], 'text/xml') !== FALSE, 'The header Content-type is correct.');
388 // Should return a 406.
389 $this->drupalGetWithFormat('test/serialize/field', 'html');
390 // We want to show the first format by default, see
391 // \Drupal\rest\Plugin\views\style\Serializer::render.
392 $this->assertHeader('content-type', 'application/json');
393 $this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
395 // Now configure now format, so all of them should be allowed.
396 $this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply'));
398 // Should return a 200.
399 $this->drupalGetWithFormat('test/serialize/field', 'json');
400 $this->assertHeader('content-type', 'application/json');
401 $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
402 // Should return a 200.
403 $this->drupalGetWithFormat('test/serialize/field', 'xml');
404 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
405 $this->assertResponse(200, 'A 200 response was returned when XML was requested');
406 // Should return a 200.
407 $this->drupalGetWithFormat('test/serialize/field', 'html');
408 // We want to show the first format by default, see
409 // \Drupal\rest\Plugin\views\style\Serializer::render.
410 $this->assertHeader('content-type', 'application/json');
411 $this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
415 * Test the field ID alias functionality of the DataFieldRow plugin.
417 public function testUIFieldAlias() {
418 $this->drupalLogin($this->adminUser);
420 // Test the UI settings for adding field ID aliases.
421 $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
422 $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
423 $this->assertLinkByHref($row_options);
425 // Test an empty string for an alias, this should not be used. This also
426 // tests that the form can be submitted with no aliases.
427 $this->drupalPostForm($row_options, ['row_options[field_options][name][alias]' => ''], t('Apply'));
428 $this->drupalPostForm(NULL, [], t('Save'));
430 $view = Views::getView('test_serializer_display_field');
431 $view->setDisplay('rest_export_1');
432 $this->executeView($view);
435 foreach ($view->result as $row) {
437 foreach ($view->field as $id => $field) {
438 $expected_row[$id] = $field->render($row);
440 $expected[] = $expected_row;
443 $this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
445 // Test a random aliases for fields, they should be replaced.
447 'name' => $this->randomMachineName(),
448 // Use # to produce an invalid character for the validation.
449 'nothing' => '#' . $this->randomMachineName(),
450 'created' => 'created',
453 $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
454 $this->drupalPostForm($row_options, $edit, t('Apply'));
455 $this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
457 // Change the map alias value to a valid one.
458 $alias_map['nothing'] = $this->randomMachineName();
460 $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
461 $this->drupalPostForm($row_options, $edit, t('Apply'));
463 $this->drupalPostForm(NULL, [], t('Save'));
465 $view = Views::getView('test_serializer_display_field');
466 $view->setDisplay('rest_export_1');
467 $this->executeView($view);
470 foreach ($view->result as $row) {
472 foreach ($view->field as $id => $field) {
473 $expected_row[$alias_map[$id]] = $field->render($row);
475 $expected[] = $expected_row;
478 $this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
482 * Tests the raw output options for row field rendering.
484 public function testFieldRawOutput() {
485 $this->drupalLogin($this->adminUser);
487 // Test the UI settings for adding field ID aliases.
488 $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
489 $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
490 $this->assertLinkByHref($row_options);
492 // Test an empty string for an alias, this should not be used. This also
493 // tests that the form can be submitted with no aliases.
495 'row_options[field_options][created][raw_output]' => '1',
496 'row_options[field_options][name][raw_output]' => '1',
498 $this->drupalPostForm($row_options, $values, t('Apply'));
499 $this->drupalPostForm(NULL, [], t('Save'));
501 $view = Views::getView('test_serializer_display_field');
502 $view->setDisplay('rest_export_1');
503 $this->executeView($view);
505 $storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
507 // Update the name for each to include a script tag.
508 foreach ($storage->loadMultiple() as $entity_test) {
509 $name = $entity_test->name->value;
510 $entity_test->set('name', "<script>$name</script>");
511 $entity_test->save();
514 // Just test the raw 'created' value against each row.
515 foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
516 $this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
517 $this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
520 // Test result with an excluded field.
521 $view->setDisplay('rest_export_1');
522 $view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
525 'table' => 'views_test_data',
527 'relationship' => 'none',
532 'table' => 'views_test_data',
533 'field' => 'created',
534 'relationship' => 'none',
538 $this->executeView($view);
539 foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
540 $this->assertTrue(!isset($values['created']), 'Excluded value not found.');
542 // Test that the excluded field is not shown in the row options.
543 $this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
544 $this->assertNoText('created');
548 * Tests the live preview output for json output.
550 public function testLivePreview() {
551 // We set up a request so it looks like an request in the live preview.
552 $request = new Request();
553 $request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
554 /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
555 $request_stack = \Drupal::service('request_stack');
556 $request_stack->push($request);
558 $view = Views::getView('test_serializer_display_entity');
559 $view->setDisplay('rest_export_1');
560 $this->executeView($view);
562 // Get the serializer service.
563 $serializer = $this->container->get('serializer');
566 foreach ($view->result as $row) {
567 $entities[] = $row->_entity;
570 $expected = $serializer->serialize($entities, 'json');
572 $view->live_preview = TRUE;
574 $build = $view->preview();
575 $rendered_json = $build['#plain_text'];
576 $this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
579 $expected = $serializer->serialize($entities, 'xml');
581 // Change the request format to xml.
582 $view->setDisplay('rest_export_1');
583 $view->getDisplay()->setOption('style', [
584 'type' => 'serializer',
586 'uses_fields' => FALSE,
593 $this->executeView($view);
594 $build = $view->preview();
595 $rendered_xml = $build['#plain_text'];
596 $this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
600 * Tests the views interface for REST export displays.
602 public function testSerializerViewsUI() {
603 $this->drupalLogin($this->adminUser);
604 // Click the "Update preview button".
605 $this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = [], t('Update preview'));
606 $this->assertResponse(200);
607 // Check if we receive the expected result.
608 $result = $this->xpath('//div[@id="views-live-preview"]/pre');
609 $this->assertIdentical($this->drupalGet('test/serialize/field'), (string) $result[0], 'The expected JSON preview output was found.');
613 * Tests the field row style using fieldapi fields.
615 public function testFieldapiField() {
616 $this->drupalCreateContentType(['type' => 'page']);
617 $node = $this->drupalCreateNode();
619 $result = $this->drupalGetJSON('test/serialize/node-field');
620 $this->assertEqual($result[0]['nid'], $node->id());
621 $this->assertEqual($result[0]['body'], $node->body->processed);
623 // Make sure that serialized fields are not exposed to XSS.
624 $node = $this->drupalCreateNode();
626 'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
627 'format' => filter_default_format(),
630 $result = $this->drupalGetJSON('test/serialize/node-field');
631 $this->assertEqual($result[1]['nid'], $node->id());
632 $this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
634 $this->drupalLogin($this->adminUser);
636 // Add an alias and make the output raw.
637 $row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
639 // Test an empty string for an alias, this should not be used. This also
640 // tests that the form can be submitted with no aliases.
641 $this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
642 $this->drupalPostForm(NULL, [], t('Save'));
644 $view = Views::getView('test_serializer_node_display_field');
645 $view->setDisplay('rest_export_1');
646 $this->executeView($view);
648 // Test the raw 'created' value against each row.
649 foreach ($this->drupalGetJSON('test/serialize/node-field') as $index => $values) {
650 $this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
653 // Test that multiple raw body fields are shown.
654 // Make the body field unlimited cardinatlity.
655 $storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
656 $storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
657 $storage_definition->save();
659 $this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
660 $this->drupalPostForm(NULL, [], t('Save'));
662 $node = $this->drupalCreateNode();
665 'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
666 'format' => filter_default_format(),
668 // Add two body items.
669 $node->body = [$body, $body];
672 $view = Views::getView('test_serializer_node_display_field');
673 $view->setDisplay('rest_export_1');
674 $this->executeView($view);
676 $result = $this->drupalGetJSON('test/serialize/node-field');
677 $this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
678 $this->assertEqual($result[2]['body'], array_map(function($item) { return $item['value']; }, $node->body->getValue()), 'Expected raw body values found.');
682 * Tests the "Grouped rows" functionality.
684 public function testGroupRows() {
685 /** @var \Drupal\Core\Render\RendererInterface $renderer */
686 $renderer = $this->container->get('renderer');
687 $this->drupalCreateContentType(['type' => 'page']);
688 // Create a text field with cardinality set to unlimited.
689 $field_name = 'field_group_rows';
690 $field_storage = FieldStorageConfig::create([
691 'field_name' => $field_name,
692 'entity_type' => 'node',
694 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
696 $field_storage->save();
697 // Create an instance of the text field on the content type.
698 $field = FieldConfig::create([
699 'field_storage' => $field_storage,
703 $grouped_field_values = ['a', 'b', 'c'];
705 'title' => $this->randomMachineName(),
706 $field_name => $grouped_field_values,
708 $this->drupalCreateNode($edit);
709 $view = Views::getView('test_serializer_node_display_field');
710 $view->setDisplay('rest_export_1');
711 // Override the view's fields to include the field_group_rows field, set the
712 // group_rows setting to true.
716 'table' => 'node__' . $field_name,
717 'field' => $field_name,
719 'group_rows' => TRUE,
722 $view->displayHandlers->get('default')->overrideOption('fields', $fields);
723 $build = $view->preview();
724 // Get the serializer service.
725 $serializer = $this->container->get('serializer');
726 // Check if the field_group_rows field is grouped.
728 $expected[] = [$field_name => implode(', ', $grouped_field_values)];
729 $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
730 // Set the group rows setting to false.
731 $view = Views::getView('test_serializer_node_display_field');
732 $view->setDisplay('rest_export_1');
733 $fields[$field_name]['group_rows'] = FALSE;
734 $view->displayHandlers->get('default')->overrideOption('fields', $fields);
735 $build = $view->preview();
736 // Check if the field_group_rows field is ungrouped and displayed per row.
738 foreach ($grouped_field_values as $grouped_field_value) {
739 $expected[] = [$field_name => $grouped_field_value];
741 $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
745 * Tests the exposed filter works.
747 * There is an exposed filter on the title field which takes a title query
748 * parameter. This is set to filter nodes by those whose title starts with
749 * the value provided.
751 public function testRestViewExposedFilter() {
752 $this->drupalCreateContentType(['type' => 'page']);
753 $node0 = $this->drupalCreateNode(['title' => 'Node 1']);
754 $node1 = $this->drupalCreateNode(['title' => 'Node 11']);
755 $node2 = $this->drupalCreateNode(['title' => 'Node 111']);
757 // Test that no filter brings back all three nodes.
758 $result = $this->drupalGetJSON('test/serialize/node-exposed-filter');
762 'nid' => $node0->id(),
763 'body' => $node0->body->processed,
766 'nid' => $node1->id(),
767 'body' => $node1->body->processed,
770 'nid' => $node2->id(),
771 'body' => $node2->body->processed,
775 $this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
777 // Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
778 $result = $this->drupalGetJSON('test/serialize/node-exposed-filter', ['query' => ['title' => 'Node 11']]);
782 'nid' => $node1->id(),
783 'body' => $node1->body->processed,
786 'nid' => $node2->id(),
787 'body' => $node2->body->processed,
792 'languages:language_content',
793 'languages:language_interface',
796 'user.node_grants:view',
800 $this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
801 $this->assertCacheContexts($cache_contexts);
805 * Test multilingual entity rows.
807 public function testMulEntityRows() {
808 // Create some languages.
809 ConfigurableLanguage::createFromLangcode('l1')->save();
810 ConfigurableLanguage::createFromLangcode('l2')->save();
812 // Create an entity with no translations.
813 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
814 $storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
816 // Create some entities with translations.
817 $entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
819 $entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
820 $entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
822 $entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
824 // Get the names of the output.
825 $json = $this->drupalGetWithFormat('test/serialize/translated_entity', 'json');
826 $decoded = $this->container->get('serializer')->decode($json, 'hal_json');
828 foreach ($decoded as $item) {
829 $names[] = $item['name'][0]['value'];
833 // Check that the names are correct.
834 $expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
835 $this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');