BlockTest.php 22 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\block\Tests;

5
use Drupal\Component\Utility\Html;
6
use Drupal\block\Entity\Block;
7
use Drupal\Core\Url;
8
use Drupal\user\Entity\Role;
9
use Drupal\user\RoleInterface;
10

11
/**
12 13 14
 * Tests basic block functionality.
 *
 * @group block
15 16
 */
class BlockTest extends BlockTestBase {
17 18

  /**
19
   * Tests block visibility.
20 21
   */
  function testBlockVisibility() {
22 23
    $block_name = 'system_powered_by_block';
    // Create a random title for the block.
24
    $title = $this->randomMachineName(8);
25
    // Enable a standard block.
26
    $default_theme = $this->config('system.theme')->get('default');
27
    $edit = array(
28
      'id' => strtolower($this->randomMachineName(8)),
29
      'region' => 'sidebar_first',
30
      'settings[label]' => $title,
31
      'settings[label_display]' => TRUE,
32
    );
33 34
    // Set the block to be hidden on any user path, and to be shown only to
    // authenticated users.
35
    $edit['visibility[request_path][pages]'] = '/user*';
36
    $edit['visibility[request_path][negate]'] = TRUE;
37
    $edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
38 39 40 41
    $this->drupalGet('admin/structure/block/add/' . $block_name . '/' . $default_theme);
    $this->assertFieldChecked('edit-visibility-request-path-negate-0');

    $this->drupalPostForm(NULL, $edit, t('Save block'));
42
    $this->assertText('The block configuration has been saved.', 'Block was saved');
43

44 45 46
    $this->clickLink('Configure');
    $this->assertFieldChecked('edit-visibility-request-path-negate-1');

47
    $this->drupalGet('');
48
    $this->assertText($title, 'Block was displayed on the front page.');
49 50

    $this->drupalGet('user');
51
    $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
52 53 54 55

    // Confirm that the block is not displayed to anonymous users.
    $this->drupalLogout();
    $this->drupalGet('');
56
    $this->assertNoText($title, 'Block was not displayed to anonymous users.');
57 58

    // Confirm that an empty block is not displayed.
59
    $this->assertNoText('Powered by Drupal', 'Empty block not displayed.');
60
    $this->assertNoRaw('sidebar-first', 'Empty sidebar-first region is not displayed.');
61 62
  }

63 64 65 66 67 68 69 70
  /**
   * Tests that visibility can be properly toggled.
   */
  public function testBlockToggleVisibility() {
    $block_name = 'system_powered_by_block';
    // Create a random title for the block.
    $title = $this->randomMachineName(8);
    // Enable a standard block.
71
    $default_theme = $this->config('system.theme')->get('default');
72 73 74 75 76 77 78
    $edit = array(
      'id' => strtolower($this->randomMachineName(8)),
      'region' => 'sidebar_first',
      'settings[label]' => $title,
    );
    $block_id = $edit['id'];
    // Set the block to be shown only to authenticated users.
79
    $edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
80 81 82 83 84
    $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
    $this->clickLink('Configure');
    $this->assertFieldChecked('edit-visibility-user-role-roles-authenticated');

    $edit = [
85
      'visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']' => FALSE,
86 87 88 89 90 91 92 93 94 95 96 97 98
    ];
    $this->drupalPostForm(NULL, $edit, 'Save block');
    $this->clickLink('Configure');
    $this->assertNoFieldChecked('edit-visibility-user-role-roles-authenticated');

    // Ensure that no visibility is configured.
    /** @var \Drupal\block\BlockInterface $block */
    $block = Block::load($block_id);
    $visibility_config = $block->getVisibilityConditions()->getConfiguration();
    $this->assertIdentical([], $visibility_config);
    $this->assertIdentical([], $block->get('visibility'));
  }

99
  /**
100
   * Test block visibility when leaving "pages" textarea empty.
101 102
   */
  function testBlockVisibilityListedEmpty() {
103 104
    $block_name = 'system_powered_by_block';
    // Create a random title for the block.
105
    $title = $this->randomMachineName(8);
106
    // Enable a standard block.
107
    $default_theme = $this->config('system.theme')->get('default');
108
    $edit = array(
109
      'id' => strtolower($this->randomMachineName(8)),
110
      'region' => 'sidebar_first',
111
      'settings[label]' => $title,
112
      'visibility[request_path][negate]' => TRUE,
113
    );
114 115
    // Set the block to be hidden on any user path, and to be shown only to
    // authenticated users.
116
    $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
117
    $this->assertText('The block configuration has been saved.', 'Block was saved');
118

119
    $this->drupalGet('user');
120
    $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
121

122
    $this->drupalGet('USER');
123
    $this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.');
124 125 126 127

    // Confirm that the block is not displayed to anonymous users.
    $this->drupalLogout();
    $this->drupalGet('');
128
    $this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.');
129 130
  }

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  /**
   * Tests adding a block from the library page with a destination query string.
   */
  public function testAddBlockFromLibrary() {
    $default_theme = $this->config('system.theme')->get('default');
    $help_url = Url::fromRoute('help.page', ['name' => 'block']);
    // Set up the request so we land on the block help page after creation.
    $options = [
      'query' => [
        'region' => 'sidebar_first',
        'destination' => $help_url->toString(),
      ],
    ];
    $this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options));

    $block_name = 'system_powered_by_block';
    $add_url = Url::fromRoute('block.admin_add', ['plugin_id' => $block_name, 'theme' => $default_theme]);
    $links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]);
    $this->assertEqual(1, count($links), 'Found one matching link');

    list($path, $query_string) = explode('?', $links[0]['href'], 2);
    parse_str($query_string, $query_parts);
    $this->assertEqual(t('Place block'), (string) $links[0]);
    $this->assertEqual($help_url->toString(), $query_parts['destination'], 'Expected destination query string is in href');

    // Create a random title for the block.
    $title = $this->randomMachineName(8);
    $block_id = strtolower($this->randomMachineName(8));
    $edit = [
      'id' => $block_id,
      'settings[label]' => $title,
    ];
    // Create the block using the link parsed from the library page.
    $this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block'));
    // Verify that we are redirected according to the original request.
    $this->assertUrl($help_url);

    // Ensure that the block was created.
    /** @var \Drupal\block\BlockInterface $block */
    $block = Block::load($block_id);
    $this->assertEqual($title, $block->label(), 'Found the block with expected title.');
  }

174 175 176 177
  /**
   * Test configuring and moving a module-define block to specific regions.
   */
  function testBlock() {
178 179 180
    // Place page title block to test error messages.
    $this->drupalPlaceBlock('page_title_block');

181
    // Select the 'Powered by Drupal' block to be configured and moved.
182
    $block = array();
183
    $block['id'] = 'system_powered_by_block';
184
    $block['settings[label]'] = $this->randomMachineName(8);
185
    $block['settings[label_display]'] = TRUE;
186
    $block['theme'] = $this->config('system.theme')->get('default');
187
    $block['region'] = 'header';
188 189

    // Set block title to confirm that interface works and override any custom titles.
190
    $this->drupalPostForm('admin/structure/block/add/' . $block['id'] . '/' . $block['theme'], array('settings[label]' => $block['settings[label]'], 'settings[label_display]' => $block['settings[label_display]'], 'id' => $block['id'], 'region' => $block['region']), t('Save block'));
191
    $this->assertText(t('The block configuration has been saved.'), 'Block title set.');
192
    // Check to see if the block was created by checking its configuration.
193
    $instance = Block::load($block['id']);
194

195
    $this->assertEqual($instance->label(), $block['settings[label]'], 'Stored block title found.');
196 197 198 199 200 201 202 203

    // Check whether the block can be moved to all available regions.
    foreach ($this->regions as $region) {
      $this->moveBlockToRegion($block, $region);
    }

    // Set the block to the disabled region.
    $edit = array();
204
    $edit['blocks[' . $block['id'] . '][region]'] = -1;
205
    $this->drupalPostForm('admin/structure/block', $edit, t('Save blocks'));
206

207
    // Confirm that the block is now listed as disabled.
208
    $this->assertText(t('The block settings have been updated.'), 'Block successfully move to disabled region.');
209

210 211
    // Confirm that the block instance title and markup are not displayed.
    $this->drupalGet('node');
212
    $this->assertNoText(t($block['settings[label]']));
213 214
    // Check for <div id="block-my-block-instance-name"> if the machine name
    // is my_block_instance_name.
215
    $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-' . str_replace('_', '-', strtolower($block['id']))));
216
    $this->assertNoFieldByXPath($xpath, FALSE, 'Block found in no regions.');
217 218

    // Test deleting the block from the edit form.
219
    $this->drupalGet('admin/structure/block/manage/' . $block['id']);
220
    $this->clickLink(t('Delete'));
221
    $this->assertRaw(t('Are you sure you want to delete the block %name?', array('%name' => $block['settings[label]'])));
222
    $this->drupalPostForm(NULL, array(), t('Delete'));
223
    $this->assertRaw(t('The block %name has been deleted.', array('%name' => $block['settings[label]'])));
224 225 226 227

    // Test deleting a block via "Configure block" link.
    $block = $this->drupalPlaceBlock('system_powered_by_block');
    $this->drupalGet('admin/structure/block/manage/' . $block->id(), array('query' => array('destination' => 'admin')));
228
    $this->clickLink(t('Delete'));
229 230
    $this->assertRaw(t('Are you sure you want to delete the block %name?', array('%name' => $block->label())));
    $this->drupalPostForm(NULL, array(), t('Delete'));
231
    $this->assertRaw(t('The block %name has been deleted.', array('%name' => $block->label())));
232 233
    $this->assertUrl('admin');
    $this->assertNoRaw($block->id());
234 235
  }

236 237 238 239
  /**
   * Tests that the block form has a theme selector when not passed via the URL.
   */
  public function testBlockThemeSelector() {
240
    // Install all themes.
241
    \Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
242
    $theme_settings = $this->config('system.theme');
243
    foreach (['bartik', 'seven', 'stark'] as $theme) {
244 245
      $this->drupalGet('admin/structure/block/list/' . $theme);
      $this->assertTitle(t('Block layout') . ' | Drupal');
246 247
      // Select the 'Powered by Drupal' block to be placed.
      $block = array();
248
      $block['id'] = strtolower($this->randomMachineName());
249 250
      $block['theme'] = $theme;
      $block['region'] = 'content';
251
      $this->drupalPostForm('admin/structure/block/add/system_powered_by_block', $block, t('Save block'));
252
      $this->assertText(t('The block configuration has been saved.'));
253
      $this->assertUrl('admin/structure/block/list/' . $theme . '?block-placement=' . Html::getClass($block['id']));
254 255 256 257

      // Set the default theme and ensure the block is placed.
      $theme_settings->set('default', $theme)->save();
      $this->drupalGet('');
258
      $elements = $this->xpath('//div[@id = :id]', array(':id' => Html::getUniqueId('block-' . $block['id'])));
259 260 261 262
      $this->assertTrue(!empty($elements), 'The block was found.');
    }
  }

263 264 265 266
  /**
   * Test block display of theme titles.
   */
  function testThemeName() {
267
    // Enable the help block.
268
    $this->drupalPlaceBlock('help_block', array('region' => 'help'));
269
    $this->drupalPlaceBlock('local_tasks_block');
270
    // Explicitly set the default and admin themes.
271
    $theme = 'block_test_specialchars_theme';
272
    \Drupal::service('theme_handler')->install(array($theme));
273 274
    \Drupal::service('router.builder')->rebuild();
    $this->drupalGet('admin/structure/block');
275
    $this->assertEscaped('<"Cat" & \'Mouse\'>');
276
    $this->drupalGet('admin/structure/block/list/block_test_specialchars_theme');
277
    $this->assertEscaped('Demonstrate block regions (<"Cat" & \'Mouse\'>)');
278 279
  }

280 281 282 283 284 285
  /**
   * Test block title display settings.
   */
  function testHideBlockTitle() {
    $block_name = 'system_powered_by_block';
    // Create a random title for the block.
286 287
    $title = $this->randomMachineName(8);
    $id = strtolower($this->randomMachineName(8));
288
    // Enable a standard block.
289
    $default_theme = $this->config('system.theme')->get('default');
290
    $edit = array(
291
      'id' => $id,
292
      'region' => 'sidebar_first',
293
      'settings[label]' => $title,
294
    );
295
    $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
296 297 298
    $this->assertText('The block configuration has been saved.', 'Block was saved');

    $this->drupalGet('user');
299
    $this->assertNoText($title, 'Block title was not displayed by default.');
300 301

    $edit = array(
302
      'settings[label_display]' => TRUE,
303
    );
304
    $this->drupalPostForm('admin/structure/block/manage/' . $id, $edit, t('Save block'));
305 306
    $this->assertText('The block configuration has been saved.', 'Block was saved');

307
    $this->drupalGet('admin/structure/block/manage/' . $id);
308
    $this->assertFieldChecked('edit-settings-label-display', 'The display_block option has the correct default value on the configuration form.');
309

310
    $this->drupalGet('user');
311
    $this->assertText($title, 'Block title was displayed when enabled.');
312 313
  }

314 315 316 317 318 319 320 321 322 323 324 325 326
  /**
   * Moves a block to a given region via the UI and confirms the result.
   *
   * @param array $block
   *   An array of information about the block, including the following keys:
   *   - module: The module providing the block.
   *   - title: The title of the block.
   *   - delta: The block's delta key.
   * @param string $region
   *   The machine name of the theme region to move the block to, for example
   *   'header' or 'sidebar_first'.
   */
  function moveBlockToRegion(array $block, $region) {
327
    // Set the created block to a specific region.
328
    $block += array('theme' => $this->config('system.theme')->get('default'));
329
    $edit = array();
330
    $edit['blocks[' . $block['id'] . '][region]'] = $region;
331
    $this->drupalPostForm('admin/structure/block', $edit, t('Save blocks'));
332 333

    // Confirm that the block was moved to the proper region.
334
    $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
335 336

    // Confirm that the block is being displayed.
337
    $this->drupalGet('');
338
    $this->assertText(t($block['settings[label]']), 'Block successfully being displayed on the page.');
339 340 341

    // Confirm that the custom block was found at the proper region.
    $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
342
      ':region-class' => 'region region-' . Html::getClass($region),
343
      ':block-id' => 'block-' . str_replace('_', '-', strtolower($block['id'])),
344
    ));
345
    $this->assertFieldByXPath($xpath, NULL, t('Block found in %region_name region.', array('%region_name' => Html::getClass($region))));
346 347 348
  }

  /**
349 350 351 352 353
   * Test that cache tags are properly set and bubbled up to the page cache.
   *
   * Verify that invalidation of these cache tags works:
   * - "block:<block ID>"
   * - "block_plugin:<block plugin ID>"
354
   */
355 356 357
  public function testBlockCacheTags() {
    // The page cache only works for anonymous users.
    $this->drupalLogout();
358

359
    // Enable page caching.
360
    $config = $this->config('system.performance');
361 362 363 364
    $config->set('cache.page.max_age', 300);
    $config->save();

    // Place the "Powered by Drupal" block.
365
    $block = $this->drupalPlaceBlock('system_powered_by_block', array('id' => 'powered'));
366 367 368 369 370 371 372 373 374

    // Prime the page cache.
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');

    // Verify a cache hit, but also the presence of the correct cache tags in
    // both the page and block caches.
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
375
    $cid_parts = array(\Drupal::url('<front>', array(), array('absolute' => TRUE)), 'html');
376
    $cid = implode(':', $cid_parts);
377
    $cache_entry = \Drupal::cache('render')->get($cid);
378
    $expected_cache_tags = array(
379
      'config:block_list',
380
      'block_view',
381
      'config:block.block.powered',
382
      'config:user.role.anonymous',
383
      'rendered',
384
    );
385
    sort($expected_cache_tags);
386
    $keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
387
    $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
388
    $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
389
    $expected_cache_tags = array(
390
      'block_view',
391
      'config:block.block.powered',
392
      'rendered',
393
    );
394
    sort($expected_cache_tags);
395
    $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
396

397
    // The "Powered by Drupal" block is modified; verify a cache miss.
398
    $block->setRegion('content');
399
    $block->save();
400 401 402 403 404 405 406 407
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');

    // Now we should have a cache hit again.
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');

    // Place the "Powered by Drupal" block another time; verify a cache miss.
408
    $block_2 = $this->drupalPlaceBlock('system_powered_by_block', array('id' => 'powered-2'));
409 410 411 412 413 414
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');

    // Verify a cache hit, but also the presence of the correct cache tags.
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
415
    $cid_parts = array(\Drupal::url('<front>', array(), array('absolute' => TRUE)), 'html');
416
    $cid = implode(':', $cid_parts);
417
    $cache_entry = \Drupal::cache('render')->get($cid);
418
    $expected_cache_tags = array(
419
      'config:block_list',
420
      'block_view',
421 422
      'config:block.block.powered',
      'config:block.block.powered-2',
423
      'config:user.role.anonymous',
424
      'rendered',
425
    );
426
    sort($expected_cache_tags);
427 428
    $this->assertEqual($cache_entry->tags, $expected_cache_tags);
    $expected_cache_tags = array(
429
      'block_view',
430
      'config:block.block.powered',
431
      'rendered',
432
    );
433
    sort($expected_cache_tags);
434 435
    $keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
    $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
436 437
    $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
    $expected_cache_tags = array(
438
      'block_view',
439
      'config:block.block.powered-2',
440
      'rendered',
441
    );
442
    sort($expected_cache_tags);
443 444
    $keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
    $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:' . implode(':', $keys));
445 446 447 448 449 450 451 452 453 454
    $this->assertIdentical($cache_entry->tags, $expected_cache_tags);

    // Now we should have a cache hit again.
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');

    // Delete the "Powered by Drupal" blocks; verify a cache miss.
    entity_delete_multiple('block', array('powered', 'powered-2'));
    $this->drupalGet('<front>');
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
455
  }
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471

  /**
   * Tests that a link exists to block layout from the appearance form.
   */
  public function testThemeAdminLink() {
    $this->drupalPlaceBlock('help_block', ['region' => 'help']);
    $theme_admin = $this->drupalCreateUser([
      'administer blocks',
      'administer themes',
      'access administration pages',
    ]);
    $this->drupalLogin($theme_admin);
    $this->drupalGet('admin/appearance');
    $this->assertText('You can place blocks for each theme on the block layout page');
    $this->assertLinkByHref('admin/structure/block');
  }
472

473 474 475 476 477 478 479 480 481 482 483 484 485
  /**
   * Tests that uninstalling a theme removes its block configuration.
   */
  public function testUninstallTheme() {
    /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
    $theme_handler = \Drupal::service('theme_handler');

    $theme_handler->install(['seven']);
    $theme_handler->setDefault('seven');
    $block = $this->drupalPlaceBlock('system_powered_by_block', ['theme' => 'seven', 'region' => 'help']);
    $this->drupalGet('<front>');
    $this->assertText('Powered by Drupal');

486
    $theme_handler->setDefault('classy');
487 488 489 490 491 492
    $theme_handler->uninstall(['seven']);

    // Ensure that the block configuration does not exist anymore.
    $this->assertIdentical(NULL, Block::load($block->id()));
  }

493 494 495 496 497 498 499 500 501 502 503 504 505 506
  /**
   * Tests the block access.
   */
  public function testBlockAccess() {
    $this->drupalPlaceBlock('test_access', ['region' => 'help']);

    $this->drupalGet('<front>');
    $this->assertNoText('Hello test world');

    \Drupal::state()->set('test_block_access', TRUE);
    $this->drupalGet('<front>');
    $this->assertText('Hello test world');
  }

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
  /**
   * Tests block_user_role_delete.
   */
  public function testBlockUserRoleDelete() {
    $role1 = Role::create(['id' => 'test_role1', 'name' => $this->randomString()]);
    $role1->save();

    $role2 = Role::create(['id' => 'test_role2', 'name' => $this->randomString()]);
    $role2->save();

    $block = Block::create([
      'id' => $this->randomMachineName(),
      'plugin' => 'system_powered_by_block',
    ]);

    $block->setVisibilityConfig('user_role', [
      'roles' => [
        $role1->id() => $role1->id(),
        $role2->id() => $role2->id(),
      ],
    ]);

    $block->save();

    $this->assertEqual($block->getVisibility()['user_role']['roles'], [
      $role1->id() => $role1->id(),
      $role2->id() => $role2->id()
    ]);

    $role1->delete();

    $block = Block::load($block->id());
    $this->assertEqual($block->getVisibility()['user_role']['roles'], [
      $role2->id() => $role2->id()
    ]);
  }

544
}