lupus_decoupled-1.x-dev/modules/lupus_decoupled_views/tests/src/Functional/Views/CustomElementsViewsDisplaysTest.php
modules/lupus_decoupled_views/tests/src/Functional/Views/CustomElementsViewsDisplaysTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\lupus_decoupled_views\Functional\Views;
use Drupal\custom_elements\CustomElement;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Views;
use Drupal\Core\Render\RenderContext;
use Drupal\views\Plugin\Block\ViewsBlock;
/**
* Tests the custom elements views display plugins.
*
* @group lupus_decoupled_views
*/
class CustomElementsViewsDisplaysTest extends ViewTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'node',
'views',
'file',
'menu_link_content',
'custom_elements',
'lupus_ce_renderer',
'lupus_decoupled',
'lupus_decoupled_ce_api',
'lupus_decoupled_views',
'lupus_decoupled_views_test',
'block',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static array $testViews = ['custom_elements_view_test'];
/**
* Nodes to use during this test.
*
* @var array
*/
protected $nodes = [];
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['lupus_decoupled_views_test']): void {
parent::setUp($import_test_views, $modules);
$this->renderer = $this->container->get('renderer');
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Create test nodes with unique titles and creation dates (for sorting).
$node = [
'title' => 'Test node 1',
'status' => TRUE,
'published' => TRUE,
'type' => 'page',
'created' => \Drupal::time()->getRequestTime(),
];
$this->nodes[] = $this->drupalCreateNode($node);
$node['title'] = 'Test node 2';
$node['created']--;
$this->nodes[] = $this->drupalCreateNode($node);
$node['title'] = 'Test node 3';
$node['created']--;
$this->nodes[] = $this->drupalCreateNode($node);
$node['title'] = 'Test article 1';
$node['created']--;
$node['type'] = 'article';
$this->nodes[] = $this->drupalCreateNode($node);
}
/**
* Test custom elements page view.
*/
public function testCustomElementsPageView(): void {
// Set config to use preview by default - API responses should still use
// 'markup' variant to ensure custom elements markup is generated.
\Drupal::configFactory()
->getEditable('custom_elements.settings')
->set('default_render_variant', 'preview:markup')
->save();
// Test json response.
$json_response = json_decode($this->drupalGet('ce-api/view/test-ce', ['query' => ['_content_format' => 'json']]), TRUE);
$this->assertSession()->statusCodeEquals(200);
$this->assertSame('CE view page', $json_response['title'] ?? []);
$this->assertSame('drupal-view-custom-elements-view-test-page', $json_response['content']['element'] ?? []);
$this->assertCount(4, $json_response['content']['slots']['rows'] ?? []);
$this->assertStringContainsString('Test node 1', $json_response['content']['slots']['rows'][0]['props']['title'] ?? '');
$this->assertStringContainsString('Test node 2', $json_response['content']['slots']['rows'][1]['props']['title'] ?? '');
$this->assertStringContainsString('Test node 3', $json_response['content']['slots']['rows'][2]['props']['title'] ?? '');
$this->assertStringContainsString('Test article', $json_response['content']['slots']['rows'][3]['props']['title'] ?? '');
$this->assertSame('custom_elements_page_1', $json_response['content']['props']['displayId'] ?? []);
$this->assertSame('custom_elements_view_test', $json_response['content']['props']['viewId'] ?? []);
// Verify rows_wrapper attribute is empty for default 'drupal-markup'.
$this->assertSame('', $json_response['content']['props']['rowsWrapper'] ?? 'not-set');
// Test custom element name override.
$view = Views::getView('custom_elements_view_test');
$view->setDisplay('custom_elements_page_1');
$view->initDisplay();
$view->display_handler->options['custom_element_name'] = 'my-custom-view';
$view->execute();
$output = $view->render();
$custom_element = $output['#custom_element'];
$this->assertEquals('my-custom-view', $custom_element->getTag(),
'Custom element name should be used when configured');
// Test markup response.
$markup_response = json_decode($this->drupalGet('ce-api/view/test-ce', ['query' => ['_content_format' => 'markup']]), TRUE);
$this->assertSession()->statusCodeEquals(200);
$this->assertSame('CE view page', $markup_response['title'] ?? '');
$this->assertStringContainsString('</drupal-view-custom-elements-view-test-page>', $markup_response['content'] ?? '');
$this->assertStringContainsString('Test node 3', $markup_response['content'] ?? '');
$this->assertStringContainsString('Test node 2', $markup_response['content'] ?? '');
$this->assertStringContainsString('Test node 1', $markup_response['content'] ?? '');
$this->assertStringContainsString('display-id="custom_elements_page_1"', $markup_response['content'] ?? '');
$this->assertStringContainsString('view-id="custom_elements_view_test"', $markup_response['content'] ?? '');
// Verify API responses use 'markup' variant despite preview config:
// Custom element markup should be present (not preview containers).
$this->assertStringNotContainsString('custom-elements-preview', $markup_response['content'] ?? '');
}
/**
* Tests the custom elements block display plugin directly.
*/
public function testCustomElementsBlockPlugin(): void {
// Get the block plugin manager.
$block_manager = $this->container->get('plugin.manager.block');
// Check if the test view exists and has the expected display.
$view = Views::getView('custom_elements_view_test');
$this->assertNotNull($view, 'Test view "custom_elements_view_test" should be loaded');
// Check available displays.
$view->setDisplay('custom_elements_block_1');
$display = $view->getDisplay();
$this->assertEquals('custom_elements_block', $display->getPluginId(), 'View should have the custom_elements_block_1 display');
// Create the block plugin instance directly with the correct ID.
$block_plugin_id = 'views_block:custom_elements_view_test-custom_elements_block_1';
$block_plugin = $block_manager->createInstance($block_plugin_id, [
'label' => 'Custom Elements Test Block',
'provider' => 'views',
'label_display' => 'visible',
'views_label' => 'Custom Block Title',
'items_per_page' => 3,
]);
$this->assertInstanceOf(ViewsBlock::class, $block_plugin);
// Build the block content.
$renderer = $this->renderer;
$build = $renderer->executeInRenderContext(new RenderContext(), function () use ($block_plugin) {
return $block_plugin->build();
});
// Check the basic render array structure with the custom element.
$this->assertNotEmpty($build);
$this->assertIsArray($build);
$this->assertArrayHasKey('#custom_element', $build);
// Verify the custom element's attributes.
$element = $build['#custom_element'];
assert($element instanceof CustomElement);
$this->assertEquals('drupal-view-custom-elements-view-test-block', $element->getTag());
$this->assertEquals('Custom Block Title', $element->getAttribute('title'));
$this->assertEquals('custom_elements_view_test', $element->getAttribute('view-id'));
$this->assertEquals('custom_elements_block_1', $element->getAttribute('display-id'));
// Verify rows_wrapper attribute is empty for default 'drupal-markup'.
$this->assertEquals('', $element->getAttribute('rows-wrapper'));
// Test with custom wrapper configured.
$view_custom = Views::getView('custom_elements_view_test');
$view_custom->setDisplay('custom_elements_block_1');
$view_custom->initDisplay();
$view_custom->initStyle();
$view_custom->style_plugin->options['rows_wrapper'] = 'HumanifyGrid';
$view_custom->execute();
$output_custom = $view_custom->render();
$element_custom = $output_custom['#custom_element'];
$this->assertEquals('HumanifyGrid', $element_custom->getAttribute('rows-wrapper'),
'Custom rows wrapper should be set as attribute');
// Test custom element name override.
$view_custom_name = Views::getView('custom_elements_view_test');
$view_custom_name->setDisplay('custom_elements_block_1');
$view_custom_name->initDisplay();
$view_custom_name->display_handler->options['custom_element_name'] = 'my-block-view';
$view_custom_name->execute();
$output_custom_name = $view_custom_name->render();
$element_custom_name = $output_custom_name['#custom_element'];
$this->assertEquals('my-block-view', $element_custom_name->getTag(),
'Custom element name should be used when configured');
// Verify the slot content.
$slot_data = $element->getSortedSlotsByName();
$this->assertIsArray($slot_data);
$this->assertArrayHasKey('rows', $slot_data);
$this->assertEquals('Test node 1', $slot_data['rows'][0]['content']->getAttribute('title'));
$this->assertEquals('Test node 2', $slot_data['rows'][1]['content']->getAttribute('title'));
$this->assertEquals('Test node 3', $slot_data['rows'][2]['content']->getAttribute('title'));
// Check once again on the rendered output of the block.
$this->assertEquals('custom_element', $build['#theme']);
$renderer = $this->renderer;
$output = $this->renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, $build) {
return $renderer->render($build);
});
$this->assertStringContainsString('drupal-view-custom-elements-view-test', (string) $output);
$this->assertStringContainsString('Custom Block Title', (string) $output);
$this->assertStringContainsString('Test node 1', (string) $output);
$this->assertStringContainsString('Test node 2', (string) $output);
$this->assertStringContainsString('Test node 3', (string) $output);
$this->assertStringNotContainsString('Test article 1', (string) $output);
// Verify the cacheability metadata.
$this->assertContains('node:' . $this->nodes[0]->id(), $element->getCacheTags());
$this->assertContains('node:' . $this->nodes[1]->id(), $element->getCacheTags());
$this->assertContains('node:' . $this->nodes[2]->id(), $element->getCacheTags());
$this->assertNotContains('node:' . $this->nodes[3]->id(), $element->getCacheTags());
// Verify the view's configuration title is taken when no the block does
// not override it.
$block_plugin = $block_manager->createInstance($block_plugin_id, [
'provider' => 'views',
'items_per_page' => 3,
]);
$element = $this->renderBlockPluginToCustomElement($block_plugin);
$this->assertEquals('CE view page', $element->getAttribute('title'));
// Verify "display title"/label_display option is respected.
$block_plugin = $block_manager->createInstance($block_plugin_id, [
'provider' => 'views',
'label_display' => FALSE,
'items_per_page' => 3,
]);
$element = $this->renderBlockPluginToCustomElement($block_plugin);
$this->assertEquals('', $element->getAttribute('title'));
// Test pager data.
$pager = $element->getAttribute('pager');
$this->assertIsArray($pager);
$this->assertEquals(2, $pager['total_pages']);
$this->assertEquals(0, $pager['current']);
// Test block with page=1.
$block_plugin = $block_manager->createInstance($block_plugin_id, [
'provider' => 'views',
'items_per_page' => 3,
]);
$block_plugin->getViewExecutable()->setCurrentPage(1);
$element = $this->renderBlockPluginToCustomElement($block_plugin);
$pager = $element->getAttribute('pager');
$this->assertIsArray($pager);
$this->assertEquals(2, $pager['total_pages']);
$this->assertEquals(1, $pager['current']);
$this->assertNotContains('node:' . $this->nodes[0]->id(), $element->getCacheTags());
$this->assertNotContains('node:' . $this->nodes[1]->id(), $element->getCacheTags());
$this->assertNotContains('node:' . $this->nodes[2]->id(), $element->getCacheTags());
$this->assertContains('node:' . $this->nodes[3]->id(), $element->getCacheTags());
}
/**
* Tests custom wrapper is rendered in regular page displays.
*
* Verifies that when a regular (non-CE) page display uses the Custom
* Elements style with a custom wrapper configured, the wrapper element
* is rendered directly in the output (not unwrapped).
*/
public function testCustomWrapperInRegularPageDisplay(): void {
// Use the pre-configured regular page display.
$view = Views::getView('custom_elements_view_test');
$view->setDisplay('page_1');
$view->initDisplay();
$view->initStyle();
// Configure custom wrapper.
$view->style_plugin->options['rows_wrapper'] = 'custom-grid';
// Execute and render.
$view->execute();
$output = $view->render();
// Render to HTML.
$rendered = $this->renderer->renderRoot($output);
$rendered_html = (string) $rendered;
// Verify the custom-grid wrapper element is in the rendered output.
$this->assertStringContainsString('<custom-grid', $rendered_html,
'Custom wrapper element should be rendered in regular page display');
$this->assertStringContainsString('</custom-grid>', $rendered_html,
'Custom wrapper element should have closing tag');
// Verify nodes are rendered inside the wrapper.
$this->assertStringContainsString('Test node 1', $rendered_html);
}
/**
* Tests validation error when CE display used without CE style.
*/
public function testValidationForMissingCustomElementsStyle(): void {
// Load the view and change style to non-CE.
$view = Views::getView('custom_elements_view_test');
$view->setDisplay('custom_elements_page_1');
$view->display_handler->setOption('style', [
'type' => 'default',
]);
$errors = $view->display_handler->validate();
// Verify validation error is present.
$this->assertNotEmpty($errors, 'Validation errors should be present');
$found_error = FALSE;
foreach ($errors as $error) {
if (strpos((string) $error, 'Custom Elements display') !== FALSE) {
$found_error = TRUE;
$this->assertStringContainsString('"Custom Elements" style', (string) $error);
break;
}
}
$this->assertTrue($found_error, 'Validation error about missing CE style should be present');
}
/**
* Renders the given block plugin to a custom element.
*
* @param \Drupal\views\Plugin\Block\ViewsBlock $block_plugin
* The block to render.
*
* @return \Drupal\custom_elements\CustomElement
* The generated element.
*/
private function renderBlockPluginToCustomElement(ViewsBlock $block_plugin): CustomElement {
$context = new RenderContext();
$build = $this->renderer->executeInRenderContext($context, function () use ($block_plugin) {
return $block_plugin->build();
});
$this->assertArrayHasKey('#custom_element', $build);
return $build['#custom_element'];
}
}
