3 namespace Drupal\Tests\outside_in\FunctionalJavascript;
5 use Drupal\block\Entity\Block;
6 use Drupal\block_content\Entity\BlockContent;
7 use Drupal\block_content\Entity\BlockContentType;
8 use Drupal\user\Entity\Role;
11 * Testing opening and saving block forms in the off-canvas dialog.
15 class OutsideInBlockFormTest extends OutsideInJavascriptTestBase {
17 const TOOLBAR_EDIT_LINK_SELECTOR = '#toolbar-bar div.contextual-toolbar-tab button';
19 const LABEL_INPUT_SELECTOR = 'input[data-drupal-selector="edit-settings-label"]';
24 public static $modules = [
35 // Add test module to override CSS pointer-events properties because they
36 // cause test failures.
37 'outside_in_test_css',
43 protected function setUp() {
46 $this->createBlockContentType('basic', TRUE);
47 $block_content = $this->createBlockContent('Custom Block', 'basic', TRUE);
48 $user = $this->createUser([
50 'access contextual links',
53 'access in-place editing',
56 $this->drupalLogin($user);
57 $this->placeBlock('block_content:' . $block_content->uuid(), ['id' => 'custom']);
61 * Tests opening off-canvas dialog by click blocks and elements in the blocks.
63 * @dataProvider providerTestBlocks
65 public function testBlocks($block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
66 $web_assert = $this->assertSession();
67 $page = $this->getSession()->getPage();
68 foreach ($this->getTestThemes() as $theme) {
69 $this->enableTheme($theme);
70 $block = $this->placeBlock($block_plugin);
71 $block_selector = str_replace('_', '-', $this->getBlockSelector($block));
72 $block_id = $block->id();
73 $this->drupalGet('user');
75 $link = $page->find('css', "$block_selector .contextual-links li a");
76 $this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block.");
77 $this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href'));
79 if (isset($toolbar_item)) {
80 // Check that you can open a toolbar tray and it will be closed after
81 // entering edit mode.
82 if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
83 // If a tray was open from page load close it.
85 $this->waitForNoElement("#toolbar-administration a.is-active");
87 $page->find('css', $toolbar_item)->click();
88 $this->assertElementVisibleAfterWait('css', "{$toolbar_item}.is-active");
90 $this->enableEditMode();
91 if (isset($toolbar_item)) {
92 $this->waitForNoElement("{$toolbar_item}.is-active");
94 $this->openBlockForm($block_selector);
95 switch ($block_plugin) {
96 case 'system_powered_by_block':
97 // Confirm "Display Title" is not checked.
98 $web_assert->checkboxNotChecked('settings[label_display]');
99 // Confirm Title is not visible.
100 $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
101 $page->checkField('settings[label_display]');
102 $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
103 // Fill out form, save the form.
104 $page->fillField('settings[label]', $new_page_text);
108 case 'system_branding_block':
109 // Fill out form, save the form.
110 $page->fillField('settings[site_information][site_name]', $new_page_text);
114 if (isset($new_page_text)) {
115 $page->pressButton($button_text);
116 // Make sure the changes are present.
117 // @todo Use a wait method that will take into account the form submitting
118 // and all JavaScript activity. https://www.drupal.org/node/2837676
119 // The use \Behat\Mink\WebAssert::pageTextContains to check text.
120 $this->assertJsCondition('jQuery("' . $block_selector . ' ' . $label_selector . '").html() == "' . $new_page_text . '"');
123 $this->openBlockForm($block_selector);
125 $this->disableEditMode();
126 // Canvas should close when editing module is closed.
127 $this->waitForOffCanvasToClose();
129 $this->enableEditMode();
131 // Open block form by clicking a element inside the block.
132 // This confirms that default action for links and form elements is
134 $this->openBlockForm("$block_selector {$element_selector}");
135 $web_assert->elementTextContains('css', '.contextual-toolbar-tab button', 'Editing');
136 $web_assert->elementAttributeContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-outside-in-edit-mode');
137 // Simulate press the Escape key.
138 $this->getSession()->executeScript('jQuery("body").trigger(jQuery.Event("keyup", { keyCode: 27 }));');
139 $this->waitForOffCanvasToClose();
140 $this->getSession()->wait(100);
141 $this->assertEditModeDisabled();
142 $web_assert->elementTextContains('css', '#drupal-live-announce', 'Exited edit mode.');
143 $web_assert->elementTextNotContains('css', '.contextual-toolbar-tab button', 'Editing');
144 $web_assert->elementAttributeNotContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-outside-in-edit-mode');
145 // Delete the block that was placed for the current theme.
151 * Dataprovider for testBlocks().
153 public function providerTestBlocks() {
156 'block_plugin' => 'system_powered_by_block',
157 'new_page_text' => 'Can you imagine anyone showing the label on this block?',
158 'element_selector' => 'span a',
159 'label_selector' => 'h2',
160 'button_text' => 'Save Powered by Drupal',
161 'toolbar_item' => '#toolbar-item-user',
163 'block-branding' => [
164 'block_plugin' => 'system_branding_block',
165 'new_page_text' => 'The site that will live a very short life.',
166 'element_selector' => "a[rel='home']:last-child",
167 'label_selector' => "a[rel='home']:last-child",
168 'button_text' => 'Save Site branding',
169 'toolbar_item' => '#toolbar-item-administration',
172 'block_plugin' => 'search_form_block',
173 'new_page_text' => NULL,
174 'element_selector' => '#edit-submit',
175 'label_selector' => 'h2',
176 'button_text' => 'Save Search form',
177 'toolbar_item' => NULL,
184 * Enables edit mode by pressing edit button in the toolbar.
186 protected function enableEditMode() {
187 $this->pressToolbarEditButton();
188 $this->waitForToolbarToLoad();
189 $this->assertEditModeEnabled();
193 * Disables edit mode by pressing edit button in the toolbar.
195 protected function disableEditMode() {
196 $this->pressToolbarEditButton();
197 $this->waitForToolbarToLoad();
198 $this->assertEditModeDisabled();
202 * Asserts that Off-Canvas block form is valid.
204 protected function assertOffCanvasBlockFormIsValid() {
205 $web_assert = $this->assertSession();
206 // Confirm that Block title display label has been changed.
207 $web_assert->elementTextContains('css', '.form-item-settings-label-display label', 'Display block title');
208 // Confirm Block title label is shown if checkbox is checked.
209 if ($this->getSession()->getPage()->find('css', 'input[name="settings[label_display]"]')->isChecked()) {
210 $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
211 $web_assert->elementTextContains('css', '.form-item-settings-label label', 'Block title');
214 $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
217 // Check that common block form elements exist.
218 $web_assert->elementExists('css', static::LABEL_INPUT_SELECTOR);
219 $web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label-display"]');
220 // Check that advanced block form elements do not exist.
221 $web_assert->elementNotExists('css', 'input[data-drupal-selector="edit-visibility-request-path-pages"]');
222 $web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]');
226 * Open block form by clicking the element found with a css selector.
228 * @param string $block_selector
229 * A css selector selects the block or an element within it.
231 protected function openBlockForm($block_selector) {
232 $this->waitForToolbarToLoad();
233 $this->click($block_selector);
234 $this->waitForOffCanvasToOpen();
235 $this->assertOffCanvasBlockFormIsValid();
239 * Tests QuickEdit links behavior.
241 public function testQuickEditLinks() {
242 $quick_edit_selector = '#quickedit-entity-toolbar';
243 $node_selector = '[data-quickedit-entity-id="node/1"]';
244 $body_selector = '[data-quickedit-field-id="node/1/body/en/full"]';
245 $web_assert = $this->assertSession();
246 // Create a Content type and two test nodes.
247 $this->createContentType(['type' => 'page']);
248 $auth_role = Role::load(Role::AUTHENTICATED_ID);
249 $this->grantPermissions($auth_role, [
250 'edit any page content',
253 $node = $this->createNode(
255 'title' => 'Page One',
259 'value' => 'Regular NODE body for the test.',
260 'format' => 'plain_text',
265 $page = $this->getSession()->getPage();
266 $block_plugin = 'system_powered_by_block';
268 foreach ($this->getTestThemes() as $theme) {
270 $this->enableTheme($theme);
272 $block = $this->placeBlock($block_plugin);
273 $block_selector = str_replace('_', '-', $this->getBlockSelector($block));
274 // Load the same page twice.
275 foreach ([1, 2] as $page_load_times) {
276 $this->drupalGet('node/' . $node->id());
277 // Waiting for Toolbar module.
278 // @todo Remove the hack after https://www.drupal.org/node/2542050.
279 $this->assertElementVisibleAfterWait('css', '.toolbar-fixed');
280 // Waiting for Toolbar animation.
281 $web_assert->assertWaitOnAjaxRequest();
282 // The 2nd page load we should already be in edit mode.
283 if ($page_load_times == 1) {
284 $this->enableEditMode();
286 // In Edit mode clicking field should open QuickEdit toolbar.
287 $page->find('css', $body_selector)->click();
288 $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
290 $this->disableEditMode();
291 // Exiting Edit mode should close QuickEdit toolbar.
292 $web_assert->elementNotExists('css', $quick_edit_selector);
293 // When not in Edit mode QuickEdit toolbar should not open.
294 $page->find('css', $body_selector)->click();
295 $web_assert->elementNotExists('css', $quick_edit_selector);
296 $this->enableEditMode();
297 $this->openBlockForm($block_selector);
298 $page->find('css', $body_selector)->click();
299 $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
300 // Off-canvas dialog should be closed when opening QuickEdit toolbar.
301 $this->waitForOffCanvasToClose();
303 $this->openBlockForm($block_selector);
304 // QuickEdit toolbar should be closed when opening Off-canvas dialog.
305 $web_assert->elementNotExists('css', $quick_edit_selector);
307 // Check using contextual links to invoke QuickEdit and open the tray.
308 $this->drupalGet('node/' . $node->id());
309 $web_assert->assertWaitOnAjaxRequest();
310 $this->disableEditMode();
311 // Open QuickEdit toolbar before going into Edit mode.
312 $this->clickContextualLink($node_selector, "Quick edit");
313 $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
314 // Open off-canvas and enter Edit mode via contextual link.
315 $this->clickContextualLink($block_selector, "Quick edit");
316 $this->waitForOffCanvasToOpen();
317 // QuickEdit toolbar should be closed when opening off-canvas dialog.
318 $web_assert->elementNotExists('css', $quick_edit_selector);
319 // Open QuickEdit toolbar via contextual link while in Edit mode.
320 $this->clickContextualLink($node_selector, "Quick edit", FALSE);
321 $this->waitForOffCanvasToClose();
322 $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
323 $this->disableEditMode();
328 * Tests enabling and disabling Edit Mode.
330 public function testEditModeEnableDisable() {
331 foreach ($this->getTestThemes() as $theme) {
332 $this->enableTheme($theme);
333 $block = $this->placeBlock('system_powered_by_block');
334 foreach (['contextual_link', 'toolbar_link'] as $enable_option) {
335 $this->drupalGet('user');
336 $this->assertEditModeDisabled();
337 switch ($enable_option) {
339 case 'contextual_link':
340 $this->clickContextualLink($this->getBlockSelector($block), "Quick edit");
341 $this->waitForOffCanvasToOpen();
342 $this->assertEditModeEnabled();
346 $this->enableEditMode();
349 $this->disableEditMode();
351 // Make another page request to ensure Edit mode is still disabled.
352 $this->drupalGet('user');
353 $this->assertEditModeDisabled();
354 // Make sure on this page request it also re-enables and disables
356 $this->enableEditMode();
357 $this->disableEditMode();
363 * Assert that edit mode has been properly enabled.
365 protected function assertEditModeEnabled() {
366 $web_assert = $this->assertSession();
367 // No contextual triggers should be hidden.
368 $web_assert->elementNotExists('css', '.contextual .trigger.visually-hidden');
369 // The toolbar edit button should read "Editing".
370 $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Editing');
371 // The main canvas element should have the "js-outside-in-edit-mode" class.
372 $web_assert->elementExists('css', '.dialog-off-canvas__main-canvas.js-outside-in-edit-mode');
376 * Assert that edit mode has been properly disabled.
378 protected function assertEditModeDisabled() {
379 $web_assert = $this->assertSession();
380 // Contextual triggers should be hidden.
381 $web_assert->elementExists('css', '.contextual .trigger.visually-hidden');
382 // No contextual triggers should be not hidden.
383 $web_assert->elementNotExists('css', '.contextual .trigger:not(.visually-hidden)');
384 // The toolbar edit button should read "Edit".
385 $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Edit');
386 // The main canvas element should NOT have the "js-outside-in-edit-mode"
388 $web_assert->elementNotExists('css', '.dialog-off-canvas__main-canvas.js-outside-in-edit-mode');
392 * Press the toolbar Edit button provided by the contextual module.
394 protected function pressToolbarEditButton() {
395 $this->assertSession()->waitForElement('css', '[data-contextual-id] .contextual-links a');
396 $edit_button = $this->getSession()
398 ->find('css', static::TOOLBAR_EDIT_LINK_SELECTOR);
399 $edit_button->press();
403 * Creates a custom block.
405 * @param bool|string $title
406 * (optional) Title of block. When no value is given uses a random name.
408 * @param string $bundle
409 * (optional) Bundle name. Defaults to 'basic'.
411 * (optional) Whether to save the block. Defaults to TRUE.
413 * @return \Drupal\block_content\Entity\BlockContent
414 * Created custom block.
416 protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
417 $title = $title ?: $this->randomName();
418 $block_content = BlockContent::create([
423 'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
424 'format' => 'plain_text',
427 if ($block_content && $save === TRUE) {
428 $block_content->save();
430 return $block_content;
434 * Creates a custom block type (bundle).
436 * @param string $label
437 * The block type label.
438 * @param bool $create_body
439 * Whether or not to create the body field.
441 * @return \Drupal\block_content\Entity\BlockContentType
442 * Created custom block type.
444 protected function createBlockContentType($label, $create_body = FALSE) {
445 $bundle = BlockContentType::create([
452 block_content_add_body_field($bundle->id());
458 * Tests that contextual links in custom blocks are changed.
460 * "Quick edit" is quickedit.module link.
461 * "Quick edit settings" is outside_in.module link.
463 public function testCustomBlockLinks() {
464 $this->drupalGet('user');
465 $page = $this->getSession()->getPage();
466 $links = $page->findAll('css', "#block-custom .contextual-links li a");
468 /** @var \Behat\Mink\Element\NodeElement $link */
469 foreach ($links as $link) {
470 $link_labels[$link->getAttribute('href')] = $link->getText();
472 $href = array_search('Quick edit', $link_labels);
473 $this->assertEquals('', $href);
474 $href = array_search('Quick edit settings', $link_labels);
475 $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE);
479 * Gets the block CSS selector.
481 * @param \Drupal\block\Entity\Block $block
487 public function getBlockSelector(Block $block) {
488 return '#block-' . $block->id();
492 * Determines if the label input is visible.
495 * TRUE if the label is visible, FALSE if it is not.
497 protected function isLabelInputVisible() {
498 return $this->getSession()->getPage()->find('css', static::LABEL_INPUT_SELECTOR)->isVisible();