LayoutBuilderQuickEditTest.php

Same filename in this branch
  1. 9 core/modules/quickedit/tests/src/Functional/LayoutBuilderQuickEditTest.php
Same filename and directory in other branches
  1. 8.9.x core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php
  2. 8.9.x core/modules/layout_builder/tests/src/Functional/LayoutBuilderQuickEditTest.php

Namespace

Drupal\Tests\quickedit\FunctionalJavascript

File

core/modules/quickedit/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php

View source
<?php

namespace Drupal\Tests\quickedit\FunctionalJavascript;

use Drupal\node\Entity\NodeType;
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;

/**
 * Tests that Layout Builder functions with Quick Edit.
 *
 * @covers layout_builder_entity_view_alter()
 * @covers layout_builder_quickedit_render_field()
 *
 * @group quickedit
 * @group legacy
 */
class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase {
    use EntityReferenceTestTrait;
    use ContextualLinkClickTrait;
    
    /**
     * {@inheritdoc}
     */
    protected static $modules = [
        'node',
        'layout_builder',
        'field_ui',
    ];
    
    /**
     * {@inheritdoc}
     */
    protected $defaultTheme = 'starterkit_theme';
    
    /**
     * The article node under test.
     *
     * @var \Drupal\node\NodeInterface
     */
    protected $article;
    
    /**
     * A user with permissions to edit Articles and use Quick Edit.
     *
     * @var \Drupal\user\UserInterface
     */
    protected $contentAuthorUser;
    
    /**
     * Whether the test is currently using Layout Builder on the entity.
     *
     * Allows performing assertions with and without Layout Builder.
     *
     * @var bool
     *
     * @see ::assertEntityInstanceFieldStates()
     * @see ::assertEntityInstanceFieldMarkup()
     */
    protected $usingLayoutBuilder = FALSE;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $this->drupalPlaceBlock('page_title_block');
        // Create the Article node type.
        $this->drupalCreateContentType([
            'type' => 'article',
            'name' => 'Article',
        ]);
        $this->article = $this->drupalCreateNode([
            'type' => 'article',
            'title' => 'My Test Node',
            'body' => [
                'value' => 'Hello Layout Builder!',
                'format' => 'plain_text',
            ],
        ]);
        // Log in as a content author who can use Quick Edit and edit Articles.
        $this->contentAuthorUser = $this->drupalCreateUser([
            'access contextual links',
            'access in-place editing',
            'access content',
            'edit any article content',
        ]);
        $this->drupalLogin($this->contentAuthorUser);
    }
    
    /**
     * Tests that Quick Edit still works even when there are duplicate fields.
     *
     * @see https://www.drupal.org/project/drupal/issues/3041850
     */
    public function testQuickEditIgnoresDuplicateFields() {
        // Place the body field a second time using Layout Builder.
        $this->enableLayouts('admin/structure/types/manage/article/display/default');
        $page = $this->getSession()
            ->getPage();
        $assert_session = $this->assertSession();
        $this->loginLayoutAdmin();
        $this->drupalGet('admin/structure/types/manage/article/display/default/layout');
        $page->clickLink('Add block');
        $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
        $assert_session->waitForElementVisible('named', [
            'link',
            'Body',
        ]);
        $page->clickLink('Body');
        $assert_session->waitForElementVisible('named', [
            'button',
            'Add block',
        ]);
        $page->pressButton('Add block');
        $assert_session->waitForElementVisible('named', [
            'button',
            'Save layout',
        ]);
        $page->pressButton('Save layout');
        $this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status'));
        $assert_session->pageTextContains('The layout has been saved.');
        $this->drupalLogin($this->contentAuthorUser);
        $this->usingLayoutBuilder = TRUE;
        $this->assertQuickEditInit([
            'title',
        ]);
        $this->drupalLogin($this->drupalCreateUser([
            'access contextual links',
            'access in-place editing',
            'access content',
            'edit any article content',
            'administer nodes',
        ]));
        $this->assertQuickEditInit([
            'title',
            'uid',
            'created',
        ]);
    }
    
    /**
     * Tests Quick Edit boots correctly with Layout Builder defaults & overrides.
     *
     * @param bool $use_revisions
     *   If revisions are used.
     * @param bool $admin_permission
     *   Whether to grant admin permissions to the user created for the test.
     *
     * @dataProvider providerEnableDisableLayoutBuilder
     */
    public function testEnableDisableLayoutBuilder($use_revisions, $admin_permission = FALSE) {
        if (!$use_revisions) {
            $content_type = NodeType::load('article');
            $content_type->setNewRevision(FALSE);
            $content_type->save();
        }
        $fields = [
            'title',
            'body',
        ];
        if ($admin_permission) {
            $fields = array_merge($fields, [
                'uid',
                'created',
            ]);
            $this->drupalLogin($this->drupalCreateUser([
                'access contextual links',
                'access in-place editing',
                'access content',
                'edit any article content',
                'administer nodes',
            ]));
        }
        // Test article with Layout Builder disabled.
        $this->assertQuickEditInit($fields);
        // Test article with Layout Builder enabled.
        $this->enableLayouts('admin/structure/types/manage/article/display/default');
        $this->usingLayoutBuilder = TRUE;
        $this->assertQuickEditInit($fields);
        // Test article with Layout Builder override.
        $this->createLayoutOverride();
        $this->assertQuickEditInit($fields);
        // If we're using revisions, it's not possible to disable Layout Builder
        // without deleting the node (unless the revisions containing the override
        // would be deleted).
        if (!$use_revisions) {
            // Test article with Layout Builder when reverted back to defaults.
            $this->revertLayoutToDefaults();
            $this->assertQuickEditInit($fields);
            // Test with Layout Builder disabled after being enabled.
            $this->usingLayoutBuilder = FALSE;
            $this->disableLayoutBuilder('admin/structure/types/manage/article/display/default');
            $this->assertQuickEditInit($fields);
        }
    }
    
    /**
     * DataProvider for testEnableDisableLayoutBuilder().
     */
    public function providerEnableDisableLayoutBuilder() {
        return [
            'use revisions, not admin' => [
                TRUE,
            ],
            'do not use revisions, not admin' => [
                FALSE,
            ],
            'use revisions, admin' => [
                TRUE,
                TRUE,
            ],
            'do not use revisions, admin' => [
                FALSE,
                TRUE,
            ],
        ];
    }
    
    /**
     * Enables layouts at an admin path.
     *
     * @param string $path
     *   The manage display path.
     */
    protected function enableLayouts($path) {
        // Save the current user to re-login after Layout Builder changes.
        $user = $this->loggedInUser;
        $this->loginLayoutAdmin();
        $page = $this->getSession()
            ->getPage();
        $this->drupalGet($path);
        $page->checkField('layout[enabled]');
        $page->checkField('layout[allow_custom]');
        $page->pressButton('Save');
        $this->drupalLogin($user);
    }
    
    /**
     * {@inheritdoc}
     */
    protected function assertEntityInstanceFieldStates($entity_type_id, $entity_id, $entity_instance_id, array $expected_field_states) : void {
        parent::assertEntityInstanceFieldStates($entity_type_id, $entity_id, $entity_instance_id, $this->replaceLayoutBuilderFieldIdKeys($expected_field_states));
    }
    
    /**
     * {@inheritdoc}
     */
    protected function assertEntityInstanceFieldMarkup($expected_field_attributes) : void {
        if (func_num_args() === 4) {
            $expected_field_attributes = func_get_arg(3);
            @trigger_error('Calling ' . __METHOD__ . '() with 4 arguments is deprecated in drupal:9.1.0 and will throw an error in drupal:10.0.0. See https://www.drupal.org/project/drupal/issues/3037436', E_USER_DEPRECATED);
        }
        parent::assertEntityInstanceFieldMarkup($this->replaceLayoutBuilderFieldIdKeys($expected_field_attributes));
    }
    
    /**
     * Replaces the array keys with Layout Builder field IDs when needed.
     *
     * @param array $array
     *   The array with field IDs as keys.
     *
     * @return array
     *   The array with the keys replaced.
     */
    protected function replaceLayoutBuilderFieldIdKeys(array $array) {
        if (!$this->usingLayoutBuilder) {
            return $array;
        }
        $replacement = [];
        foreach ($array as $field_key => $value) {
            $new_field_key = $this->getQuickEditFieldId($field_key);
            $replacement[$new_field_key] = $value;
        }
        return $replacement;
    }
    
    /**
     * Login the Layout admin user for the test.
     */
    protected function loginLayoutAdmin() {
        // Enable for the Layout Builder.
        $this->drupalLogin($this->drupalCreateUser([
            'configure any layout',
            'access content',
            'administer node display',
            'administer node fields',
            'administer blocks',
        ]));
    }
    
    /**
     * Creates a layout override.
     */
    protected function createLayoutOverride() {
        $page = $this->getSession()
            ->getPage();
        $assert_session = $this->assertSession();
        // Save the current user to re-login after Layout Builder changes.
        $user = $this->loggedInUser;
        $this->loginLayoutAdmin();
        $this->drupalGet('node/' . $this->article
            ->id() . '/layout');
        $page->pressButton('Save layout');
        $this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status'));
        $assert_session->pageTextContains('The layout override has been saved.');
        $this->drupalLogin($user);
    }
    
    /**
     * Reverts a layout override.
     */
    protected function revertLayoutToDefaults() {
        $page = $this->getSession()
            ->getPage();
        $assert_session = $this->assertSession();
        // Save the current user to re-login after Layout Builder changes.
        $user = $this->loggedInUser;
        $this->loginLayoutAdmin();
        $this->drupalGet('node/' . $this->article
            ->id() . '/layout');
        $assert_session->buttonExists('Revert to defaults');
        $page->pressButton('Revert to defaults');
        $page->pressButton('Revert');
        $assert_session->pageTextContains('The layout has been reverted back to defaults.');
        $this->drupalLogin($user);
    }
    
    /**
     * Disables Layout Builder.
     *
     * @param string $path
     *   The path to the manage display page.
     */
    protected function disableLayoutBuilder($path) {
        $page = $this->getSession()
            ->getPage();
        // Save the current user to re-login after Layout Builder changes.
        $user = $this->loggedInUser;
        $this->loginLayoutAdmin();
        $this->drupalGet($path);
        $page->uncheckField('layout[allow_custom]');
        $page->uncheckField('layout[enabled]');
        $page->pressButton('Save');
        $page->pressButton('Confirm');
        $this->drupalLogin($user);
    }
    
    /**
     * Asserts that Quick Edit is initialized on the node view correctly.
     *
     * @todo Replace calls to this method with calls to ::doTestArticle() in
     *    https://www.drupal.org/node/3037436.
     *
     * @param string[] $fields
     *   The fields test.
     */
    private function assertQuickEditInit(array $fields) : void {
        $this->assertNotEmpty($fields);
        $node = $this->article;
        $this->drupalGet('node/' . $node->id());
        // Initial state.
        $this->awaitQuickEditForEntity('node', 1);
        $this->assertEntityInstanceStates([
            'node/1[0]' => 'closed',
        ]);
        $field_states = [];
        foreach ($fields as $field) {
            $field_states["node/1/{$field}/en/full"] = 'inactive';
        }
        $this->assertEntityInstanceFieldStates('node', 1, 0, $field_states);
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getQuickEditFieldId($original_field_id) {
        $page = $this->getSession()
            ->getPage();
        $parts = explode('/', $original_field_id);
        // Removes the last part of the field id which will contain the Quick Edit
        // view mode ID. When using the Layout Builder the view_mode will contain a
        // hash of the layout sections and will be different each time the layout
        // changes.
        array_pop($parts);
        $field_key_without_view_mode = implode('/', $parts);
        $element = $page->find('css', "[data-quickedit-field-id^=\"{$field_key_without_view_mode}\"]");
        $this->assertNotEmpty($element, "Found Quick Edit-enabled field whose data-quickedit-field attribute starts with: {$field_key_without_view_mode}");
        try {
            $has_attribute = $element->hasAttribute('data-quickedit-field-id');
        } catch (\Exception $e) {
            $has_attribute = FALSE;
        }
        $this->assertTrue($has_attribute, $field_key_without_view_mode);
        $new_field_id = $element->getAttribute('data-quickedit-field-id');
        return $new_field_id;
    }

}

Classes

Title Deprecated Summary
LayoutBuilderQuickEditTest Tests that Layout Builder functions with Quick Edit.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.