3 namespace Drupal\Tests\rest\Functional\Views;
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;
22 * Tests the serializer style plugin.
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
30 class StyleSerializerTest extends ViewTestBase {
32 use AssertPageCacheContextsAndTagsTrait;
39 public static $modules = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth'];
42 * Views used by this test.
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', 'test_serializer_shared_path'];
49 * A user with administrative privileges to look at test entity and configure views.
53 protected function setUp($import_test_views = TRUE) {
54 parent::setUp($import_test_views);
56 ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
58 $this->adminUser = $this->drupalCreateUser(['administer views', 'administer entity_test content', 'access user profiles', 'view test entity']);
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();
65 $this->enableViewsTestModule();
69 * Checks that the auth options restricts access to a REST views display.
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);
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();
83 // But if we use the basic auth authentication strategy, we should be able
85 $url = $this->buildUrl('test/serialize/auth_with_perm');
86 $response = \Drupal::httpClient()->get($url, [
87 'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
93 // Ensure that any changes to variables in the other thread are picked up.
94 $this->refreshVariables();
96 $this->assertResponse(200);
100 * Checks the behavior of the Serializer callback paths and row plugins.
102 public function testSerializerResponses() {
103 // Test the serialize callback.
104 $view = Views::getView('test_serializer_display_field');
105 $view->initDisplay();
106 $this->executeView($view);
108 $actual_json = $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
109 $this->assertResponse(200);
110 $this->assertCacheTags($view->getCacheTags());
111 $this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
112 // @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
113 // propagation of cache max-age.
115 // Test the http Content-type.
116 $headers = $this->drupalGetHeaders();
117 $this->assertSame(['application/json'], $headers['Content-Type']);
120 foreach ($view->result as $row) {
122 foreach ($view->field as $id => $field) {
123 $expected_row[$id] = $field->render($row);
125 $expected[] = $expected_row;
128 $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
130 // Test that the rendered output and the preview output are the same.
132 $view->setDisplay('rest_export_1');
133 // Mock the request content type by setting it on the display handler.
134 $view->display_handler->setContentType('json');
135 $output = $view->preview();
136 $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
138 // Test a 403 callback.
139 $this->drupalGet('test/serialize/denied', ['query' => ['_format' => 'json']]);
140 $this->assertResponse(403);
142 // Test the entity rows.
143 $view = Views::getView('test_serializer_display_entity');
144 $view->initDisplay();
145 $this->executeView($view);
147 // Get the serializer service.
148 $serializer = $this->container->get('serializer');
151 foreach ($view->result as $row) {
152 $entities[] = $row->_entity;
155 $expected = $serializer->serialize($entities, 'json');
157 $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
158 $this->assertResponse(200);
159 $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
160 $expected_cache_tags = $view->getCacheTags();
161 $expected_cache_tags[] = 'entity_test_list';
162 /** @var \Drupal\Core\Entity\EntityInterface $entity */
163 foreach ($entities as $entity) {
164 $expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
166 $this->assertCacheTags($expected_cache_tags);
167 $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
169 $expected = $serializer->serialize($entities, 'hal_json');
170 $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'hal_json']]);
171 $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
172 $this->assertCacheTags($expected_cache_tags);
174 // Change the format to xml.
175 $view->setDisplay('rest_export_1');
176 $view->getDisplay()->setOption('style', [
177 'type' => 'serializer',
179 'uses_fields' => FALSE,
186 $expected = $serializer->serialize($entities, 'xml');
187 $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
188 $this->assertSame(trim($expected), $actual_xml);
189 $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
191 // Allow multiple formats.
192 $view->setDisplay('rest_export_1');
193 $view->getDisplay()->setOption('style', [
194 'type' => 'serializer',
196 'uses_fields' => FALSE,
204 $expected = $serializer->serialize($entities, 'json');
205 $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
206 $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
207 $expected = $serializer->serialize($entities, 'xml');
208 $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
209 $this->assertSame(trim($expected), $actual_xml);
213 * Verifies REST export views work on the same path as a page display.
215 public function testSharedPagePath() {
216 // Test with no format as well as html explicitly.
217 $this->drupalGet('test/serialize/shared');
218 $this->assertResponse(200);
219 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
221 $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'html']]);
222 $this->assertResponse(200);
223 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
225 $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'json']]);
226 $this->assertResponse(200);
227 $this->assertHeader('content-type', 'application/json');
229 $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'xml']]);
230 $this->assertResponse(200);
231 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
235 * Verifies site maintenance mode functionality.
237 public function testSiteMaintenance() {
238 $view = Views::getView('test_serializer_display_field');
239 $view->initDisplay();
240 $this->executeView($view);
242 // Set the site to maintenance mode.
243 $this->container->get('state')->set('system.maintenance_mode', TRUE);
245 $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
246 // Verify that the endpoint is unavailable for anonymous users.
247 $this->assertResponse(503);
251 * Sets up a request on the request stack with a specified format.
253 * @param string $format
254 * The new request format.
256 protected function addRequestWithFormat($format) {
257 $request = \Drupal::request();
258 $request = clone $request;
259 $request->setRequestFormat($format);
261 \Drupal::requestStack()->push($request);
265 * Tests REST export with views render caching enabled.
267 public function testRestRenderCaching() {
268 $this->drupalLogin($this->adminUser);
269 /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
270 $render_cache = \Drupal::service('render_cache');
272 // Enable render caching for the views.
273 /** @var \Drupal\views\ViewEntityInterface $storage */
274 $storage = View::load('test_serializer_display_entity');
275 $options = &$storage->getDisplay('default');
276 $options['display_options']['cache'] = [
281 $original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
283 // Ensure that there is no corresponding render cache item yet.
284 $original['#cache'] += ['contexts' => []];
285 $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
288 'config:views.view.test_serializer_display_entity',
302 'entity_test_view_grants',
303 'languages:language_interface',
308 $this->assertFalse($render_cache->get($original));
310 // Request the page, once in XML and once in JSON to ensure that the caching
312 $result1 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
313 $this->addRequestWithFormat('json');
314 $this->assertHeader('content-type', 'application/json');
315 $this->assertCacheContexts($cache_contexts);
316 $this->assertCacheTags($cache_tags);
317 $this->assertTrue($render_cache->get($original));
319 $result_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
320 $this->addRequestWithFormat('xml');
321 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
322 $this->assertCacheContexts($cache_contexts);
323 $this->assertCacheTags($cache_tags);
324 $this->assertTrue($render_cache->get($original));
326 // Ensure that the XML output is different from the JSON one.
327 $this->assertNotEqual($result1, $result_xml);
329 // Ensure that the cached page works.
330 $result2 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
331 $this->addRequestWithFormat('json');
332 $this->assertHeader('content-type', 'application/json');
333 $this->assertEqual($result2, $result1);
334 $this->assertCacheContexts($cache_contexts);
335 $this->assertCacheTags($cache_tags);
336 $this->assertTrue($render_cache->get($original));
338 // Create a new entity and ensure that the cache tags are taken over.
339 EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
340 $result3 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
341 $this->addRequestWithFormat('json');
342 $this->assertHeader('content-type', 'application/json');
343 $this->assertNotEqual($result3, $result2);
345 // Add the new entity cache tag and remove the first one, because we just
346 // show 10 items in total.
347 $cache_tags[] = 'entity_test:11';
348 unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
350 $this->assertCacheContexts($cache_contexts);
351 $this->assertCacheTags($cache_tags);
352 $this->assertTrue($render_cache->get($original));
356 * Tests the response format configuration.
358 public function testResponseFormatConfiguration() {
359 $this->drupalLogin($this->adminUser);
361 $style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
363 // Test with no format.
364 $this->drupalGet('test/serialize/field');
365 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
366 $this->assertResponse(406, 'A 406 response was returned when no format was requested.');
368 // Select only 'xml' as an accepted format.
369 $this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply'));
370 $this->drupalPostForm(NULL, [], t('Save'));
372 // Should return a 406.
373 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
374 $this->assertHeader('content-type', 'application/json');
375 $this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
376 // Should return a 200.
377 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
378 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
379 $this->assertResponse(200, 'A 200 response was returned when XML was requested.');
381 // Add 'json' as an accepted format, so we have multiple.
382 $this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply'));
383 $this->drupalPostForm(NULL, [], t('Save'));
385 // Should return a 406. Emulates a sample Firefox header.
386 $this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
387 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
388 $this->assertResponse(406, 'A 406 response was returned when a browser accept header was requested.');
390 // Should return a 406.
391 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
392 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
393 $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
395 // Should return a 200.
396 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
397 $this->assertHeader('content-type', 'application/json');
398 $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
400 // Should return a 200.
401 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
402 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
403 $this->assertResponse(200, 'A 200 response was returned when XML was requested');
405 // Now configure no format, so both serialization formats should be allowed.
406 $this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply'));
408 // Should return a 200.
409 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
410 $this->assertHeader('content-type', 'application/json');
411 $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
412 // Should return a 200.
413 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
414 $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
415 $this->assertResponse(200, 'A 200 response was returned when XML was requested');
417 // Should return a 406 for HTML still.
418 $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
419 $this->assertHeader('content-type', 'text/html; charset=UTF-8');
420 $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
424 * Test the field ID alias functionality of the DataFieldRow plugin.
426 public function testUIFieldAlias() {
427 $this->drupalLogin($this->adminUser);
429 // Test the UI settings for adding field ID aliases.
430 $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
431 $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
432 $this->assertLinkByHref($row_options);
434 // Test an empty string for an alias, this should not be used. This also
435 // tests that the form can be submitted with no aliases.
436 $this->drupalPostForm($row_options, ['row_options[field_options][name][alias]' => ''], t('Apply'));
437 $this->drupalPostForm(NULL, [], t('Save'));
439 $view = Views::getView('test_serializer_display_field');
440 $view->setDisplay('rest_export_1');
441 $this->executeView($view);
444 foreach ($view->result as $row) {
446 foreach ($view->field as $id => $field) {
447 $expected_row[$id] = $field->render($row);
449 $expected[] = $expected_row;
452 $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
454 // Test a random aliases for fields, they should be replaced.
456 'name' => $this->randomMachineName(),
457 // Use # to produce an invalid character for the validation.
458 'nothing' => '#' . $this->randomMachineName(),
459 'created' => 'created',
462 $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
463 $this->drupalPostForm($row_options, $edit, t('Apply'));
464 $this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
466 // Change the map alias value to a valid one.
467 $alias_map['nothing'] = $this->randomMachineName();
469 $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
470 $this->drupalPostForm($row_options, $edit, t('Apply'));
472 $this->drupalPostForm(NULL, [], t('Save'));
474 $view = Views::getView('test_serializer_display_field');
475 $view->setDisplay('rest_export_1');
476 $this->executeView($view);
479 foreach ($view->result as $row) {
481 foreach ($view->field as $id => $field) {
482 $expected_row[$alias_map[$id]] = $field->render($row);
484 $expected[] = $expected_row;
487 $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
491 * Tests the raw output options for row field rendering.
493 public function testFieldRawOutput() {
494 $this->drupalLogin($this->adminUser);
496 // Test the UI settings for adding field ID aliases.
497 $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
498 $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
499 $this->assertLinkByHref($row_options);
501 // Test an empty string for an alias, this should not be used. This also
502 // tests that the form can be submitted with no aliases.
504 'row_options[field_options][created][raw_output]' => '1',
505 'row_options[field_options][name][raw_output]' => '1',
507 $this->drupalPostForm($row_options, $values, t('Apply'));
508 $this->drupalPostForm(NULL, [], t('Save'));
510 $view = Views::getView('test_serializer_display_field');
511 $view->setDisplay('rest_export_1');
512 $this->executeView($view);
514 $storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
516 // Update the name for each to include a script tag.
517 foreach ($storage->loadMultiple() as $entity_test) {
518 $name = $entity_test->name->value;
519 $entity_test->set('name', "<script>$name</script>");
520 $entity_test->save();
523 // Just test the raw 'created' value against each row.
524 foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
525 $this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
526 $this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
529 // Test result with an excluded field.
530 $view->setDisplay('rest_export_1');
531 $view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
534 'table' => 'views_test_data',
536 'relationship' => 'none',
541 'table' => 'views_test_data',
542 'field' => 'created',
543 'relationship' => 'none',
547 $this->executeView($view);
548 foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
549 $this->assertTrue(!isset($values['created']), 'Excluded value not found.');
551 // Test that the excluded field is not shown in the row options.
552 $this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
553 $this->assertNoText('created');
557 * Tests the live preview output for json output.
559 public function testLivePreview() {
560 // We set up a request so it looks like an request in the live preview.
561 $request = new Request();
562 $request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
563 /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
564 $request_stack = \Drupal::service('request_stack');
565 $request_stack->push($request);
567 $view = Views::getView('test_serializer_display_entity');
568 $view->setDisplay('rest_export_1');
569 $this->executeView($view);
571 // Get the serializer service.
572 $serializer = $this->container->get('serializer');
575 foreach ($view->result as $row) {
576 $entities[] = $row->_entity;
579 $expected = $serializer->serialize($entities, 'json');
581 $view->live_preview = TRUE;
583 $build = $view->preview();
584 $rendered_json = $build['#plain_text'];
585 $this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
588 $expected = $serializer->serialize($entities, 'xml');
590 // Change the request format to xml.
591 $view->setDisplay('rest_export_1');
592 $view->getDisplay()->setOption('style', [
593 'type' => 'serializer',
595 'uses_fields' => FALSE,
602 $this->executeView($view);
603 $build = $view->preview();
604 $rendered_xml = $build['#plain_text'];
605 $this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
609 * Tests the views interface for REST export displays.
611 public function testSerializerViewsUI() {
612 $this->drupalLogin($this->adminUser);
613 // Click the "Update preview button".
614 $this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = [], t('Update preview'));
615 $this->assertResponse(200);
616 // Check if we receive the expected result.
617 $result = $this->xpath('//div[@id="views-live-preview"]/pre');
618 $json_preview = $result[0]->getText();
619 $this->assertSame($json_preview, $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]), 'The expected JSON preview output was found.');
623 * Tests the field row style using fieldapi fields.
625 public function testFieldapiField() {
626 $this->drupalCreateContentType(['type' => 'page']);
627 $node = $this->drupalCreateNode();
629 $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
630 $this->assertEqual($result[0]['nid'], $node->id());
631 $this->assertEqual($result[0]['body'], $node->body->processed);
633 // Make sure that serialized fields are not exposed to XSS.
634 $node = $this->drupalCreateNode();
636 'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
637 'format' => filter_default_format(),
640 $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
641 $this->assertEqual($result[1]['nid'], $node->id());
642 $this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
644 $this->drupalLogin($this->adminUser);
646 // Add an alias and make the output raw.
647 $row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
649 // Test an empty string for an alias, this should not be used. This also
650 // tests that the form can be submitted with no aliases.
651 $this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
652 $this->drupalPostForm(NULL, [], t('Save'));
654 $view = Views::getView('test_serializer_node_display_field');
655 $view->setDisplay('rest_export_1');
656 $this->executeView($view);
658 // Test the raw 'created' value against each row.
659 foreach (Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']])) as $index => $values) {
660 $this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
663 // Test that multiple raw body fields are shown.
664 // Make the body field unlimited cardinatlity.
665 $storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
666 $storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
667 $storage_definition->save();
669 $this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
670 $this->drupalPostForm(NULL, [], t('Save'));
672 $node = $this->drupalCreateNode();
675 'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
676 'format' => filter_default_format(),
678 // Add two body items.
679 $node->body = [$body, $body];
682 $view = Views::getView('test_serializer_node_display_field');
683 $view->setDisplay('rest_export_1');
684 $this->executeView($view);
686 $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
687 $this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
688 $this->assertEqual($result[2]['body'], array_map(function ($item) {
689 return $item['value'];
690 }, $node->body->getValue()), 'Expected raw body values found.');
694 * Tests the "Grouped rows" functionality.
696 public function testGroupRows() {
697 /** @var \Drupal\Core\Render\RendererInterface $renderer */
698 $renderer = $this->container->get('renderer');
699 $this->drupalCreateContentType(['type' => 'page']);
700 // Create a text field with cardinality set to unlimited.
701 $field_name = 'field_group_rows';
702 $field_storage = FieldStorageConfig::create([
703 'field_name' => $field_name,
704 'entity_type' => 'node',
706 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
708 $field_storage->save();
709 // Create an instance of the text field on the content type.
710 $field = FieldConfig::create([
711 'field_storage' => $field_storage,
715 $grouped_field_values = ['a', 'b', 'c'];
717 'title' => $this->randomMachineName(),
718 $field_name => $grouped_field_values,
720 $this->drupalCreateNode($edit);
721 $view = Views::getView('test_serializer_node_display_field');
722 $view->setDisplay('rest_export_1');
723 // Override the view's fields to include the field_group_rows field, set the
724 // group_rows setting to true.
728 'table' => 'node__' . $field_name,
729 'field' => $field_name,
731 'group_rows' => TRUE,
734 $view->displayHandlers->get('default')->overrideOption('fields', $fields);
735 $build = $view->preview();
736 // Get the serializer service.
737 $serializer = $this->container->get('serializer');
738 // Check if the field_group_rows field is grouped.
740 $expected[] = [$field_name => implode(', ', $grouped_field_values)];
741 $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
742 // Set the group rows setting to false.
743 $view = Views::getView('test_serializer_node_display_field');
744 $view->setDisplay('rest_export_1');
745 $fields[$field_name]['group_rows'] = FALSE;
746 $view->displayHandlers->get('default')->overrideOption('fields', $fields);
747 $build = $view->preview();
748 // Check if the field_group_rows field is ungrouped and displayed per row.
750 foreach ($grouped_field_values as $grouped_field_value) {
751 $expected[] = [$field_name => $grouped_field_value];
753 $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
757 * Tests the exposed filter works.
759 * There is an exposed filter on the title field which takes a title query
760 * parameter. This is set to filter nodes by those whose title starts with
761 * the value provided.
763 public function testRestViewExposedFilter() {
764 $this->drupalCreateContentType(['type' => 'page']);
765 $node0 = $this->drupalCreateNode(['title' => 'Node 1']);
766 $node1 = $this->drupalCreateNode(['title' => 'Node 11']);
767 $node2 = $this->drupalCreateNode(['title' => 'Node 111']);
769 // Test that no filter brings back all three nodes.
770 $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json']]));
774 'nid' => $node0->id(),
775 'body' => $node0->body->processed,
778 'nid' => $node1->id(),
779 'body' => $node1->body->processed,
782 'nid' => $node2->id(),
783 'body' => $node2->body->processed,
787 $this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
789 // Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
790 $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json', 'title' => 'Node 11']]));
794 'nid' => $node1->id(),
795 'body' => $node1->body->processed,
798 'nid' => $node2->id(),
799 'body' => $node2->body->processed,
804 'languages:language_content',
805 'languages:language_interface',
808 'user.node_grants:view',
812 $this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
813 $this->assertCacheContexts($cache_contexts);
817 * Test multilingual entity rows.
819 public function testMulEntityRows() {
820 // Create some languages.
821 ConfigurableLanguage::createFromLangcode('l1')->save();
822 ConfigurableLanguage::createFromLangcode('l2')->save();
824 // Create an entity with no translations.
825 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
826 $storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
828 // Create some entities with translations.
829 $entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
831 $entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
832 $entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
834 $entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
836 // Get the names of the output.
837 $json = $this->drupalGet('test/serialize/translated_entity', ['query' => ['_format' => 'json']]);
838 $decoded = $this->container->get('serializer')->decode($json, 'hal_json');
840 foreach ($decoded as $item) {
841 $names[] = $item['name'][0]['value'];
845 // Check that the names are correct.
846 $expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
847 $this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');