updated core

This commit is contained in:
Bachir Soussi Chiadmi
2018-01-24 13:36:32 +01:00
parent cd7dea45a9
commit f9374cf96d
1997 changed files with 5175 additions and 127715 deletions
+3 -3
View File
@@ -6,8 +6,8 @@ package: Core
# core: 8.x
configure: entity.action.collection
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,13 +0,0 @@
id: d6_action_settings
label: Action configuration
migration_tags:
- Drupal 6
source:
plugin: variable
variables:
- actions_max_stack
process:
recursion_limit: actions_max_stack
destination:
plugin: config
config_name: action.settings
@@ -9,8 +9,8 @@ dependencies:
- views
- node
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,68 +0,0 @@
<?php
namespace Drupal\Tests\action\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests actions source plugin.
*
* @group action
*/
class ActionTest extends MigrateSqlSourceTestCase {
// The plugin system is not working during unit testing so the source plugin
// class needs to be manually specified.
const PLUGIN_CLASS = 'Drupal\action\Plugin\migrate\source\Action';
// The fake Migration configuration entity.
protected $migrationConfiguration = array(
// The ID of the entity, can be any string.
'id' => 'test',
'source' => array(
'plugin' => 'action',
),
);
// We need to set up the database contents; it's easier to do that below.
protected $expectedResults = array(
array(
'aid' => 'Redirect to node list page',
'type' => 'system',
'callback' => 'system_goto_action',
'parameters' => 'a:1:{s:3:"url";s:4:"node";}',
'description' => 'Redirect to node list page',
),
array(
'aid' => 'Test notice email',
'type' => 'system',
'callback' => 'system_send_email_action',
'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test',
'description' => 'Test notice email',
),
array(
'aid' => 'comment_publish_action',
'type' => 'comment',
'callback' => 'comment_publish_action',
'parameters' => NULL,
'description' => NULL,
),
array(
'aid' => 'node_publish_action',
'type' => 'comment',
'callback' => 'node_publish_action',
'parameters' => NULL,
'description' => NULL,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['actions'] = $this->expectedResults;
parent::setUp();
}
}
+3 -3
View File
@@ -9,8 +9,8 @@ dependencies:
- file
- options
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -40,3 +40,13 @@ function aggregator_update_8200() {
$field_definition->setRequired(TRUE);
$definition_update_manager->updateFieldStorageDefinition($field_definition);
}
/**
* Add a default value for the 'Refresh' field for aggregator feed entities.
*/
function aggregator_update_8501() {
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$field_definition = $definition_update_manager->getFieldStorageDefinition('refresh', 'aggregator_feed');
$field_definition->setDefaultValue(3600);
$definition_update_manager->updateFieldStorageDefinition($field_definition);
}
@@ -47,10 +47,7 @@ class AggregatorController extends ControllerBase {
* A form array as expected by drupal_render().
*/
public function feedAdd() {
$feed = $this->entityManager()->getStorage('aggregator_feed')
->create([
'refresh' => 3600,
]);
$feed = $this->entityManager()->getStorage('aggregator_feed')->create();
return $this->entityFormBuilder()->getForm($feed);
}
@@ -168,6 +168,7 @@ class Feed extends ContentEntityBase implements FeedInterface {
$fields['refresh'] = BaseFieldDefinition::create('list_integer')
->setLabel(t('Update interval'))
->setDescription(t('The length of time between feed updates. Requires a correctly configured cron maintenance task.'))
->setDefaultValue(3600)
->setSetting('unsigned', TRUE)
->setRequired(TRUE)
->setSetting('allowed_values', $period)
@@ -1,95 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Add feed test.
*
* @group aggregator
*/
class AddFeedTest extends AggregatorTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Creates and ensures that a feed is unique, checks source, and deletes feed.
*/
public function testAddFeed() {
$feed = $this->createFeed();
$feed->refreshItems();
// Check feed data.
$this->assertUrl(\Drupal::url('aggregator.feed_add', [], ['absolute' => TRUE]), [], 'Directed to correct URL.');
$this->assertTrue($this->uniqueFeed($feed->label(), $feed->getUrl()), 'The feed is unique.');
// Check feed source.
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, 'Feed source exists.');
$this->assertText($feed->label(), 'Page title');
$this->assertRaw($feed->getWebsiteUrl());
// Try to add a duplicate.
$edit = [
'title[0][value]' => $feed->label(),
'url[0][value]' => $feed->getUrl(),
'refresh' => '900',
];
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
$this->assertRaw(t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $feed->label())));
$this->assertRaw(t('A feed with this URL %url already exists. Enter a unique URL.', array('%url' => $feed->getUrl())));
// Delete feed.
$this->deleteFeed($feed);
}
/**
* Ensures that the feed label is escaping when rendering the feed icon.
*/
public function testFeedLabelEscaping() {
$feed = $this->createFeed(NULL, ['title[0][value]' => 'Test feed title <script>alert(123);</script>']);
$this->checkForMetaRefresh();
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200);
$this->assertEscaped('Test feed title <script>alert(123);</script>');
$this->assertNoRaw('Test feed title <script>alert(123);</script>');
// Ensure the feed icon title is escaped.
$this->assertTrue(strpos(str_replace(["\n", "\r"], '', $this->getRawContent()), 'class="feed-icon"> Subscribe to Test feed title &lt;script&gt;alert(123);&lt;/script&gt; feed</a>') !== FALSE);
}
/**
* Tests feeds with very long URLs.
*/
public function testAddLongFeed() {
// Create a feed with a URL of > 255 characters.
$long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb";
$feed = $this->createFeed($long_url);
$feed->refreshItems();
// Create a second feed of > 255 characters, where the only difference is
// after the 255th character.
$long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889";
$feed_2 = $this->createFeed($long_url_2);
$feed->refreshItems();
// Check feed data.
$this->assertTrue($this->uniqueFeed($feed->label(), $feed->getUrl()), 'The first long URL feed is unique.');
$this->assertTrue($this->uniqueFeed($feed_2->label(), $feed_2->getUrl()), 'The second long URL feed is unique.');
// Check feed source.
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, 'Long URL feed source exists.');
$this->assertText($feed->label(), 'Page title');
// Delete feeds.
$this->deleteFeed($feed);
$this->deleteFeed($feed_2);
}
}
@@ -1,84 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Tests aggregator admin pages.
*
* @group aggregator
*/
class AggregatorAdminTest extends AggregatorTestBase {
/**
* Tests the settings form to ensure the correct default values are used.
*/
public function testSettingsPage() {
$this->drupalGet('admin/config');
$this->clickLink('Feed aggregator');
$this->clickLink('Settings');
// Make sure that test plugins are present.
$this->assertText('Test fetcher');
$this->assertText('Test parser');
$this->assertText('Test processor');
// Set new values and enable test plugins.
$edit = array(
'aggregator_allowed_html_tags' => '<a>',
'aggregator_summary_items' => 10,
'aggregator_clear' => 3600,
'aggregator_teaser_length' => 200,
'aggregator_fetcher' => 'aggregator_test_fetcher',
'aggregator_parser' => 'aggregator_test_parser',
'aggregator_processors[aggregator_test_processor]' => 'aggregator_test_processor',
);
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
foreach ($edit as $name => $value) {
$this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', array('@name' => $name)));
}
// Check for our test processor settings form.
$this->assertText(t('Dummy length setting'));
// Change its value to ensure that settingsSubmit is called.
$edit = array(
'dummy_length' => 100,
);
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$this->assertFieldByName('dummy_length', 100, '"dummy_length" has correct default value.');
// Make sure settings form is still accessible even after uninstalling a module
// that provides the selected plugins.
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
$this->resetAll();
$this->drupalGet('admin/config/services/aggregator/settings');
$this->assertResponse(200);
}
/**
* Tests the overview page.
*/
function testOverviewPage() {
$feed = $this->createFeed($this->getRSS091Sample());
$this->drupalGet('admin/config/services/aggregator');
$result = $this->xpath('//table/tbody/tr');
// Check if the amount of feeds in the overview matches the amount created.
$this->assertEqual(1, count($result), 'Created feed is found in the overview');
// Check if the fields in the table match with what's expected.
$this->assertEqual($feed->label(), (string) $result[0]->td[0]->a);
$count = $this->container->get('entity.manager')->getStorage('aggregator_item')->getItemCount($feed);
$this->assertEqual(\Drupal::translation()->formatPlural($count, '1 item', '@count items'), (string) $result[0]->td[1]);
// Update the items of the first feed.
$feed->refreshItems();
$this->drupalGet('admin/config/services/aggregator');
$result = $this->xpath('//table/tbody/tr');
// Check if the fields in the table match with what's expected.
$this->assertEqual($feed->label(), (string) $result[0]->td[0]->a);
$count = $this->container->get('entity.manager')->getStorage('aggregator_item')->getItemCount($feed);
$this->assertEqual(\Drupal::translation()->formatPlural($count, '1 item', '@count items'), (string) $result[0]->td[1]);
}
}
@@ -1,45 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Update feeds on cron.
*
* @group aggregator
*/
class AggregatorCronTest extends AggregatorTestBase {
/**
* Adds feeds and updates them via cron process.
*/
public function testCron() {
// Create feed and test basic updating on cron.
$this->createSampleNodes();
$feed = $this->createFeed();
$this->cronRun();
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
$this->deleteFeedItems($feed);
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
$this->cronRun();
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
// Test feed locking when queued for update.
$this->deleteFeedItems($feed);
db_update('aggregator_feed')
->condition('fid', $feed->id())
->fields(array(
'queued' => REQUEST_TIME,
))
->execute();
$this->cronRun();
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
db_update('aggregator_feed')
->condition('fid', $feed->id())
->fields(array(
'queued' => 0,
))
->execute();
$this->cronRun();
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
}
}
@@ -1,149 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\Component\Utility\SafeMarkup;
/**
* Tests display of aggregator items on the page.
*
* @group aggregator
*/
class AggregatorRenderingTest extends AggregatorTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'test_page_test');
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Adds a feed block to the page and checks its links.
*/
public function testBlockLinks() {
// Create feed.
$this->createSampleNodes();
$feed = $this->createFeed();
$this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
// Need admin user to be able to access block admin.
$admin_user = $this->drupalCreateUser(array(
'administer blocks',
'access administration pages',
'administer news feeds',
'access news feeds',
));
$this->drupalLogin($admin_user);
$block = $this->drupalPlaceBlock("aggregator_feed_block", array('label' => 'feed-' . $feed->label()));
// Configure the feed that should be displayed.
$block->getPlugin()->setConfigurationValue('feed', $feed->id());
$block->getPlugin()->setConfigurationValue('block_count', 2);
$block->save();
// Confirm that the block is now being displayed on pages.
$this->drupalGet('test-page');
$this->assertText($block->label(), 'Feed block is displayed on the page.');
// Confirm items appear as links.
$items = $this->container->get('entity.manager')->getStorage('aggregator_item')->loadByFeed($feed->id(), 1);
$links = $this->xpath('//a[@href = :href]', array(':href' => reset($items)->getLink()));
$this->assert(isset($links[0]), 'Item link found.');
// Find the expected read_more link.
$href = $feed->url();
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
$this->assert(isset($links[0]), format_string('Link to href %href found.', array('%href' => $href)));
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$cache_tags = explode(' ', $cache_tags_header);
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
// Visit that page.
$this->drupalGet($feed->urlInfo()->getInternalPath());
$correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->label()));
$this->assertFalse(empty($correct_titles), 'Aggregator feed page is available and has the correct title.');
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
$this->assertTrue(in_array('aggregator_feed_view', $cache_tags));
$this->assertTrue(in_array('aggregator_item_view', $cache_tags));
// Set the number of news items to 0 to test that the block does not show
// up.
$block->getPlugin()->setConfigurationValue('block_count', 0);
$block->save();
// Check that the block is no longer displayed.
$this->drupalGet('test-page');
$this->assertNoText($block->label(), 'Feed block is not displayed on the page when number of items is set to 0.');
}
/**
* Creates a feed and checks that feed's page.
*/
public function testFeedPage() {
// Increase the number of items published in the rss.xml feed so we have
// enough articles to test paging.
$view = entity_load('view', 'frontpage');
$display = &$view->getDisplay('feed_1');
$display['display_options']['pager']['options']['items_per_page'] = 30;
$view->save();
// Create a feed with 30 items.
$this->createSampleNodes(30);
$feed = $this->createFeed();
$this->updateFeedItems($feed, 30);
// Check for presence of an aggregator pager.
$this->drupalGet('aggregator');
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
// Check for sources page title.
$this->drupalGet('aggregator/sources');
$titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => 'Sources'));
$this->assertTrue(!empty($titles), 'Source page contains correct title.');
// Find the expected read_more link on the sources page.
$href = $feed->url();
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
$this->assertTrue(isset($links[0]), SafeMarkup::format('Link to href %href found.', array('%href' => $href)));
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$cache_tags = explode(' ', $cache_tags_header);
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
// Check the rss aggregator page as anonymous user.
$this->drupalLogout();
$this->drupalGet('aggregator/rss');
$this->assertResponse(403);
// Check the rss aggregator page as admin.
$this->drupalLogin($this->adminUser);
$this->drupalGet('aggregator/rss');
$this->assertResponse(200);
$this->assertEqual($this->drupalGetHeader('Content-type'), 'application/rss+xml; charset=utf-8');
// Check the opml aggregator page.
$this->drupalGet('aggregator/opml');
$outline = $this->xpath('//outline[1]');
$this->assertEqual($outline[0]['type'], 'rss', 'The correct type attribute is used for rss OPML.');
$this->assertEqual($outline[0]['text'], $feed->label(), 'The correct text attribute is used for rss OPML.');
$this->assertEqual($outline[0]['xmlurl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
// Check for the presence of a pager.
$this->drupalGet('aggregator/sources/' . $feed->id());
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
$this->assertTrue(in_array('aggregator_feed_view', $cache_tags));
$this->assertTrue(in_array('aggregator_item_view', $cache_tags));
}
}
@@ -1,40 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Delete feed items from a feed.
*
* @group aggregator
*/
class DeleteFeedItemTest extends AggregatorTestBase {
/**
* Tests running "delete items" from 'admin/config/services/aggregator' page.
*/
public function testDeleteFeedItem() {
// Create a bunch of test feeds.
$feed_urls = array();
// No last-modified, no etag.
$feed_urls[] = \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE));
// Last-modified, but no etag.
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1), array('absolute' => TRUE));
// No Last-modified, but etag.
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 0, 'use_etag' => 1), array('absolute' => TRUE));
// Last-modified and etag.
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1, 'use_etag' => 1), array('absolute' => TRUE));
foreach ($feed_urls as $feed_url) {
$feed = $this->createFeed($feed_url);
// Update and delete items two times in a row to make sure that removal
// resets all 'modified' information (modified, etag, hash) and allows for
// immediate update. There's 8 items in the feed, but one has an empty
// title and is skipped.
$this->updateAndDelete($feed, 7);
$this->updateAndDelete($feed, 7);
$this->updateAndDelete($feed, 7);
// Delete feed.
$this->deleteFeed($feed);
}
}
}
@@ -1,50 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Delete feed test.
*
* @group aggregator
*/
class DeleteFeedTest extends AggregatorTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block');
/**
* Deletes a feed and ensures that all of its services are deleted.
*/
public function testDeleteFeed() {
$feed1 = $this->createFeed();
$feed2 = $this->createFeed();
// Place a block for both feeds.
$block = $this->drupalPlaceBlock('aggregator_feed_block');
$block->getPlugin()->setConfigurationValue('feed', $feed1->id());
$block->save();
$block2 = $this->drupalPlaceBlock('aggregator_feed_block');
$block2->getPlugin()->setConfigurationValue('feed', $feed2->id());
$block2->save();
// Delete feed.
$this->deleteFeed($feed1);
$this->assertText($feed2->label());
$block_storage = $this->container->get('entity.manager')->getStorage('block');
$this->assertNull($block_storage->load($block->id()), 'Block for the deleted feed was deleted.');
$this->assertEqual($block2->id(), $block_storage->load($block2->id())->id(), 'Block for not deleted feed still exists.');
// Check feed source.
$this->drupalGet('aggregator/sources/' . $feed1->id());
$this->assertResponse(404, 'Deleted feed source does not exists.');
// Check database for feed.
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed1->label(), ':url' => $feed1->getUrl()))->fetchField();
$this->assertFalse($result, 'Feed not found in database');
}
}
@@ -1,63 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Tests the display of a feed on the feed aggregator list page.
*
* @group aggregator
*/
class FeedAdminDisplayTest extends AggregatorTestBase {
/**
* Tests the "Next update" and "Last update" fields.
*/
public function testFeedUpdateFields() {
// Create scheduled feed.
$scheduled_feed = $this->createFeed(NULL, array('refresh' => '900'));
$this->drupalGet('admin/config/services/aggregator');
$this->assertResponse(200, 'Aggregator feed overview page exists.');
// The scheduled feed shows that it has not been updated yet and is
// scheduled.
$this->assertText('never', 'The scheduled feed has not been updated yet. Last update shows "never".');
$this->assertText('imminently', 'The scheduled feed has not been updated yet. Next update shows "imminently".');
$this->assertNoText('ago', 'The scheduled feed has not been updated yet. Last update does not show "x x ago".');
$this->assertNoText('left', 'The scheduled feed has not been updated yet. Next update does not show "x x left".');
$this->updateFeedItems($scheduled_feed);
$this->drupalGet('admin/config/services/aggregator');
// After the update, an interval should be displayed on both last updated
// and next update.
$this->assertNoText('never', 'The scheduled feed has been updated. Last updated changed.');
$this->assertNoText('imminently', 'The scheduled feed has been updated. Next update changed.');
$this->assertText('ago', 'The scheduled feed been updated. Last update shows "x x ago".');
$this->assertText('left', 'The scheduled feed has been updated. Next update shows "x x left".');
// Delete scheduled feed.
$this->deleteFeed($scheduled_feed);
// Create non-scheduled feed.
$non_scheduled_feed = $this->createFeed(NULL, array('refresh' => '0'));
$this->drupalGet('admin/config/services/aggregator');
// The non scheduled feed shows that it has not been updated yet.
$this->assertText('never', 'The non scheduled feed has not been updated yet. Last update shows "never".');
$this->assertNoText('imminently', 'The non scheduled feed does not show "imminently" as next update.');
$this->assertNoText('ago', 'The non scheduled feed has not been updated. It does not show "x x ago" as last update.');
$this->assertNoText('left', 'The feed is not scheduled. It does not show a timeframe "x x left" for next update.');
$this->updateFeedItems($non_scheduled_feed);
$this->drupalGet('admin/config/services/aggregator');
// After the feed update, we still need to see "never" as next update label.
// Last update will show an interval.
$this->assertNoText('imminently', 'The updated non scheduled feed does not show "imminently" as next update.');
$this->assertText('never', 'The updated non scheduled feed still shows "never" as next update.');
$this->assertText('ago', 'The non scheduled feed has been updated. It shows "x x ago" as last update.');
$this->assertNoText('left', 'The feed is not scheduled. It does not show a timeframe "x x left" for next update.');
}
}
@@ -1,52 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\aggregator\Entity\Feed;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the Feed entity's cache tags.
*
* @group aggregator
*/
class FeedCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('aggregator');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Give anonymous users permission to access feeds, so that we can verify
// the cache tags of cached versions of feeds.
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
$user_role->grantPermission('access news feeds');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" feed.
$feed = Feed::create(array(
'title' => 'Llama',
'url' => 'https://www.drupal.org/',
'refresh' => 900,
'checked' => 1389919932,
'description' => 'Drupal.org',
));
$feed->save();
return $feed;
}
}
@@ -1,44 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Tests the fetcher plugins functionality and discoverability.
*
* @group aggregator
*
* @see \Drupal\aggregator_test\Plugin\aggregator\fetcher\TestFetcher.
*/
class FeedFetcherPluginTest extends AggregatorTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable test plugins.
$this->enableTestPlugins();
// Create some nodes.
$this->createSampleNodes();
}
/**
* Test fetching functionality.
*/
public function testfetch() {
// Create feed with local url.
$feed = $this->createFeed();
$this->updateFeedItems($feed);
$this->assertFalse(empty($feed->items));
// Delete items and restore checked property to 0.
$this->deleteFeedItems($feed);
// Change its name and try again.
$feed->setTitle('Do not fetch');
$feed->save();
$this->updateFeedItems($feed);
// Fetch should fail due to feed name.
$this->assertTrue(empty($feed->items));
}
}
@@ -1,85 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests aggregator feeds in multiple languages.
*
* @group aggregator
*/
class FeedLanguageTest extends AggregatorTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('language');
/**
* List of langcodes.
*
* @var string[]
*/
protected $langcodes = array();
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create test languages.
$this->langcodes = array(ConfigurableLanguage::load('en'));
for ($i = 1; $i < 3; ++$i) {
$language = ConfigurableLanguage::create(array(
'id' => 'l' . $i,
'label' => $this->randomString(),
));
$language->save();
$this->langcodes[$i] = $language->id();
}
}
/**
* Tests creation of feeds with a language.
*/
public function testFeedLanguage() {
$admin_user = $this->drupalCreateUser(['administer languages', 'access administration pages', 'administer news feeds', 'access news feeds', 'create article content']);
$this->drupalLogin($admin_user);
// Enable language selection for feeds.
$edit['entity_types[aggregator_feed]'] = TRUE;
$edit['settings[aggregator_feed][aggregator_feed][settings][language][language_alterable]'] = TRUE;
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
/** @var \Drupal\aggregator\FeedInterface[] $feeds */
$feeds = array();
// Create feeds.
$feeds[1] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[1]));
$feeds[2] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[2]));
// Make sure that the language has been assigned.
$this->assertEqual($feeds[1]->language()->getId(), $this->langcodes[1]);
$this->assertEqual($feeds[2]->language()->getId(), $this->langcodes[2]);
// Create example nodes to create feed items from and then update the feeds.
$this->createSampleNodes();
$this->cronRun();
// Loop over the created feed items and verify that their language matches
// the one from the feed.
foreach ($feeds as $feed) {
/** @var \Drupal\aggregator\ItemInterface[] $items */
$items = entity_load_multiple_by_properties('aggregator_item', array('fid' => $feed->id()));
$this->assertTrue(count($items) > 0, 'Feed items were created.');
foreach ($items as $item) {
$this->assertEqual($item->language()->getId(), $feed->language()->getId());
}
}
}
}
@@ -1,111 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\Core\Url;
use Drupal\aggregator\Entity\Feed;
/**
* Tests the built-in feed parser with valid feed samples.
*
* @group aggregator
*/
class FeedParserTest extends AggregatorTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Do not delete old aggregator items during these tests, since our sample
// feeds have hardcoded dates in them (which may be expired when this test
// is run).
$this->config('aggregator.settings')->set('items.expire', AGGREGATOR_CLEAR_NEVER)->save();
}
/**
* Tests a feed that uses the RSS 0.91 format.
*/
public function testRSS091Sample() {
$feed = $this->createFeed($this->getRSS091Sample());
$feed->refreshItems();
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
$this->assertText('First example feed item title');
$this->assertLinkByHref('http://example.com/example-turns-one');
$this->assertText('First example feed item description.');
$this->assertRaw('<img src="http://example.com/images/druplicon.png"');
// Several additional items that include elements over 255 characters.
$this->assertRaw("Second example feed item title.");
$this->assertText('Long link feed item title');
$this->assertText('Long link feed item description');
$this->assertLinkByHref('http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing');
$this->assertText('Long author feed item title');
$this->assertText('Long author feed item description');
$this->assertLinkByHref('http://example.com/long/author');
}
/**
* Tests a feed that uses the Atom format.
*/
public function testAtomSample() {
$feed = $this->createFeed($this->getAtomSample());
$feed->refreshItems();
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
$this->assertText('Atom-Powered Robots Run Amok');
$this->assertLinkByHref('http://example.org/2003/12/13/atom03');
$this->assertText('Some text.');
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.');
// Check for second feed entry.
$this->assertText('We tried to stop them, but we failed.');
$this->assertLinkByHref('http://example.org/2003/12/14/atom03');
$this->assertText('Some other text.');
$db_guid = db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(
':link' => 'http://example.org/2003/12/14/atom03',
))->fetchField();
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $db_guid, 'Atom entry id element is parsed correctly.');
}
/**
* Tests a feed that uses HTML entities in item titles.
*/
public function testHtmlEntitiesSample() {
$feed = $this->createFeed($this->getHtmlEntitiesSample());
$feed->refreshItems();
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
$this->assertRaw("Quote&quot; Amp&amp;");
}
/**
* Tests that a redirected feed is tracked to its target.
*/
public function testRedirectFeed() {
$redirect_url = Url::fromRoute('aggregator_test.redirect')->setAbsolute()->toString();
$feed = Feed::create(array('url' => $redirect_url, 'title' => $this->randomMachineName()));
$feed->save();
$feed->refreshItems();
// Make sure that the feed URL was updated correctly.
$this->assertEqual($feed->getUrl(), \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE)));
}
/**
* Tests error handling when an invalid feed is added.
*/
public function testInvalidFeed() {
// Simulate a typo in the URL to force a curl exception.
$invalid_url = 'http:/www.drupal.org';
$feed = Feed::create(array('url' => $invalid_url, 'title' => $this->randomMachineName()));
$feed->save();
// Update the feed. Use the UI to be able to check the message easily.
$this->drupalGet('admin/config/services/aggregator');
$this->clickLink(t('Update items'));
$this->assertRaw(t('The feed from %title seems to be broken because of error', array('%title' => $feed->label())));
}
}
@@ -1,67 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\aggregator\Entity\Feed;
use Drupal\aggregator\Entity\Item;
/**
* Tests the processor plugins functionality and discoverability.
*
* @group aggregator
*
* @see \Drupal\aggregator_test\Plugin\aggregator\processor\TestProcessor.
*/
class FeedProcessorPluginTest extends AggregatorTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable test plugins.
$this->enableTestPlugins();
// Create some nodes.
$this->createSampleNodes();
}
/**
* Test processing functionality.
*/
public function testProcess() {
$feed = $this->createFeed();
$this->updateFeedItems($feed);
foreach ($feed->items as $iid) {
$item = Item::load($iid);
$this->assertTrue(strpos($item->label(), 'testProcessor') === 0);
}
}
/**
* Test deleting functionality.
*/
public function testDelete() {
$feed = $this->createFeed();
$description = $feed->description->value ?: '';
$this->updateAndDelete($feed, NULL);
// Make sure the feed title is changed.
$entities = entity_load_multiple_by_properties('aggregator_feed', array('description' => $description));
$this->assertTrue(empty($entities));
}
/**
* Test post-processing functionality.
*/
public function testPostProcess() {
$feed = $this->createFeed(NULL, array('refresh' => 1800));
$this->updateFeedItems($feed);
$feed_id = $feed->id();
// Reset entity cache manually.
\Drupal::entityManager()->getStorage('aggregator_feed')->resetCache(array($feed_id));
// Reload the feed to get new values.
$feed = Feed::load($feed_id);
// Make sure its refresh rate doubled.
$this->assertEqual($feed->getRefreshRate(), 3600);
}
}
@@ -1,124 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Tests OPML import.
*
* @group aggregator
*/
class ImportOpmlTest extends AggregatorTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'help');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content', 'administer blocks'));
$this->drupalLogin($admin_user);
}
/**
* Opens OPML import form.
*/
public function openImportForm() {
// Enable the help block.
$this->drupalPlaceBlock('help_block', array('region' => 'help'));
$this->drupalGet('admin/config/services/aggregator/add/opml');
$this->assertText('A single OPML document may contain many feeds.', 'Found OPML help text.');
$this->assertField('files[upload]', 'Found file upload field.');
$this->assertField('remote', 'Found Remote URL field.');
$this->assertField('refresh', '', 'Found Refresh field.');
}
/**
* Submits form filled with invalid fields.
*/
public function validateImportFormFields() {
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
$edit = array();
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if no fields are filled.');
$path = $this->getEmptyOpml();
$edit = array(
'files[upload]' => $path,
'remote' => file_create_url($path),
);
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if both fields are filled.');
$edit = array('remote' => 'invalidUrl://empty');
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
$this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.');
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
$this->assertEqual($before, $after, 'No feeds were added during the three last form submissions.');
}
/**
* Submits form with invalid, empty, and valid OPML files.
*/
protected function submitImportForm() {
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
$form['files[upload]'] = $this->getInvalidOpml();
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $form, t('Import'));
$this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.');
$edit = array('remote' => file_create_url($this->getEmptyOpml()));
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
$this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
$this->assertEqual($before, $after, 'No feeds were added during the two last form submissions.');
db_delete('aggregator_feed')->execute();
$feeds[0] = $this->getFeedEditArray();
$feeds[1] = $this->getFeedEditArray();
$feeds[2] = $this->getFeedEditArray();
$edit = array(
'files[upload]' => $this->getValidOpml($feeds),
'refresh' => '900',
);
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
$this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url[0][value]'])), 'Verifying that a duplicate URL was identified');
$this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title[0][value]'])), 'Verifying that a duplicate title was identified');
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
$this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
$feeds_from_db = db_query("SELECT title, url, refresh FROM {aggregator_feed}");
$refresh = TRUE;
foreach ($feeds_from_db as $feed) {
$title[$feed->url] = $feed->title;
$url[$feed->title] = $feed->url;
$refresh = $refresh && $feed->refresh == 900;
}
$this->assertEqual($title[$feeds[0]['url[0][value]']], $feeds[0]['title[0][value]'], 'First feed was added correctly.');
$this->assertEqual($url[$feeds[1]['title[0][value]']], $feeds[1]['url[0][value]'], 'Second feed was added correctly.');
$this->assertTrue($refresh, 'Refresh times are correct.');
}
/**
* Tests the import of an OPML file.
*/
public function testOpmlImport() {
$this->openImportForm();
$this->validateImportFormFields();
$this->submitImportForm();
}
}
@@ -1,83 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\aggregator\Entity\Feed;
use Drupal\aggregator\Entity\Item;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the Item entity's cache tags.
*
* @group aggregator
*/
class ItemCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('aggregator');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Give anonymous users permission to access feeds, so that we can verify
// the cache tags of cached versions of feed items.
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
$user_role->grantPermission('access news feeds');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" feed.
$feed = Feed::create(array(
'title' => 'Camelids',
'url' => 'https://groups.drupal.org/not_used/167169',
'refresh' => 900,
'checked' => 1389919932,
'description' => 'Drupal Core Group feed',
));
$feed->save();
// Create a "Llama" aggregator feed item.
$item = Item::create(array(
'fid' => $feed->id(),
'title' => t('Llama'),
'path' => 'https://www.drupal.org/',
));
$item->save();
return $item;
}
/**
* Tests that when creating a feed item, the feed tag is invalidated.
*/
public function testEntityCreation() {
// Create a cache entry that is tagged with a feed cache tag.
\Drupal::cache('render')->set('foo', 'bar', CacheBackendInterface::CACHE_PERMANENT, $this->entity->getCacheTags());
// Verify a cache hit.
$this->verifyRenderCache('foo', array('aggregator_feed:1'));
// Now create a feed item in that feed.
Item::create(array(
'fid' => $this->entity->getFeedId(),
'title' => t('Llama 2'),
'path' => 'https://groups.drupal.org/',
))->save();
// Verify a cache miss.
$this->assertFalse(\Drupal::cache('render')->get('foo'), 'Creating a new feed item invalidates the cache tag of the feed.');
}
}
@@ -1,41 +0,0 @@
<?php
namespace Drupal\aggregator\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests that node settings are properly updated during database updates.
*
* @group aggregator
*/
class AggregatorUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
];
}
/**
* Tests that the 'Source feed' field is required.
*
* @see aggregator_update_8200()
*/
public function testSourceFeedRequired() {
// Check that the 'fid' field is not required prior to the update.
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('fid', 'aggregator_item');
$this->assertFalse($field_definition->isRequired());
// Run updates.
$this->runUpdates();
// Check that the 'fid' field is now required.
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('fid', 'aggregator_item');
$this->assertTrue($field_definition->isRequired());
}
}
@@ -1,70 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
use Drupal\aggregator\Entity\Feed;
/**
* Update feed items from a feed.
*
* @group aggregator
*/
class UpdateFeedItemTest extends AggregatorTestBase {
/**
* Tests running "update items" from 'admin/config/services/aggregator' page.
*/
public function testUpdateFeedItem() {
$this->createSampleNodes();
// Create a feed and test updating feed items if possible.
$feed = $this->createFeed();
if (!empty($feed)) {
$this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
$this->deleteFeedItems($feed);
}
// Delete feed.
$this->deleteFeed($feed);
// Test updating feed items without valid timestamp information.
$edit = array(
'title[0][value]' => "Feed without publish timestamp",
'url[0][value]' => $this->getRSS091Sample(),
);
$this->drupalGet($edit['url[0][value]']);
$this->assertResponse(200);
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url[0][value]']))->fetchField();
$feed = Feed::load($fid);
$feed->refreshItems();
$before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
// Sleep for 3 second.
sleep(3);
db_update('aggregator_feed')
->condition('fid', $feed->id())
->fields(array(
'checked' => 0,
'hash' => '',
'etag' => '',
'modified' => 0,
))
->execute();
$feed->refreshItems();
$after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
$this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (@before === @after)', array('@before' => $before, '@after' => $after)));
// Make sure updating items works even after uninstalling a module
// that provides the selected plugins.
$this->enableTestPlugins();
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
$this->updateFeedItems($feed);
$this->assertResponse(200);
}
}
@@ -1,46 +0,0 @@
<?php
namespace Drupal\aggregator\Tests;
/**
* Update feed test.
*
* @group aggregator
*/
class UpdateFeedTest extends AggregatorTestBase {
/**
* Creates a feed and attempts to update it.
*/
public function testUpdateFeed() {
$remaining_fields = array('title[0][value]', 'url[0][value]', '');
foreach ($remaining_fields as $same_field) {
$feed = $this->createFeed();
// Get new feed data array and modify newly created feed.
$edit = $this->getFeedEditArray();
// Change refresh value.
$edit['refresh'] = 1800;
if (isset($feed->{$same_field}->value)) {
$edit[$same_field] = $feed->{$same_field}->value;
}
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/configure', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])), format_string('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])));
// Check feed data.
$this->assertUrl($feed->url('canonical', ['absolute' => TRUE]));
$this->assertTrue($this->uniqueFeed($edit['title[0][value]'], $edit['url[0][value]']), 'The feed is unique.');
// Check feed source.
$this->drupalGet('aggregator/sources/' . $feed->id());
$this->assertResponse(200, 'Feed source exists.');
$this->assertText($edit['title[0][value]'], 'Page title');
// Set correct title so deleteFeed() will work.
$feed->title = $edit['title[0][value]'];
// Delete feed.
$this->deleteFeed($feed);
}
}
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -8,8 +8,8 @@ dependencies:
- aggregator
- views
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -38,4 +38,21 @@ class AggregatorUpdateTest extends UpdatePathTestBase {
$this->assertTrue($field_definition->isRequired());
}
/**
* Tests that the 'Update interval' field has a default value.
*/
public function testUpdateIntervalDefaultValue() {
// Check that the 'refresh' field does not have a default value prior to the
// update.
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('refresh', 'aggregator_feed');
$this->assertSame([], $field_definition->getDefaultValueLiteral());
// Run updates.
$this->runUpdates();
// Check that the 'refresh' has a default value now.
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('refresh', 'aggregator_feed');
$this->assertSame([['value' => 3600]], $field_definition->getDefaultValueLiteral());
}
}
@@ -109,5 +109,6 @@ class AggregatorPluginSettingsBaseTest extends UnitTestCase {
namespace Drupal\Core\Form;
if (!function_exists('drupal_set_message')) {
function drupal_set_message() {}
function drupal_set_message() {
}
}
@@ -1,44 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests aggregator item source plugin.
*
* @group aggregator
*/
class AggregatorItemTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorItem';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'aggregator_item',
),
);
protected $expectedResults = array(
array(
'iid' => 1,
'fid' => 1,
'title' => 'This (three) weeks in Drupal Core - January 10th 2014',
'link' => 'https://groups.drupal.org/node/395218',
'author' => 'larowlan',
'description' => "<h2 id='new'>What's new with Drupal 8?</h2>",
'timestamp' => 1389297196,
'guid' => '395218 at https://groups.drupal.org',
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_item'] = $this->expectedResults;
parent::setUp();
}
}
@@ -1,60 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D6 aggregator feed source plugin.
*
* @group aggregator
*/
class AggregatorFeedTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorFeed';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd6_aggregator_feed',
),
);
protected $expectedResults = array(
array(
'fid' => 1,
'title' => 'feed title 1',
'url' => 'http://example.com/feed.rss',
'refresh' => 900,
'checked' => 0,
'link' => 'http://example.com',
'description' => 'A vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
array(
'fid' => 2,
'title' => 'feed title 2',
'url' => 'http://example.net/news.rss',
'refresh' => 1800,
'checked' => 0,
'link' => 'http://example.net',
'description' => 'An even more vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_feed'] = $this->expectedResults;
parent::setUp();
}
}
@@ -1,62 +0,0 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D7 aggregator feed source plugin.
*
* @group aggregator
*/
class AggregatorFeedTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\aggregator\Plugin\migrate\source\AggregatorFeed';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'd7_aggregator_feed',
),
);
protected $expectedResults = array(
array(
'fid' => 1,
'title' => 'feed title 1',
'url' => 'http://example.com/feed.rss',
'refresh' => 900,
'checked' => 0,
'queued' => 0,
'link' => 'http://example.com',
'description' => 'A vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
array(
'fid' => 2,
'title' => 'feed title 2',
'url' => 'http://example.net/news.rss',
'refresh' => 1800,
'checked' => 0,
'queued' => 0,
'link' => 'http://example.net',
'description' => 'An even more vague description',
'image' => '',
'etag' => '',
'modified' => 0,
'block' => 5,
),
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['aggregator_feed'] = $this->expectedResults;
parent::setUp();
}
}
@@ -6,8 +6,8 @@ package: Core
# core: 8.x
configure: system.cron_settings
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
+3 -3
View File
@@ -6,8 +6,8 @@ package: Core
# core: 8.x
configure: ban.admin_page
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,43 +0,0 @@
<?php
namespace Drupal\Tests\ban\Unit\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D7 blocked_ip source plugin.
*
* @coversDefaultClass \Drupal\ban\Plugin\migrate\source\d7\BlockedIps
* @group ban
*/
class BlockedIpsTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\ban\Plugin\migrate\source\d7\BlockedIps';
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd7_blocked_ips',
],
];
protected $expectedResults = [
[
'ip' => '127.0.0.1',
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['blocked_ips'] = [
[
'iid' => 1,
'ip' => '127.0.0.1',
]
];
parent::setUp();
}
}
+3 -3
View File
@@ -7,8 +7,8 @@ package: Web services
dependencies:
- user
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,178 +0,0 @@
<?php
namespace Drupal\basic_auth\Tests\Authentication;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Url;
use Drupal\basic_auth\Tests\BasicAuthTestTrait;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests for BasicAuth authentication provider.
*
* @group basic_auth
*/
class BasicAuthTest extends WebTestBase {
use BasicAuthTestTrait;
/**
* Modules installed for all tests.
*
* @var array
*/
public static $modules = array('basic_auth', 'router_test', 'locale');
/**
* Test http basic authentication.
*/
public function testBasicAuth() {
// Enable page caching.
$config = $this->config('system.performance');
$config->set('cache.page.max_age', 300);
$config->save();
$account = $this->drupalCreateUser();
$url = Url::fromRoute('router_test.11');
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
$this->assertText($account->getUsername(), 'Account name is displayed.');
$this->assertResponse('200', 'HTTP response is OK');
$this->curlClose();
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
$this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Cache-Control is not set to public');
$this->basicAuthGet($url, $account->getUsername(), $this->randomMachineName());
$this->assertNoText($account->getUsername(), 'Bad basic auth credentials do not authenticate the user.');
$this->assertResponse('403', 'Access is not granted.');
$this->curlClose();
$this->drupalGet($url);
$this->assertEqual($this->drupalGetHeader('WWW-Authenticate'), SafeMarkup::format('Basic realm="@realm"', ['@realm' => \Drupal::config('system.site')->get('name')]));
$this->assertResponse('401', 'Not authenticated on the route that allows only basic_auth. Prompt to authenticate received.');
$this->drupalGet('admin');
$this->assertResponse('403', 'No authentication prompt for routes not explicitly defining authentication providers.');
$account = $this->drupalCreateUser(array('access administration pages'));
$this->basicAuthGet(Url::fromRoute('system.admin'), $account->getUsername(), $account->pass_raw);
$this->assertNoLink('Log out', 'User is not logged in');
$this->assertResponse('403', 'No basic authentication for routes not explicitly defining authentication providers.');
$this->curlClose();
// Ensure that pages already in the page cache aren't returned from page
// cache if basic auth credentials are provided.
$url = Url::fromRoute('router_test.10');
$this->drupalGet($url);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
$this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'No page cache response when requesting a cached page with basic auth credentials.');
}
/**
* Test the global login flood control.
*/
function testGlobalLoginFloodControl() {
$this->config('user.flood')
->set('ip_limit', 2)
// Set a high per-user limit out so that it is not relevant in the test.
->set('user_limit', 4000)
->save();
$user = $this->drupalCreateUser(array());
$incorrect_user = clone $user;
$incorrect_user->pass_raw .= 'incorrect';
$url = Url::fromRoute('router_test.11');
// Try 2 failed logins.
for ($i = 0; $i < 2; $i++) {
$this->basicAuthGet($url, $incorrect_user->getUsername(), $incorrect_user->pass_raw);
}
// IP limit has reached to its limit. Even valid user credentials will fail.
$this->basicAuthGet($url, $user->getUsername(), $user->pass_raw);
$this->assertResponse('403', 'Access is blocked because of IP based flood prevention.');
}
/**
* Test the per-user login flood control.
*/
function testPerUserLoginFloodControl() {
$this->config('user.flood')
// Set a high global limit out so that it is not relevant in the test.
->set('ip_limit', 4000)
->set('user_limit', 2)
->save();
$user = $this->drupalCreateUser(array());
$incorrect_user = clone $user;
$incorrect_user->pass_raw .= 'incorrect';
$user2 = $this->drupalCreateUser(array());
$url = Url::fromRoute('router_test.11');
// Try a failed login.
$this->basicAuthGet($url, $incorrect_user->getUsername(), $incorrect_user->pass_raw);
// A successful login will reset the per-user flood control count.
$this->basicAuthGet($url, $user->getUsername(), $user->pass_raw);
$this->assertResponse('200', 'Per user flood prevention gets reset on a successful login.');
// Try 2 failed logins for a user. They will trigger flood control.
for ($i = 0; $i < 2; $i++) {
$this->basicAuthGet($url, $incorrect_user->getUsername(), $incorrect_user->pass_raw);
}
// Now the user account is blocked.
$this->basicAuthGet($url, $user->getUsername(), $user->pass_raw);
$this->assertResponse('403', 'The user account is blocked due to per user flood prevention.');
// Try one successful attempt for a different user, it should not trigger
// any flood control.
$this->basicAuthGet($url, $user2->getUsername(), $user2->pass_raw);
$this->assertResponse('200', 'Per user flood prevention does not block access for other users.');
}
/**
* Tests compatibility with locale/UI translation.
*/
function testLocale() {
ConfigurableLanguage::createFromLangcode('de')->save();
$this->config('system.site')->set('default_langcode', 'de')->save();
$account = $this->drupalCreateUser();
$url = Url::fromRoute('router_test.11');
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
$this->assertText($account->getUsername(), 'Account name is displayed.');
$this->assertResponse('200', 'HTTP response is OK');
$this->curlClose();
}
/**
* Tests if a comprehensive message is displayed when the route is denied.
*/
function testUnauthorizedErrorMessage() {
$account = $this->drupalCreateUser();
$url = Url::fromRoute('router_test.11');
// Case when no credentials are passed.
$this->drupalGet($url);
$this->assertResponse('401', 'The user is blocked when no credentials are passed.');
$this->assertNoText('Exception', "No raw exception is displayed on the page.");
$this->assertText('Please log in to access this page.', "A user friendly access unauthorized message is displayed.");
// Case when empty credentials are passed.
$this->basicAuthGet($url, NULL, NULL);
$this->assertResponse('403', 'The user is blocked when empty credentials are passed.');
$this->assertText('Access denied', "A user friendly access denied message is displayed");
// Case when wrong credentials are passed.
$this->basicAuthGet($url, $account->getUsername(), $this->randomMachineName());
$this->assertResponse('403', 'The user is blocked when wrong credentials are passed.');
$this->assertText('Access denied', "A user friendly access denied message is displayed");
}
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
+3 -3
View File
@@ -5,8 +5,8 @@ package: Core
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -446,7 +446,6 @@ class BigPipe {
}
}
// Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
// before the HTML they're associated with. In other words: ensure the
// critical assets for this placeholder's markup are loaded first.
@@ -483,7 +482,6 @@ class BigPipe {
}
}
// Send this embedded HTML response.
$this->sendChunk($html_response);
@@ -1,144 +0,0 @@
<?php
namespace Drupal\big_pipe\Render;
/**
* Interface for sending an HTML response in chunks (to get faster page loads).
*
* At a high level, BigPipe sends a HTML response in chunks:
* 1. one chunk: everything until just before </body> this contains BigPipe
* placeholders for the personalized parts of the page. Hence this sends the
* non-personalized parts of the page. Let's call it The Skeleton.
* 2. N chunks: a <script> tag per BigPipe placeholder in The Skeleton.
* 3. one chunk: </body> and everything after it.
*
* This is conceptually identical to Facebook's BigPipe (hence the name).
*
* @see https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
*
* The major way in which Drupal differs from Facebook's implementation (and
* others) is in its ability to automatically figure out which parts of the page
* can benefit from BigPipe-style delivery. Drupal's render system has the
* concept of "auto-placeholdering": content that is too dynamic is replaced
* with a placeholder that can then be rendered at a later time. On top of that,
* it also has the concept of "placeholder strategies": by default, placeholders
* are replaced on the server side and the response is blocked on all of them
* being replaced. But it's possible to add additional placeholder strategies.
* BigPipe is just another placeholder strategy. Others could be ESI, AJAX
*
* @see https://www.drupal.org/developing/api/8/render/arrays/cacheability/auto-placeholdering
* @see \Drupal\Core\Render\PlaceholderGeneratorInterface::shouldAutomaticallyPlaceholder()
* @see \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface
* @see \Drupal\Core\Render\Placeholder\SingleFlushStrategy
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
*
* There is also one noteworthy technical addition that Drupal makes. BigPipe as
* described above, and as implemented by Facebook, can only work if JavaScript
* is enabled. The BigPipe module also makes it possible to replace placeholders
* using BigPipe in-situ, without JavaScript. This is not technically BigPipe at
* all; it's just the use of multiple flushes. Since it is able to reuse much of
* the logic though, we choose to call this "no-JS BigPipe".
*
* However, there is also a tangible benefit: some dynamic/expensive content is
* not HTML, but for example a HTML attribute value (or part thereof). It's not
* possible to efficiently replace such content using JavaScript, so "classic"
* BigPipe is out of the question. For example: CSRF tokens in URLs.
*
* This allows us to use both no-JS BigPipe and "classic" BigPipe in the same
* response to maximize the amount of content we can send as early as possible.
*
* Finally, a closer look at the implementation, and how it supports and reuses
* existing Drupal concepts:
* 1. BigPipe placeholders: 1 HtmlResponse + N embedded AjaxResponses.
* - Before a BigPipe response is sent, it is just a HTML response that
* contains BigPipe placeholders. Those placeholders look like
* <div data-big-pipe-placeholder-id=""></div>. JavaScript is used to
* replace those placeholders.
* Therefore these placeholders are actually sent to the client.
* - The Skeleton of course has attachments, including most notably asset
* libraries. And those we track in drupalSettings.ajaxPageState.libraries
* so that when we load new content through AJAX, we don't load the same
* asset libraries again. A HTML page can have multiple AJAX responses, each
* of which should take into account the combined AJAX page state of the
* HTML document and all preceding AJAX responses.
* - BigPipe does not make use of multiple AJAX requests/responses. It uses a
* single HTML response. But it is a more long-lived one: The Skeleton is
* sent first, the closing </body> tag is not yet sent, and the connection
* is kept open. Whenever another BigPipe Placeholder is rendered, Drupal
* sends (and so actually appends to the already-sent HTML) something like
* <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{}}, {"command":}.
* - So, for every BigPipe placeholder, we send such a <script
* type="application/vnd.drupal-ajax"> tag. And the contents of that tag is
* exactly like an AJAX response. The BigPipe module has JavaScript that
* listens for these and applies them. Let's call it an Embedded AJAX
* Response (since it is embedded in the HTML response). Now for the
* interesting bit: each of those Embedded AJAX Responses must also take
* into account the cumulative AJAX page state of the HTML document and all
* preceding Embedded AJAX responses.
* 2. No-JS BigPipe placeholders: 1 HtmlResponse + N embedded HtmlResponses.
* - Before a BigPipe response is sent, it is just a HTML response that
* contains no-JS BigPipe placeholders. Those placeholders can take two
* different forms:
* 1. <div data-big-pipe-nojs-placeholder-id=""></div> if it's a
* placeholder that will be replaced by HTML
* 2. big_pipe_nojs_placeholder_attribute_safe: if it's a placeholder
* inside a HTML attribute, in which 1. would be invalid (angle brackets
* are not allowed inside HTML attributes)
* No-JS BigPipe placeholders are not replaced using JavaScript, they must
* be replaced upon sending the BigPipe response. So, while the response is
* being sent, upon encountering these placeholders, their corresponding
* placeholder replacements are sent instead.
* Therefore these placeholders are never actually sent to the client.
* - See second bullet of point 1.
* - No-JS BigPipe does not use multiple AJAX requests/responses. It uses a
* single HTML response. But it is a more long-lived one: The Skeleton is
* split into multiple parts, the separators are where the no-JS BigPipe
* placeholders used to be. Whenever another no-JS BigPipe placeholder is
* rendered, Drupal sends (and so actually appends to the already-sent HTML)
* something like
* <link rel="stylesheet" ><script ><content>.
* - So, for every no-JS BigPipe placeholder, we send its associated CSS and
* header JS that has not already been sent (the bottom JS is not yet sent,
* so we can accumulate all of it and send it together at the end). This
* ensures that the markup is rendered as it was originally intended: its
* CSS and JS used to be blocking, and it still is. Let's call it an
* Embedded HTML response. Each of those Embedded HTML Responses must also
* take into account the cumulative AJAX page state of the HTML document and
* all preceding Embedded HTML responses.
* - Finally: any non-critical JavaScript associated with all Embedded HTML
* Responses, i.e. any footer/bottom/non-header JavaScript, is loaded after
* The Skeleton.
*
* Combining all of the above, when using both BigPipe placeholders and no-JS
* BigPipe placeholders, we therefore send: 1 HtmlResponse + M Embedded HTML
* Responses + N Embedded AJAX Responses. Schematically, we send these chunks:
* 1. Byte zero until 1st no-JS placeholder: headers + <html><head /><div></div>
* 2. 1st no-JS placeholder replacement: <link rel="stylesheet" ><script ><content>
* 3. Content until 2nd no-JS placeholder: <div></div>
* 4. 2nd no-JS placeholder replacement: <link rel="stylesheet" ><script ><content>
* 5. Content until 3rd no-JS placeholder: <div></div>
* 6. [ repeat until all no-JS placeholder replacements are sent ]
* 7. Send content after last no-JS placeholder.
* 8. Send script_bottom (markup to load bottom i.e. non-critical JS).
* 9. 1st placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{}}, {"command":}
* 10. 2nd placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{}}, {"command":}
* 11. [ repeat until all placeholder replacements are sent ]
* 12. Send </body> and everything after it.
* 13. Terminate request/response cycle.
*
* @see \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
*/
interface BigPipeInterface {
/**
* Sends an HTML response in chunks using the BigPipe technique.
*
* @param string $content
* The HTML response content to send.
* @param array $attachments
* The HTML response's attachments.
*/
public function sendContent($content, array $attachments);
}
@@ -1,433 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Tests\big_pipe\Unit\Render\Placeholder\BigPipePlaceholderTestCases.
*/
namespace Drupal\big_pipe\Tests;
use Drupal\big_pipe\Render\BigPipeMarkup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* BigPipe placeholder test cases for use in both unit and integration tests.
*
* - Unit test:
* \Drupal\Tests\big_pipe\Unit\Render\Placeholder\BigPipeStrategyTest
* - Integration test for BigPipe with JS on:
* \Drupal\big_pipe\Tests\BigPipeTest::testBigPipe()
* - Integration test for BigPipe with JS off:
* \Drupal\big_pipe\Tests\BigPipeTest::testBigPipeNoJs()
*/
class BigPipePlaceholderTestCases {
/**
* Gets all BigPipe placeholder test cases.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface|null $container
* Optional. Necessary to get the embedded AJAX/HTML responses.
* @param \Drupal\Core\Session\AccountInterface|null $user
* Optional. Necessary to get the embedded AJAX/HTML responses.
*
* @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
*/
public static function cases(ContainerInterface $container = NULL, AccountInterface $user = NULL) {
// Define the two types of cacheability that we expect to see. These will be
// used in the expectations.
$cacheability_depends_on_session_only = [
'max-age' => 0,
'contexts' => ['session.exists'],
];
$cacheability_depends_on_session_and_nojs_cookie = [
'max-age' => 0,
'contexts' => ['session.exists', 'cookies:big_pipe_nojs'],
];
// 1. Real-world example of HTML placeholder.
$status_messages = new BigPipePlaceholderTestCase(
['#type' => 'status_messages'],
'<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
[
'#lazy_builder' => [
'Drupal\Core\Render\Element\StatusMessages::renderMessages',
[NULL]
],
]
);
$status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$status_messages->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
],
],
'big_pipe_placeholders' => [
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
],
],
];
$status_messages->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>';
$status_messages->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>' => $status_messages->placeholderRenderArray,
],
],
];
if ($container && $user) {
$status_messages->embeddedAjaxResponseCommands = [
[
'command' => 'settings',
'settings' => [
'ajaxPageState' => [
'theme' => 'classy',
'libraries' => 'big_pipe/big_pipe,classy/base,classy/messages,core/drupal.active-link,core/html5shiv,core/normalize,system/base',
],
'pluralDelimiter' => PluralTranslatableMarkup::DELIMITER,
'user' => [
'uid' => '1',
'permissionsHash' => $container->get('user_permissions_hash_generator')->generate($user),
],
],
'merge' => TRUE,
],
[
'command' => 'add_css',
'data' => '<link rel="stylesheet" href="' . base_path() . 'core/themes/classy/css/components/messages.css?' . $container->get('state')->get('system.css_js_query_string') . '" media="all" />' . "\n"
],
[
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n ",
'settings' => NULL,
],
];
$status_messages->embeddedHtmlResponse = '<link rel="stylesheet" href="' . base_path() . 'core/themes/classy/css/components/messages.css?' . $container->get('state')->get('system.css_js_query_string') . '" media="all" />' . "\n" . "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n \n";
}
// 2. Real-world example of HTML attribute value placeholder: form action.
$form_action = new BigPipePlaceholderTestCase(
$container ? $container->get('form_builder')->getForm('Drupal\big_pipe_test\Form\BigPipeTestForm') : [],
'form_action_cc611e1d',
[
'#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []],
]
);
$form_action->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d';
$form_action->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d',
'#cache' => $cacheability_depends_on_session_only,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d' => $form_action->placeholderRenderArray,
],
],
];
if ($container) {
$form_action->embeddedHtmlResponse = '<form class="big-pipe-test-form" data-drupal-selector="big-pipe-test-form" action="' . base_path() . 'big_pipe_test"';
}
// 3. Real-world example of HTML attribute value subset placeholder: CSRF
// token in link.
$csrf_token = new BigPipePlaceholderTestCase(
[
'#title' => 'Link with CSRF token',
'#type' => 'link',
'#url' => Url::fromRoute('system.theme_set_default'),
],
'e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e',
[
'#lazy_builder' => [
'route_processor_csrf:renderPlaceholderCsrfToken',
['admin/config/user-interface/shortcut/manage/default/add-link-inline']
],
]
);
$csrf_token->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e';
$csrf_token->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => 'big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e',
'#cache' => $cacheability_depends_on_session_only,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e' => $csrf_token->placeholderRenderArray,
],
],
];
if ($container) {
$csrf_token->embeddedHtmlResponse = $container->get('csrf_token')->get('admin/appearance/default');
}
// 4. Edge case: custom string to be considered as a placeholder that
// happens to not be valid HTML.
$hello = new BigPipePlaceholderTestCase(
[
'#markup' => BigPipeMarkup::create('<hello'),
'#attached' => [
'placeholders' => [
'<hello' => ['#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::helloOrYarhar', []]],
]
],
],
'<hello',
[
'#lazy_builder' => [
'hello_or_yarhar',
[]
],
]
);
$hello->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:&lt;hello';
$hello->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => 'big_pipe_nojs_placeholder_attribute_safe:&lt;hello',
'#cache' => $cacheability_depends_on_session_only,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'big_pipe_nojs_placeholder_attribute_safe:&lt;hello' => $hello->placeholderRenderArray,
],
],
];
$hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>';
// 5. Edge case: non-#lazy_builder placeholder.
$current_time = new BigPipePlaceholderTestCase(
[
'#markup' => BigPipeMarkup::create('<time>CURRENT TIME</time>'),
'#attached' => [
'placeholders' => [
'<time>CURRENT TIME</time>' => [
'#pre_render' => [
'\Drupal\big_pipe_test\BigPipeTestController::currentTime',
],
]
]
]
],
'<time>CURRENT TIME</time>',
[
'#pre_render' => ['current_time'],
]
);
$current_time->bigPipePlaceholderId = 'timecurrent-timetime';
$current_time->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="timecurrent-timetime"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'timecurrent-timetime' => TRUE,
],
],
'big_pipe_placeholders' => [
'timecurrent-timetime' => $current_time->placeholderRenderArray,
],
],
];
$current_time->embeddedAjaxResponseCommands = [
[
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '[data-big-pipe-placeholder-id="timecurrent-timetime"]',
'data' => '<time datetime="1991-03-14"></time>',
'settings' => NULL,
],
];
$current_time->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>';
$current_time->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>' => $current_time->placeholderRenderArray,
],
],
];
$current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>';
// 6. Edge case: #lazy_builder that throws an exception.
$exception = new BigPipePlaceholderTestCase(
[
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
'#create_placeholder' => TRUE,
],
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&amp;1=suck" token="uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></drupal-render-placeholder>',
[
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
]
);
$exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
$exception->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => TRUE,
],
],
'big_pipe_placeholders' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
],
],
];
$exception->embeddedAjaxResponseCommands = NULL;
$exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>';
$exception->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => $exception->bigPipeNoJsPlaceholder,
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'big_pipe_nojs_placeholders' => [
$exception->bigPipeNoJsPlaceholder => $exception->placeholderRenderArray,
],
],
];
$exception->embeddedHtmlResponse = NULL;
// 7. Edge case: response filter throwing an exception for this placeholder.
$embedded_response_exception = new BigPipePlaceholderTestCase(
[
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
'#create_placeholder' => TRUE,
],
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></drupal-render-placeholder>',
[
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
]
);
$embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU';
$embedded_response_exception->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => TRUE,
],
],
'big_pipe_placeholders' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => $embedded_response_exception->placeholderRenderArray,
],
],
];
$embedded_response_exception->embeddedAjaxResponseCommands = NULL;
$embedded_response_exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></span>';
$embedded_response_exception->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => $embedded_response_exception->bigPipeNoJsPlaceholder,
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'big_pipe_nojs_placeholders' => [
$embedded_response_exception->bigPipeNoJsPlaceholder => $embedded_response_exception->placeholderRenderArray,
],
],
];
$exception->embeddedHtmlResponse = NULL;
return [
'html' => $status_messages,
'html_attribute_value' => $form_action,
'html_attribute_value_subset' => $csrf_token,
'edge_case__invalid_html' => $hello,
'edge_case__html_non_lazy_builder' => $current_time,
'exception__lazy_builder' => $exception,
'exception__embedded_response' => $embedded_response_exception,
];
}
}
class BigPipePlaceholderTestCase {
/**
* The original render array.
*
* @var array
*/
public $renderArray;
/**
* The expected corresponding placeholder string.
*
* @var string
*/
public $placeholder;
/**
* The expected corresponding placeholder render array.
*
* @var array
*/
public $placeholderRenderArray;
/**
* The expected BigPipe placeholder ID.
*
* (Only possible for HTML placeholders.)
*
* @var null|string
*/
public $bigPipePlaceholderId = NULL;
/**
* The corresponding expected BigPipe placeholder render array.
*
* @var null|array
*/
public $bigPipePlaceholderRenderArray = NULL;
/**
* The corresponding expected embedded AJAX response.
*
* @var null|array
*/
public $embeddedAjaxResponseCommands = NULL;
/**
* The expected BigPipe no-JS placeholder.
*
* (Possible for all placeholders, HTML or non-HTML.)
*
* @var string
*/
public $bigPipeNoJsPlaceholder;
/**
* The corresponding expected BigPipe no-JS placeholder render array.
*
* @var array
*/
public $bigPipeNoJsPlaceholderRenderArray;
/**
* The corresponding expected embedded HTML response.
*
* @var string
*/
public $embeddedHtmlResponse;
public function __construct(array $render_array, $placeholder, array $placeholder_render_array) {
$this->renderArray = $render_array;
$this->placeholder = $placeholder;
$this->placeholderRenderArray = $placeholder_render_array;
}
}
@@ -1,480 +0,0 @@
<?php
namespace Drupal\big_pipe\Tests;
use Drupal\big_pipe\Render\Placeholder\BigPipeStrategy;
use Drupal\big_pipe\Render\BigPipe;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests BigPipe's no-JS detection & response delivery (with and without JS).
*
* Covers:
* - big_pipe_page_attachments()
* - \Drupal\big_pipe\Controller\BigPipeController
* - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
* - \Drupal\big_pipe\Render\BigPipe
*
* @group big_pipe
*/
class BigPipeTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['big_pipe', 'big_pipe_test', 'dblog'];
/**
* {@inheritdoc}
*/
protected $dumpHeaders = TRUE;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Ignore the <meta> refresh that big_pipe.module sets. It causes a redirect
// to a page that sets another cookie, which causes WebTestBase to lose the
// session cookie. To avoid this problem, tests should first call
// drupalGet() and then call checkForMetaRefresh() manually, and then reset
// $this->maximumMetaRefreshCount and $this->metaRefreshCount.
// @see doMetaRefresh()
$this->maximumMetaRefreshCount = 0;
}
/**
* Performs a single <meta> refresh explicitly.
*
* This test disables the automatic <meta> refresh checking, each time it is
* desired that this runs, a test case must explicitly call this.
*
* @see setUp()
*/
protected function performMetaRefresh() {
$this->maximumMetaRefreshCount = 1;
$this->checkForMetaRefresh();
$this->maximumMetaRefreshCount = 0;
$this->metaRefreshCount = 0;
}
/**
* Tests BigPipe's no-JS detection.
*
* Covers:
* - big_pipe_page_attachments()
* - \Drupal\big_pipe\Controller\BigPipeController
*/
public function testNoJsDetection() {
$no_js_to_js_markup = '<script>document.cookie = "' . BigPipeStrategy::NOJS_COOKIE . '=1; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"</script>';
// 1. No session (anonymous).
$this->drupalGet(Url::fromRoute('<front>'));
$this->assertSessionCookieExists(FALSE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
$this->assertNoRaw($no_js_to_js_markup);
// 2. Session (authenticated).
$this->drupalLogin($this->rootUser);
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$this->assertRaw('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'user/1" />' . "\n" . '</noscript>');
$this->assertNoRaw($no_js_to_js_markup);
$this->assertBigPipeNoJsMetaRefreshRedirect();
$this->assertBigPipeNoJsCookieExists(TRUE);
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
$this->assertRaw($no_js_to_js_markup);
$this->drupalLogout();
// Close the prior connection and remove the collected state.
$this->curlClose();
$this->curlCookies = [];
$this->cookies = [];
// 3. Session (anonymous).
$this->drupalGet(Url::fromRoute('user.login', [], ['query' => ['trigger_session' => 1]]));
$this->drupalGet(Url::fromRoute('user.login'));
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$this->assertRaw('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'user/login" />' . "\n" . '</noscript>');
$this->assertNoRaw($no_js_to_js_markup);
$this->assertBigPipeNoJsMetaRefreshRedirect();
$this->assertBigPipeNoJsCookieExists(TRUE);
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
$this->assertRaw($no_js_to_js_markup);
// Close the prior connection and remove the collected state.
$this->curlClose();
$this->curlCookies = [];
$this->cookies = [];
// Edge case: route with '_no_big_pipe' option.
$this->drupalGet(Url::fromRoute('no_big_pipe'));
$this->assertSessionCookieExists(FALSE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
$this->assertNoRaw($no_js_to_js_markup);
$this->drupalLogin($this->rootUser);
$this->drupalGet(Url::fromRoute('no_big_pipe'));
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
$this->assertNoRaw($no_js_to_js_markup);
}
/**
* Tests BigPipe-delivered HTML responses when JavaScript is enabled.
*
* Covers:
* - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
* - \Drupal\big_pipe\Render\BigPipe
* - \Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
*
* @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
*/
public function testBigPipe() {
// Simulate production.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
$this->drupalLogin($this->rootUser);
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
$log_count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
// By not calling performMetaRefresh() here, we simulate JavaScript being
// enabled, because as far as the BigPipe module is concerned, JavaScript is
// enabled in the browser as long as the BigPipe no-JS cookie is *not* set.
// @see setUp()
// @see performMetaRefresh()
$this->drupalGet(Url::fromRoute('big_pipe_test'));
$this->assertBigPipeResponseHeadersPresent();
$this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
$this->setCsrfTokenSeedInTestEnvironment();
$cases = $this->getTestCases();
$this->assertBigPipeNoJsPlaceholders([
$cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
$cases['html_attribute_value']->bigPipeNoJsPlaceholder => $cases['html_attribute_value']->embeddedHtmlResponse,
$cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
]);
$this->assertBigPipePlaceholders([
$cases['html']->bigPipePlaceholderId => Json::encode($cases['html']->embeddedAjaxResponseCommands),
$cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
$cases['exception__lazy_builder']->bigPipePlaceholderId => NULL,
$cases['exception__embedded_response']->bigPipePlaceholderId => NULL,
], [
0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
// The 'html' case contains the 'status messages' placeholder, which is
// always rendered last.
1 => $cases['html']->bigPipePlaceholderId,
]);
$this->assertRaw('</body>', 'Closing body tag present.');
$this->pass('Verifying BigPipe assets are present…', 'Debug');
$this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings present.');
$this->assertTrue(in_array('big_pipe/big_pipe', explode(',', $this->getDrupalSettings()['ajaxPageState']['libraries'])), 'BigPipe asset library is present.');
// Verify that the two expected exceptions are logged as errors.
$this->assertEqual($log_count + 2, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), 'Two new watchdog entries.');
$records = db_query('SELECT * FROM {watchdog} ORDER BY wid DESC LIMIT 2')->fetchAll();
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
$this->assertTrue(FALSE !== strpos((string) unserialize($records[0]->variables)['@message'], 'Oh noes!'));
$this->assertEqual(RfcLogLevel::ERROR, $records[1]->severity);
$this->assertTrue(FALSE !== strpos((string) unserialize($records[1]->variables)['@message'], 'You are not allowed to say llamas are not cool!'));
// Verify that 4xx responses work fine. (4xx responses are handled by
// subrequests to a route pointing to a controller with the desired output.)
$this->drupalGet(Url::fromUri('base:non-existing-path'));
// Simulate development.
$this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet(Url::fromRoute('big_pipe_test'));
// The 'edge_case__html_exception' case throws an exception.
$this->assertRaw('The website encountered an unexpected error. Please try again later');
$this->assertRaw('You are not allowed to say llamas are not cool!');
$this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent: error occurred before then.');
$this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
// The exception is expected. Do not interpret it as a test failure.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
}
/**
* Tests BigPipe-delivered HTML responses when JavaScript is disabled.
*
* Covers:
* - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
* - \Drupal\big_pipe\Render\BigPipe
* - \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()
*
* @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
*/
public function testBigPipeNoJs() {
// Simulate production.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
$this->drupalLogin($this->rootUser);
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
// By calling performMetaRefresh() here, we simulate JavaScript being
// disabled, because as far as the BigPipe module is concerned, it is
// enabled in the browser when the BigPipe no-JS cookie is set.
// @see setUp()
// @see performMetaRefresh()
$this->performMetaRefresh();
$this->assertBigPipeNoJsCookieExists(TRUE);
$this->drupalGet(Url::fromRoute('big_pipe_test'));
$this->assertBigPipeResponseHeadersPresent();
$this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
$this->setCsrfTokenSeedInTestEnvironment();
$cases = $this->getTestCases();
$this->assertBigPipeNoJsPlaceholders([
$cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
$cases['html_attribute_value']->bigPipeNoJsPlaceholder => $cases['html_attribute_value']->embeddedHtmlResponse,
$cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
$cases['html']->bigPipeNoJsPlaceholder => $cases['html']->embeddedHtmlResponse,
$cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholder => $cases['edge_case__html_non_lazy_builder']->embeddedHtmlResponse,
$cases['exception__lazy_builder']->bigPipePlaceholderId => NULL,
$cases['exception__embedded_response']->bigPipePlaceholderId => NULL,
]);
$this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug');
$this->assertEqual('<none>', $this->drupalGetHeader('BigPipe-Test-Placeholders'));
$this->pass('Verifying BigPipe start/stop signals are absent…', 'Debug');
$this->assertNoRaw(BigPipe::START_SIGNAL, 'BigPipe start signal absent.');
$this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.');
$this->pass('Verifying BigPipe assets are absent…', 'Debug');
$this->assertTrue(!isset($this->getDrupalSettings()['bigPipePlaceholderIds']) && empty($this->getDrupalSettings()['ajaxPageState']), 'BigPipe drupalSettings and BigPipe asset library absent.');
$this->assertRaw('</body>', 'Closing body tag present.');
// Verify that 4xx responses work fine. (4xx responses are handled by
// subrequests to a route pointing to a controller with the desired output.)
$this->drupalGet(Url::fromUri('base:non-existing-path'));
// Simulate development.
$this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet(Url::fromRoute('big_pipe_test'));
// The 'edge_case__html_exception' case throws an exception.
$this->assertRaw('The website encountered an unexpected error. Please try again later');
$this->assertRaw('You are not allowed to say llamas are not cool!');
$this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
// The exception is expected. Do not interpret it as a test failure.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
}
/**
* Tests BigPipe with a multi-occurrence placeholder.
*/
public function testBigPipeMultiOccurrencePlaceholders() {
$this->drupalLogin($this->rootUser);
$this->assertSessionCookieExists(TRUE);
$this->assertBigPipeNoJsCookieExists(FALSE);
// By not calling performMetaRefresh() here, we simulate JavaScript being
// enabled, because as far as the BigPipe module is concerned, JavaScript is
// enabled in the browser as long as the BigPipe no-JS cookie is *not* set.
// @see setUp()
// @see performMetaRefresh()
$this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
$big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
$this->assertRaw('The count is 1.');
$this->assertNoRaw('The count is 2.');
$this->assertNoRaw('The count is 3.');
$raw_content = $this->getRawContent();
$this->assertTrue(substr_count($raw_content, $expected_placeholder_replacement) == 1, 'Only one placeholder replacement was found for the duplicate #lazy_builder arrays.');
// By calling performMetaRefresh() here, we simulate JavaScript being
// disabled, because as far as the BigPipe module is concerned, it is
// enabled in the browser when the BigPipe no-JS cookie is set.
// @see setUp()
// @see performMetaRefresh()
$this->performMetaRefresh();
$this->assertBigPipeNoJsCookieExists(TRUE);
$this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
$this->assertRaw('The count is 1.');
$this->assertNoRaw('The count is 2.');
$this->assertNoRaw('The count is 3.');
}
protected function assertBigPipeResponseHeadersPresent() {
$this->pass('Verifying BigPipe response headers…', 'Debug');
$this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), 'private'), 'Cache-Control header set to "private".');
$this->assertEqual('no-store, content="BigPipe/1.0"', $this->drupalGetHeader('Surrogate-Control'));
$this->assertEqual('no', $this->drupalGetHeader('X-Accel-Buffering'));
}
/**
* Asserts expected BigPipe no-JS placeholders are present and replaced.
*
* @param array $expected_big_pipe_nojs_placeholders
* Keys: BigPipe no-JS placeholder markup. Values: expected replacement
* markup.
*/
protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_placeholders) {
$this->pass('Verifying BigPipe no-JS placeholders & replacements…', 'Debug');
$this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->drupalGetHeader('BigPipe-Test-No-Js-Placeholders'))));
foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
$this->pass('Checking whether the replacement for the BigPipe no-JS placeholder "' . $big_pipe_nojs_placeholder . '" is present:');
$this->assertNoRaw($big_pipe_nojs_placeholder);
if ($expected_replacement !== NULL) {
$this->assertRaw($expected_replacement);
}
}
}
/**
* Asserts expected BigPipe placeholders are present and replaced.
*
* @param array $expected_big_pipe_placeholders
* Keys: BigPipe placeholder IDs. Values: expected AJAX response.
* @param array $expected_big_pipe_placeholder_stream_order
* Keys: BigPipe placeholder IDs. Values: expected AJAX response. Keys are
* defined in the order that they are expected to be rendered & streamed.
*/
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
$this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
$placeholder_positions = [];
$placeholder_replacement_positions = [];
foreach ($expected_big_pipe_placeholders as $big_pipe_placeholder_id => $expected_ajax_response) {
$this->pass('BigPipe placeholder: ' . $big_pipe_placeholder_id, 'Debug');
// Verify expected placeholder.
$expected_placeholder_html = '<span data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '"></span>';
$this->assertRaw($expected_placeholder_html, 'BigPipe placeholder for placeholder ID "' . $big_pipe_placeholder_id . '" found.');
$pos = strpos($this->getRawContent(), $expected_placeholder_html);
$placeholder_positions[$pos] = $big_pipe_placeholder_id;
// Verify expected placeholder replacement.
$expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
$result = $this->xpath('//script[@data-big-pipe-replacement-for-placeholder-with-id=:id]', [':id' => Html::decodeEntities($big_pipe_placeholder_id)]);
if ($expected_ajax_response === NULL) {
$this->assertEqual(0, count($result));
$this->assertNoRaw($expected_placeholder_replacement);
continue;
}
$this->assertEqual($expected_ajax_response, trim((string) $result[0]));
$this->assertRaw($expected_placeholder_replacement);
$pos = strpos($this->getRawContent(), $expected_placeholder_replacement);
$placeholder_replacement_positions[$pos] = $big_pipe_placeholder_id;
}
ksort($placeholder_positions, SORT_NUMERIC);
$this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
$placeholders = array_map(function(\SimpleXMLElement $element) { return (string) $element['data-big-pipe-placeholder-id']; }, $this->cssSelect('[data-big-pipe-placeholder-id]'));
$this->assertEqual(count($expected_big_pipe_placeholders), count(array_unique($placeholders)));
$expected_big_pipe_placeholders_with_replacements = [];
foreach ($expected_big_pipe_placeholder_stream_order as $big_pipe_placeholder_id) {
$expected_big_pipe_placeholders_with_replacements[$big_pipe_placeholder_id] = $expected_big_pipe_placeholders[$big_pipe_placeholder_id];
}
$this->assertEqual($expected_big_pipe_placeholders_with_replacements, array_filter($expected_big_pipe_placeholders));
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
$this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
$this->pass('Verifying BigPipe start/stop signals…', 'Debug');
$this->assertRaw(BigPipe::START_SIGNAL, 'BigPipe start signal present.');
$this->assertRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal present.');
$start_signal_position = strpos($this->getRawContent(), BigPipe::START_SIGNAL);
$stop_signal_position = strpos($this->getRawContent(), BigPipe::STOP_SIGNAL);
$this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
$this->pass('Verifying BigPipe placeholder replacements and start/stop signals were streamed in the correct order…', 'Debug');
$expected_stream_order = array_keys($expected_big_pipe_placeholders_with_replacements);
array_unshift($expected_stream_order, BigPipe::START_SIGNAL);
array_push($expected_stream_order, BigPipe::STOP_SIGNAL);
$actual_stream_order = $placeholder_replacement_positions + [
$start_signal_position => BigPipe::START_SIGNAL,
$stop_signal_position => BigPipe::STOP_SIGNAL,
];
ksort($actual_stream_order, SORT_NUMERIC);
$this->assertEqual($expected_stream_order, array_values($actual_stream_order));
}
/**
* Ensures CSRF tokens can be generated for the current user's session.
*/
protected function setCsrfTokenSeedInTestEnvironment() {
$session_data = $this->container->get('session_handler.write_safe')->read($this->cookies[$this->getSessionName()]['value']);
$csrf_token_seed = unserialize(explode('_sf2_meta|', $session_data)[1])['s'];
$this->container->get('session_manager.metadata_bag')->setCsrfTokenSeed($csrf_token_seed);
}
/**
* @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
*/
protected function getTestCases($has_session = TRUE) {
return BigPipePlaceholderTestCases::cases($this->container, $this->rootUser);
}
/**
* Asserts whether arrays A and B are equal, when treated as sets.
*/
protected function assertSetsEqual(array $a, array $b) {
return count($a) == count($b) && !array_diff_assoc($a, $b);
}
/**
* Asserts whether a BigPipe no-JS cookie exists or not.
*/
protected function assertBigPipeNoJsCookieExists($expected) {
$this->assertCookieExists('big_pipe_nojs', $expected, 'BigPipe no-JS');
}
/**
* Asserts whether a session cookie exists or not.
*/
protected function assertSessionCookieExists($expected) {
$this->assertCookieExists($this->getSessionName(), $expected, 'Session');
}
/**
* Asserts whether a cookie exists on the client or not.
*/
protected function assertCookieExists($cookie_name, $expected, $cookie_label) {
$non_deleted_cookies = array_filter($this->cookies, function ($item) { return $item['value'] !== 'deleted'; });
$this->assertEqual($expected, isset($non_deleted_cookies[$cookie_name]), $expected ? "$cookie_label cookie exists." : "$cookie_label cookie does not exist.");
}
/**
* Calls ::performMetaRefresh() and asserts the responses.
*/
protected function assertBigPipeNoJsMetaRefreshRedirect() {
$original_url = $this->url;
$this->performMetaRefresh();
$this->assertEqual($original_url, $this->url, 'Redirected back to the original location.');
$headers = $this->drupalGetHeaders(TRUE);
$this->assertEqual(2, count($headers), 'Two requests were made upon following the <meta> refresh, there are headers for two responses.');
// First response: redirect.
$this->assertEqual('HTTP/1.1 302 Found', $headers[0][':status'], 'The first response was a 302 (redirect).');
$this->assertIdentical(0, strpos($headers[0]['set-cookie'], 'big_pipe_nojs=1'), 'The first response sets the big_pipe_nojs cookie.');
$this->assertEqual($original_url, $headers[0]['location'], 'The first response redirected back to the original page.');
$this->assertTrue(empty(array_diff(['cookies:big_pipe_nojs', 'session.exists'], explode(' ', $headers[0]['x-drupal-cache-contexts']))), 'The first response varies by the "cookies:big_pipe_nojs" and "session.exists" cache contexts.');
$this->assertFalse(isset($headers[0]['surrogate-control']), 'The first response has no "Surrogate-Control" header.');
// Second response: redirect followed.
$this->assertEqual('HTTP/1.1 200 OK', $headers[1][':status'], 'The second response was a 200.');
$this->assertTrue(empty(array_diff(['cookies:big_pipe_nojs', 'session.exists'], explode(' ', $headers[0]['x-drupal-cache-contexts']))), 'The first response varies by the "cookies:big_pipe_nojs" and "session.exists" cache contexts.');
$this->assertEqual('no-store, content="BigPipe/1.0"', $headers[1]['surrogate-control'], 'The second response has a "Surrogate-Control" header.');
$this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=', 'Once the BigPipe no-JS cookie is set, the <meta> refresh is absent: only one redirect ever happens.');
}
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -45,7 +45,6 @@ class BigPipePlaceholderTestCases {
'contexts' => ['session.exists', 'cookies:big_pipe_nojs'],
];
// 1. Real-world example of HTML placeholder.
$status_messages = new BigPipePlaceholderTestCase(
['#type' => 'status_messages'],
@@ -96,7 +95,6 @@ class BigPipePlaceholderTestCases {
$status_messages->embeddedHtmlResponse = '<div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n \n";
}
// 2. Real-world example of HTML attribute value placeholder: form action.
$form_action = new BigPipePlaceholderTestCase(
$container ? $container->get('form_builder')->getForm('Drupal\big_pipe_test\Form\BigPipeTestForm') : [],
@@ -119,7 +117,6 @@ class BigPipePlaceholderTestCases {
$form_action->embeddedHtmlResponse = '<form class="big-pipe-test-form" data-drupal-selector="big-pipe-test-form" action="' . base_path() . 'big_pipe_test"';
}
// 3. Real-world example of HTML attribute value subset placeholder: CSRF
// token in link.
$csrf_token = new BigPipePlaceholderTestCase(
@@ -150,7 +147,6 @@ class BigPipePlaceholderTestCases {
$csrf_token->embeddedHtmlResponse = $container->get('csrf_token')->get('admin/appearance/default');
}
// 4. Edge case: custom string to be considered as a placeholder that
// happens to not be valid HTML.
$hello = new BigPipePlaceholderTestCase(
@@ -182,7 +178,6 @@ class BigPipePlaceholderTestCases {
];
$hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>';
// 5. Edge case: non-#lazy_builder placeholder.
$current_time = new BigPipePlaceholderTestCase(
[
@@ -239,7 +234,6 @@ class BigPipePlaceholderTestCases {
];
$current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>';
// 6. Edge case: #lazy_builder that throws an exception.
$exception = new BigPipePlaceholderTestCase(
[
@@ -101,8 +101,8 @@ class BigPipeResponseAttachmentsProcessorTest extends UnitTestCase {
'random attachment type (unofficial), with random assigned value, to prove BigPipeResponseAttachmentsProcessor is a perfect decorator' => [$random_attachments],
];
$big_pipe_placeholder_attachments = ['big_pipe_placeholders' => $this->randomMachineName()];
$big_pipe_nojs_placeholder_attachments = ['big_pipe_nojs_placeholders' => $this->randomMachineName()];
$big_pipe_placeholder_attachments = ['big_pipe_placeholders' => [$this->randomMachineName()]];
$big_pipe_nojs_placeholder_attachments = ['big_pipe_nojs_placeholders' => [$this->randomMachineName()]];
$big_pipe_cases = [
'only big_pipe_placeholders' => [$big_pipe_placeholder_attachments],
'only big_pipe_nojs_placeholders' => [$big_pipe_nojs_placeholder_attachments],
@@ -4,8 +4,8 @@ description: 'Theme for testing BigPipe edge cases.'
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
+3 -3
View File
@@ -6,8 +6,8 @@ package: Core
# core: 8.x
configure: block.admin_display
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,77 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the block system with admin themes.
*
* @group block
*/
class BlockAdminThemeTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'contextual'];
/**
* Check for the accessibility of the admin theme on the block admin page.
*/
public function testAdminTheme() {
// Create administrative user.
$admin_user = $this->drupalCreateUser(['administer blocks', 'administer themes']);
$this->drupalLogin($admin_user);
// Ensure that access to block admin page is denied when theme is not
// installed.
$this->drupalGet('admin/structure/block/list/bartik');
$this->assertResponse(403);
// Install admin theme and confirm that tab is accessible.
\Drupal::service('theme_handler')->install(['bartik']);
$edit['admin_theme'] = 'bartik';
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('admin/structure/block/list/bartik');
$this->assertResponse(200);
}
/**
* Ensure contextual links are disabled in Seven theme.
*/
public function testSevenAdminTheme() {
// Create administrative user.
$admin_user = $this->drupalCreateUser([
'access administration pages',
'administer themes',
'access contextual links',
'view the administration theme',
]);
$this->drupalLogin($admin_user);
// Install admin theme and confirm that tab is accessible.
\Drupal::service('theme_handler')->install(['seven']);
$edit['admin_theme'] = 'seven';
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
// Define our block settings.
$settings = [
'theme' => 'seven',
'region' => 'header',
];
// Place a block.
$block = $this->drupalPlaceBlock('local_tasks_block', $settings);
// Open admin page.
$this->drupalGet('admin');
// Check if contextual link classes are unavailable.
$this->assertNoRaw('<div data-contextual-id="block:block=' . $block->id() . ':langcode=en"></div>');
$this->assertNoRaw('contextual-region');
}
}
@@ -1,215 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Core\Cache\Cache;
use Drupal\simpletest\WebTestBase;
/**
* Tests block caching.
*
* @group block
*/
class BlockCacheTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'block_test', 'test_page_test');
/**
* A user with permission to create and edit books and to administer blocks.
*
* @var object
*/
protected $adminUser;
/**
* An authenticated user to test block caching.
*
* @var object
*/
protected $normalUser;
/**
* Another authenticated user to test block caching.
*
* @var object
*/
protected $normalUserAlt;
/**
* The block used by this test.
*
* @var \Drupal\block\BlockInterface
*/
protected $block;
protected function setUp() {
parent::setUp();
// Create an admin user, log in and enable test blocks.
$this->adminUser = $this->drupalCreateUser(array('administer blocks', 'access administration pages'));
$this->drupalLogin($this->adminUser);
// Create additional users to test caching modes.
$this->normalUser = $this->drupalCreateUser();
$this->normalUserAlt = $this->drupalCreateUser();
// Sync the roles, since drupalCreateUser() creates separate roles for
// the same permission sets.
$this->normalUserAlt->roles = $this->normalUser->getRoles();
$this->normalUserAlt->save();
// Enable our test block.
$this->block = $this->drupalPlaceBlock('test_cache');
}
/**
* Test "user.roles" cache context.
*/
function testCachePerRole() {
\Drupal::state()->set('block_test.cache_contexts', ['user.roles']);
// Enable our test block. Set some content for it to display.
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalLogin($this->normalUser);
$this->drupalGet('');
$this->assertText($current_content, 'Block content displays.');
// Change the content, but the cached copy should still be served.
$old_content = $current_content;
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('');
$this->assertText($old_content, 'Block is served from the cache.');
// Clear the cache and verify that the stale data is no longer there.
Cache::invalidateTags(array('block_view'));
$this->drupalGet('');
$this->assertNoText($old_content, 'Block cache clear removes stale cache data.');
$this->assertText($current_content, 'Fresh block content is displayed after clearing the cache.');
// Test whether the cached data is served for the correct users.
$old_content = $current_content;
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoText($old_content, 'Anonymous user does not see content cached per-role for normal user.');
$this->drupalLogin($this->normalUserAlt);
$this->drupalGet('');
$this->assertText($old_content, 'User with the same roles sees per-role cached content.');
$this->drupalLogin($this->adminUser);
$this->drupalGet('');
$this->assertNoText($old_content, 'Admin user does not see content cached per-role for normal user.');
$this->drupalLogin($this->normalUser);
$this->drupalGet('');
$this->assertText($old_content, 'Block is served from the per-role cache.');
}
/**
* Test a cacheable block without any additional cache context.
*/
function testCachePermissions() {
// user.permissions is a required context, so a user with different
// permissions will see a different version of the block.
\Drupal::state()->set('block_test.cache_contexts', []);
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('');
$this->assertText($current_content, 'Block content displays.');
$old_content = $current_content;
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('user');
$this->assertText($old_content, 'Block content served from cache.');
$this->drupalLogout();
$this->drupalGet('user');
$this->assertText($current_content, 'Block content not served from cache.');
}
/**
* Test non-cacheable block.
*/
function testNoCache() {
\Drupal::state()->set('block_test.cache_max_age', 0);
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
// If max_age = 0 has no effect, the next request would be cached.
$this->drupalGet('');
$this->assertText($current_content, 'Block content displays.');
// A cached copy should not be served.
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('');
$this->assertText($current_content, 'Maximum age of zero prevents blocks from being cached.');
}
/**
* Test "user" cache context.
*/
function testCachePerUser() {
\Drupal::state()->set('block_test.cache_contexts', ['user']);
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalLogin($this->normalUser);
$this->drupalGet('');
$this->assertText($current_content, 'Block content displays.');
$old_content = $current_content;
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('');
$this->assertText($old_content, 'Block is served from per-user cache.');
$this->drupalLogin($this->normalUserAlt);
$this->drupalGet('');
$this->assertText($current_content, 'Per-user block cache is not served for other users.');
$this->drupalLogin($this->normalUser);
$this->drupalGet('');
$this->assertText($old_content, 'Per-user block cache is persistent.');
}
/**
* Test "url" cache context.
*/
function testCachePerPage() {
\Drupal::state()->set('block_test.cache_contexts', ['url']);
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('test-page');
$this->assertText($current_content, 'Block content displays on the test page.');
$old_content = $current_content;
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalGet('user');
$this->assertResponse(200);
$this->assertNoText($old_content, 'Block content cached for the test page does not show up for the user page.');
$this->drupalGet('test-page');
$this->assertResponse(200);
$this->assertText($old_content, 'Block content cached for the test page.');
}
}
@@ -1,73 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Crypt;
use Drupal\simpletest\WebTestBase;
/**
* Tests form in block caching.
*
* @group block
*/
class BlockFormInBlockTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'block_test', 'test_page_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable our test block.
$this->drupalPlaceBlock('test_form_in_block');
}
/**
* Test to see if form in block's redirect isn't cached.
*/
public function testCachePerPage() {
$form_values = ['email' => 'test@example.com'];
// Go to "test-page" and test if the block is enabled.
$this->drupalGet('test-page');
$this->assertResponse(200);
$this->assertText('Your .com email address.', 'form found');
// Make sure that we're currently still on /test-page after submitting the
// form.
$this->drupalPostForm(NULL, $form_values, t('Submit'));
$this->assertUrl('test-page');
$this->assertText(t('Your email address is @email', ['@email' => 'test@example.com']));
// Go to a different page and see if the block is enabled there as well.
$this->drupalGet('test-render-title');
$this->assertResponse(200);
$this->assertText('Your .com email address.', 'form found');
// Make sure that submitting the form didn't redirect us to the first page
// we submitted the form from after submitting the form from
// /test-render-title.
$this->drupalPostForm(NULL, $form_values, t('Submit'));
$this->assertUrl('test-render-title');
$this->assertText(t('Your email address is @email', ['@email' => 'test@example.com']));
}
/**
* Test the actual placeholders
*/
public function testPlaceholders() {
$this->drupalGet('test-multiple-forms');
$placeholder = 'form_action_' . Crypt::hashBase64('Drupal\Core\Form\FormBuilder::prepareForm');
$this->assertText('Form action: ' . $placeholder, 'placeholder found.');
}
}
@@ -1,73 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that a newly installed theme does not inherit blocks to its hidden
* regions.
*
* @group block
*/
class BlockHiddenRegionTest extends WebTestBase {
/**
* An administrative user to configure the test environment.
*/
protected $adminUser;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'block_test', 'search');
protected function setUp() {
parent::setUp();
// Create administrative user.
$this->adminUser = $this->drupalCreateUser(array(
'administer blocks',
'administer themes',
'search content',
)
);
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('search_form_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests that hidden regions do not inherit blocks when a theme is installed.
*/
public function testBlockNotInHiddenRegion() {
// Ensure that the search form block is displayed.
$this->drupalGet('');
$this->assertText('Search', 'Block was displayed on the front page.');
// Install "block_test_theme" and set it as the default theme.
$theme = 'block_test_theme';
// We need to install a non-hidden theme so that there is more than one
// local task.
\Drupal::service('theme_handler')->install(array($theme, 'stark'));
$this->config('system.theme')
->set('default', $theme)
->save();
// Installing a theme will cause the kernel terminate event to rebuild the
// router. Simulate that here.
\Drupal::service('router.builder')->rebuildIfNeeded();
// Ensure that "block_test_theme" is set as the default theme.
$this->drupalGet('admin/structure/block');
$this->assertText('Block test theme(' . t('active tab') . ')', 'Default local task on blocks admin page is the block test theme.');
// Ensure that the search form block is displayed.
$this->drupalGet('');
$this->assertText('Search', 'Block was displayed on the front page.');
}
}
@@ -1,51 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
/**
* Tests for Block module regarding hook_entity_operations_alter().
*
* @group block
*/
class BlockHookOperationTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'entity_test');
protected function setUp() {
parent::setUp();
$permissions = array(
'administer blocks',
);
// Create and log in user.
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
}
/**
* Tests the block list to see if the test_operation link is added.
*/
public function testBlockOperationAlter() {
// Add a test block, any block will do.
// Set the machine name so the test_operation link can be built later.
$block_id = Unicode::strtolower($this->randomMachineName(16));
$this->drupalPlaceBlock('system_powered_by_block', array('id' => $block_id));
// Get the Block listing.
$this->drupalGet('admin/structure/block');
$test_operation_link = 'admin/structure/block/manage/' . $block_id . '/test_operation';
// Test if the test_operation link is on the page.
$this->assertLinkByHref($test_operation_link);
}
}
@@ -1,50 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests block HTML ID validity.
*
* @group block
*/
class BlockHtmlTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'block_test');
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->rootUser);
// Enable the test_html block, to test HTML ID and attributes.
\Drupal::state()->set('block_test.attributes', array('data-custom-attribute' => 'foo'));
\Drupal::state()->set('block_test.content', $this->randomMachineName());
$this->drupalPlaceBlock('test_html', array('id' => 'test_html_block'));
// Enable a menu block, to test more complicated HTML.
$this->drupalPlaceBlock('system_menu_block:admin');
}
/**
* Tests for valid HTML for a block.
*/
function testHtml() {
$this->drupalGet('');
// Ensure that a block's ID is converted to an HTML valid ID, and that
// block-specific attributes are added to the same DOM element.
$this->assertFieldByXPath('//div[@id="block-test-html-block" and @data-custom-attribute="foo"]', NULL, 'HTML ID and attributes for test block are valid and on the same DOM element.');
// Ensure expected markup for a menu block.
$elements = $this->xpath('//nav[contains(@class, :nav-class)]/ul[contains(@class, :ul-class)]/li', array(':nav-class' => 'block-menu', ':ul-class' => 'menu'));
$this->assertTrue(!empty($elements), 'The proper block markup was found.');
}
}
@@ -1,33 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests block module's installation.
*
* @group block
*/
class BlockInstallTest extends WebTestBase {
public function testCacheTagInvalidationUponInstallation() {
// Warm the page cache.
$this->drupalGet('');
$this->assertNoText('Powered by Drupal');
$this->assertNoCacheTag('config:block_list');
// Install the block module, and place the "Powered by Drupal" block.
$this->container->get('module_installer')->install(['block', 'shortcut']);
$this->rebuildContainer();
$this->container->get('router.builder')->rebuild();
$this->drupalPlaceBlock('system_powered_by_block');
// Check the same page, block.module's hook_install() should have
// invalidated the 'rendered' cache tag to make blocks show up.
$this->drupalGet('');
$this->assertCacheTag('config:block_list');
$this->assertText('Powered by Drupal');
}
}
@@ -1,63 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\block\Entity\Block;
/**
* Tests that an active block assigned to a non-existing region triggers the
* warning message and is disabled.
*
* @group block
*/
class BlockInvalidRegionTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'block_test');
protected function setUp() {
parent::setUp();
// Create an admin user.
$admin_user = $this->drupalCreateUser(array(
'administer site configuration',
'access administration pages',
'administer blocks',
));
$this->drupalLogin($admin_user);
}
/**
* Tests that blocks assigned to invalid regions work correctly.
*/
function testBlockInInvalidRegion() {
// Enable a test block and place it in an invalid region.
$block = $this->drupalPlaceBlock('test_html');
$block->setRegion('invalid_region');
$block->save();
$warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block->id(), '%region' => 'invalid_region'));
// Clearing the cache should disable the test block placed in the invalid region.
$this->drupalPostForm('admin/config/development/performance', array(), 'Clear all caches');
$this->assertRaw($warning_message, 'Enabled block was in the invalid region and has been disabled.');
// Clear the cache to check if the warning message is not triggered.
$this->drupalPostForm('admin/config/development/performance', array(), 'Clear all caches');
$this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.');
// Place disabled test block in the invalid region of the default theme.
$block = Block::load($block->id());
$block->setRegion('invalid_region');
$block->save();
// Clear the cache to check if the warning message is not triggered.
$this->drupalPostForm('admin/config/development/performance', array(), 'Clear all caches');
$this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.');
}
}
@@ -1,77 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests display of menu blocks with multiple languages.
*
* @group block
*/
class BlockLanguageCacheTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'language', 'menu_ui'];
/**
* List of langcodes.
*
* @var array
*/
protected $langcodes = [];
protected function setUp() {
parent::setUp();
// Create test languages.
$this->langcodes = [ConfigurableLanguage::load('en')];
for ($i = 1; $i < 3; ++$i) {
$language = ConfigurableLanguage::create([
'id' => 'l' . $i,
'label' => $this->randomString(),
]);
$language->save();
$this->langcodes[$i] = $language;
}
}
/**
* Creates a block in a language, check blocks page in all languages.
*/
public function testBlockLinks() {
// Create admin user to be able to access block admin.
$admin_user = $this->drupalCreateUser([
'administer blocks',
'access administration pages',
'administer menu',
]);
$this->drupalLogin($admin_user);
// Create the block cache for all languages.
foreach ($this->langcodes as $langcode) {
$this->drupalGet('admin/structure/block', ['language' => $langcode]);
$this->clickLinkPartialName('Place block');
}
// Create a menu in the default language.
$edit['label'] = $this->randomMachineName();
$edit['id'] = Unicode::strtolower($edit['label']);
$this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
$this->assertText(t('Menu @label has been added.', ['@label' => $edit['label']]));
// Check that the block is listed for all languages.
foreach ($this->langcodes as $langcode) {
$this->drupalGet('admin/structure/block', ['language' => $langcode]);
$this->clickLinkPartialName('Place block');
$this->assertText($edit['label']);
}
}
}
@@ -1,185 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\block\Entity\Block;
/**
* Tests if a block can be configured to be only visible on a particular
* language.
*
* @group block
*/
class BlockLanguageTest extends WebTestBase {
/**
* An administrative user to configure the test environment.
*/
protected $adminUser;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('language', 'block', 'content_translation');
protected function setUp() {
parent::setUp();
// Create a new user, allow him to manage the blocks and the languages.
$this->adminUser = $this->drupalCreateUser(array('administer blocks', 'administer languages'));
$this->drupalLogin($this->adminUser);
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$this->assertText('French', 'Language added successfully.');
}
/**
* Tests the visibility settings for the blocks based on language.
*/
public function testLanguageBlockVisibility() {
// Check if the visibility setting is available.
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme);
$this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.');
$this->assertNoField('visibility[language][context_mapping][language]', 'Language type field is not visible.');
// Enable a standard block and set the visibility setting for one language.
$edit = array(
'visibility[language][langcodes][en]' => TRUE,
'id' => strtolower($this->randomMachineName(8)),
'region' => 'sidebar_first',
);
$this->drupalPostForm('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
// Change the default language.
$edit = array(
'site_default_language' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
// Check that a page has a block.
$this->drupalGet('en');
$this->assertText('Powered by Drupal', 'The body of the custom block appears on the page.');
// Check that a page doesn't has a block for the current language anymore.
$this->drupalGet('fr');
$this->assertNoText('Powered by Drupal', 'The body of the custom block does not appear on the page.');
}
/**
* Tests if the visibility settings are removed if the language is deleted.
*/
public function testLanguageBlockVisibilityLanguageDelete() {
// Enable a standard block and set the visibility setting for one language.
$edit = array(
'visibility' => array(
'language' => array(
'langcodes' => array(
'fr' => 'fr',
),
'context_mapping' => ['language' => '@language.current_language_context:language_interface'],
),
),
);
$block = $this->drupalPlaceBlock('system_powered_by_block', $edit);
// Check that we have the language in config after saving the setting.
$visibility = $block->getVisibility();
$this->assertEqual('fr', $visibility['language']['langcodes']['fr'], 'Language is set in the block configuration.');
// Delete the language.
$this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete'));
// Check that the language is no longer stored in the configuration after
// it is deleted.
$block = Block::load($block->id());
$visibility = $block->getVisibility();
$this->assertTrue(empty($visibility['language']['langcodes']['fr']), 'Language is no longer not set in the block configuration after deleting the block.');
// Ensure that the block visibility for language is gone from the UI.
$this->drupalGet('admin/structure/block');
$this->clickLink('Configure');
$elements = $this->xpath('//details[@id="edit-visibility-language"]');
$this->assertTrue(empty($elements));
}
/**
* Tests block language visibility with different language types.
*/
public function testMultipleLanguageTypes() {
// Customize content language detection to be different from interface
// language detection.
$edit = [
// Interface language detection: only using session.
'language_interface[enabled][language-url]' => FALSE,
'language_interface[enabled][language-session]' => TRUE,
// Content language detection: only using URL.
'language_content[configurable]' => TRUE,
'language_content[enabled][language-url]' => TRUE,
'language_content[enabled][language-interface]' => FALSE,
];
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Check if the visibility setting is available with a type setting.
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme);
$this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.');
$this->assertField('visibility[language][context_mapping][language]', 'Language type field is visible.');
// Enable a standard block and set visibility to French only.
$block_id = strtolower($this->randomMachineName(8));
$edit = [
'visibility[language][context_mapping][language]' => '@language.current_language_context:language_interface',
'visibility[language][langcodes][fr]' => TRUE,
'id' => $block_id,
'region' => 'sidebar_first',
];
$this->drupalPostForm('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
// Interface negotiation depends on request arguments.
$this->drupalGet('node', ['query' => ['language' => 'en']]);
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
$this->drupalGet('node', ['query' => ['language' => 'fr']]);
$this->assertText('Powered by Drupal', 'The body of the block appears on the page.');
// Log in again in order to clear the interface language stored in the
// session.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
// Content language does not depend on session/request arguments.
// It will fall back on English (site default) and not display the block.
$this->drupalGet('en');
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
$this->drupalGet('fr');
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
// Change visibility to now depend on content language for this block.
$edit = [
'visibility[language][context_mapping][language]' => '@language.current_language_context:language_content'
];
$this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block'));
// Content language negotiation does not depend on request arguments.
// It will fall back on English (site default) and not display the block.
$this->drupalGet('node', ['query' => ['language' => 'en']]);
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
$this->drupalGet('node', ['query' => ['language' => 'fr']]);
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
// Content language negotiation depends on path prefix.
$this->drupalGet('en');
$this->assertNoText('Powered by Drupal', 'The body of the block does not appear on the page.');
$this->drupalGet('fr');
$this->assertText('Powered by Drupal', 'The body of the block appears on the page.');
}
}
@@ -1,80 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Html;
use Drupal\simpletest\WebTestBase;
/**
* Tests blocks are being rendered in order by weight.
*
* @group block
*/
class BlockRenderOrderTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['node', 'block'];
protected function setUp() {
parent::setUp();
// Create a test user.
$end_user = $this->drupalCreateUser([
'access content',
]);
$this->drupalLogin($end_user);
}
/**
* Tests the render order of the blocks.
*/
public function testBlockRenderOrder() {
// Enable test blocks and place them in the same region.
$region = 'header';
$test_blocks = [
'stark_powered' => [
'weight' => '-3',
'id' => 'stark_powered',
'label' => 'Test block A',
],
'stark_by' => [
'weight' => '3',
'id' => 'stark_by',
'label' => 'Test block C',
],
'stark_drupal' => [
'weight' => '3',
'id' => 'stark_drupal',
'label' => 'Test block B',
],
];
// Place the test blocks.
foreach ($test_blocks as $test_block) {
$this->drupalPlaceBlock('system_powered_by_block', [
'label' => $test_block['label'],
'region' => $region,
'weight' => $test_block['weight'],
'id' => $test_block['id'],
]);
}
$this->drupalGet('');
$test_content = $this->getRawContent('');
$controller = $this->container->get('entity_type.manager')->getStorage('block');
foreach ($controller->loadMultiple() as $return_block) {
$id = $return_block->id();
if ($return_block_weight = $return_block->getWeight()) {
$this->assertTrue($test_blocks[$id]['weight'] == $return_block_weight, 'Block weight is set as "' . $return_block_weight . '" for ' . $id . ' block.');
$position[$id] = strpos($test_content, Html::getClass('block-' . $test_blocks[$id]['id']));
}
}
$this->assertTrue($position['stark_powered'] < $position['stark_by'], 'Blocks with different weight are rendered in the correct order.');
$this->assertTrue($position['stark_drupal'] < $position['stark_by'], 'Blocks with identical weight are rendered in alphabetical order.');
}
}
@@ -1,119 +0,0 @@
<?php
namespace Drupal\block\Tests;
/**
* Tests branding block display.
*
* @group block
*/
class BlockSystemBrandingTest extends BlockTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set a site slogan.
$this->config('system.site')
->set('slogan', 'Community plumbing')
->save();
// Add the system branding block to the page.
$this->drupalPlaceBlock('system_branding_block', ['region' => 'header', 'id' => 'site-branding']);
}
/**
* Tests system branding block configuration.
*/
public function testSystemBrandingSettings() {
$site_logo_xpath = '//div[@id="block-site-branding"]//a[@class="site-logo"]';
$site_name_xpath = '//div[@id="block-site-branding"]//div[@class="site-name"]';
$site_slogan_xpath = '//div[@id="block-site-branding"]//div[@class="site-slogan"]';
// Set default block settings.
$this->drupalGet('');
$site_logo_element = $this->xpath($site_logo_xpath);
$site_name_element = $this->xpath($site_name_xpath);
$site_slogan_element = $this->xpath($site_slogan_xpath);
// Test that all branding elements are displayed.
$this->assertTrue(!empty($site_logo_element), 'The branding block logo was found.');
$this->assertTrue(!empty($site_name_element), 'The branding block site name was found.');
$this->assertTrue(!empty($site_slogan_element), 'The branding block slogan was found.');
$this->assertCacheTag('config:system.site');
// Be sure the slogan is XSS-filtered.
$this->config('system.site')
->set('slogan', '<script>alert("Community carpentry");</script>')
->save();
$this->drupalGet('');
$site_slogan_element = $this->xpath($site_slogan_xpath);
$this->assertEqual($site_slogan_element[0], 'alert("Community carpentry");', 'The site slogan was XSS-filtered.');
// Turn just the logo off.
$this->config('block.block.site-branding')
->set('settings.use_site_logo', 0)
->save();
$this->drupalGet('');
$site_logo_element = $this->xpath($site_logo_xpath);
$site_name_element = $this->xpath($site_name_xpath);
$site_slogan_element = $this->xpath($site_slogan_xpath);
// Re-test all branding elements.
$this->assertTrue(empty($site_logo_element), 'The branding block logo was disabled.');
$this->assertTrue(!empty($site_name_element), 'The branding block site name was found.');
$this->assertTrue(!empty($site_slogan_element), 'The branding block slogan was found.');
$this->assertCacheTag('config:system.site');
// Turn just the site name off.
$this->config('block.block.site-branding')
->set('settings.use_site_logo', 1)
->set('settings.use_site_name', 0)
->save();
$this->drupalGet('');
$site_logo_element = $this->xpath($site_logo_xpath);
$site_name_element = $this->xpath($site_name_xpath);
$site_slogan_element = $this->xpath($site_slogan_xpath);
// Re-test all branding elements.
$this->assertTrue(!empty($site_logo_element), 'The branding block logo was found.');
$this->assertTrue(empty($site_name_element), 'The branding block site name was disabled.');
$this->assertTrue(!empty($site_slogan_element), 'The branding block slogan was found.');
$this->assertCacheTag('config:system.site');
// Turn just the site slogan off.
$this->config('block.block.site-branding')
->set('settings.use_site_name', 1)
->set('settings.use_site_slogan', 0)
->save();
$this->drupalGet('');
$site_logo_element = $this->xpath($site_logo_xpath);
$site_name_element = $this->xpath($site_name_xpath);
$site_slogan_element = $this->xpath($site_slogan_xpath);
// Re-test all branding elements.
$this->assertTrue(!empty($site_logo_element), 'The branding block logo was found.');
$this->assertTrue(!empty($site_name_element), 'The branding block site name was found.');
$this->assertTrue(empty($site_slogan_element), 'The branding block slogan was disabled.');
$this->assertCacheTag('config:system.site');
// Turn the site name and the site slogan off.
$this->config('block.block.site-branding')
->set('settings.use_site_name', 0)
->set('settings.use_site_slogan', 0)
->save();
$this->drupalGet('');
$site_logo_element = $this->xpath($site_logo_xpath);
$site_name_element = $this->xpath($site_name_xpath);
$site_slogan_element = $this->xpath($site_slogan_xpath);
// Re-test all branding elements.
$this->assertTrue(!empty($site_logo_element), 'The branding block logo was found.');
$this->assertTrue(empty($site_name_element), 'The branding block site name was disabled.');
$this->assertTrue(empty($site_slogan_element), 'The branding block slogan was disabled.');
$this->assertCacheTag('config:system.site');
}
}
@@ -1,48 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\block\Entity\Block;
use Drupal\simpletest\WebTestBase;
/**
* Tests the block_theme_suggestions_block() function.
*
* @group block
*/
class BlockTemplateSuggestionsTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block');
/**
* Tests template suggestions from block_theme_suggestions_block().
*/
function testBlockThemeHookSuggestions() {
// Define a block with a derivative to be preprocessed, which includes both
// an underscore (not transformed) and a hyphen (transformed to underscore),
// and generates possibilities for each level of derivative.
// @todo Clarify this comment.
$block = Block::create(array(
'plugin' => 'system_menu_block:admin',
'region' => 'footer',
'id' => 'machinename',
));
$variables = array();
$plugin = $block->getPlugin();
$variables['elements']['#configuration'] = $plugin->getConfiguration();
$variables['elements']['#plugin_id'] = $plugin->getPluginId();
$variables['elements']['#id'] = $block->id();
$variables['elements']['#base_plugin_id'] = $plugin->getBaseId();
$variables['elements']['#derivative_plugin_id'] = $plugin->getDerivativeId();
$variables['elements']['content'] = array();
$suggestions = block_theme_suggestions_block($variables);
$this->assertEqual($suggestions, array('block__system', 'block__system_menu_block', 'block__system_menu_block__admin', 'block__machinename'));
}
}
-551
View File
@@ -1,551 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Html;
use Drupal\block\Entity\Block;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests basic block functionality.
*
* @group block
*/
class BlockTest extends BlockTestBase {
/**
* Tests block visibility.
*/
public function testBlockVisibility() {
$block_name = 'system_powered_by_block';
// Create a random title for the block.
$title = $this->randomMachineName(8);
// Enable a standard block.
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'id' => strtolower($this->randomMachineName(8)),
'region' => 'sidebar_first',
'settings[label]' => $title,
'settings[label_display]' => TRUE,
];
// Set the block to be hidden on any user path, and to be shown only to
// authenticated users.
$edit['visibility[request_path][pages]'] = '/user*';
$edit['visibility[request_path][negate]'] = TRUE;
$edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
$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'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->clickLink('Configure');
$this->assertFieldChecked('edit-visibility-request-path-negate-1');
$this->drupalGet('');
$this->assertText($title, 'Block was displayed on the front page.');
$this->drupalGet('user');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
// Confirm that the block is not displayed to anonymous users.
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoText($title, 'Block was not displayed to anonymous users.');
// Confirm that an empty block is not displayed.
$this->assertNoText('Powered by Drupal', 'Empty block not displayed.');
$this->assertNoRaw('sidebar-first', 'Empty sidebar-first region is not displayed.');
}
/**
* 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.
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'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.
$edit['visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']'] = TRUE;
$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 = [
'visibility[user_role][roles][' . RoleInterface::AUTHENTICATED_ID . ']' => FALSE,
];
$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'));
}
/**
* Test block visibility when leaving "pages" textarea empty.
*/
public function testBlockVisibilityListedEmpty() {
$block_name = 'system_powered_by_block';
// Create a random title for the block.
$title = $this->randomMachineName(8);
// Enable a standard block.
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'id' => strtolower($this->randomMachineName(8)),
'region' => 'sidebar_first',
'settings[label]' => $title,
'visibility[request_path][negate]' => TRUE,
];
// Set the block to be hidden on any user path, and to be shown only to
// authenticated users.
$this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->drupalGet('user');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
$this->drupalGet('USER');
$this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.');
// Confirm that the block is not displayed to anonymous users.
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.');
}
/**
* Tests adding a block from the library page with a weight query string.
*/
public function testAddBlockFromLibraryWithWeight() {
$default_theme = $this->config('system.theme')->get('default');
// Test one positive, zero, and one negative weight.
foreach (['7', '0', '-9'] as $weight) {
$options = [
'query' => [
'region' => 'sidebar_first',
'weight' => $weight,
],
];
$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.');
$this->assertEqual(t('Place block'), (string) $links[0], 'Found the expected link text.');
list($path, $query_string) = explode('?', $links[0]['href'], 2);
parse_str($query_string, $query_parts);
$this->assertEqual($weight, $query_parts['weight'], 'Found the expected weight query string.');
// 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'));
// Ensure that the block was created with the expected weight.
/** @var \Drupal\block\BlockInterface $block */
$block = Block::load($block_id);
$this->assertEqual($weight, $block->getWeight(), 'Found the block with expected weight.');
}
}
/**
* Test configuring and moving a module-define block to specific regions.
*/
public function testBlock() {
// Place page title block to test error messages.
$this->drupalPlaceBlock('page_title_block');
// Disable the block.
$this->drupalGet('admin/structure/block');
$this->clickLink('Disable');
// Select the 'Powered by Drupal' block to be configured and moved.
$block = [];
$block['id'] = 'system_powered_by_block';
$block['settings[label]'] = $this->randomMachineName(8);
$block['settings[label_display]'] = TRUE;
$block['theme'] = $this->config('system.theme')->get('default');
$block['region'] = 'header';
// Set block title to confirm that interface works and override any custom titles.
$this->drupalPostForm('admin/structure/block/add/' . $block['id'] . '/' . $block['theme'], ['settings[label]' => $block['settings[label]'], 'settings[label_display]' => $block['settings[label_display]'], 'id' => $block['id'], 'region' => $block['region']], t('Save block'));
$this->assertText(t('The block configuration has been saved.'), 'Block title set.');
// Check to see if the block was created by checking its configuration.
$instance = Block::load($block['id']);
$this->assertEqual($instance->label(), $block['settings[label]'], 'Stored block title found.');
// Check whether the block can be moved to all available regions.
foreach ($this->regions as $region) {
$this->moveBlockToRegion($block, $region);
}
// Disable the block.
$this->drupalGet('admin/structure/block');
$this->clickLink('Disable');
// Confirm that the block is now listed as disabled.
$this->assertText(t('The block settings have been updated.'), 'Block successfully moved to disabled region.');
// Confirm that the block instance title and markup are not displayed.
$this->drupalGet('node');
$this->assertNoText(t($block['settings[label]']));
// Check for <div id="block-my-block-instance-name"> if the machine name
// is my_block_instance_name.
$xpath = $this->buildXPathQuery('//div[@id=:id]/*', [':id' => 'block-' . str_replace('_', '-', strtolower($block['id']))]);
$this->assertNoFieldByXPath($xpath, FALSE, 'Block found in no regions.');
// Test deleting the block from the edit form.
$this->drupalGet('admin/structure/block/manage/' . $block['id']);
$this->clickLink(t('Remove block'));
$this->assertRaw(t('Are you sure you want to remove the block @name?', ['@name' => $block['settings[label]']]));
$this->drupalPostForm(NULL, [], t('Remove'));
$this->assertRaw(t('The block %name has been removed.', ['%name' => $block['settings[label]']]));
// Test deleting a block via "Configure block" link.
$block = $this->drupalPlaceBlock('system_powered_by_block');
$this->drupalGet('admin/structure/block/manage/' . $block->id(), ['query' => ['destination' => 'admin']]);
$this->clickLink(t('Remove block'));
$this->assertRaw(t('Are you sure you want to remove the block @name?', ['@name' => $block->label()]));
$this->drupalPostForm(NULL, [], t('Remove'));
$this->assertRaw(t('The block %name has been removed.', ['%name' => $block->label()]));
$this->assertUrl('admin');
$this->assertNoRaw($block->id());
}
/**
* Tests that the block form has a theme selector when not passed via the URL.
*/
public function testBlockThemeSelector() {
// Install all themes.
\Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
$theme_settings = $this->config('system.theme');
foreach (['bartik', 'seven', 'stark'] as $theme) {
$this->drupalGet('admin/structure/block/list/' . $theme);
$this->assertTitle(t('Block layout') . ' | Drupal');
// Select the 'Powered by Drupal' block to be placed.
$block = [];
$block['id'] = strtolower($this->randomMachineName());
$block['theme'] = $theme;
$block['region'] = 'content';
$this->drupalPostForm('admin/structure/block/add/system_powered_by_block', $block, t('Save block'));
$this->assertText(t('The block configuration has been saved.'));
$this->assertUrl('admin/structure/block/list/' . $theme . '?block-placement=' . Html::getClass($block['id']));
// Set the default theme and ensure the block is placed.
$theme_settings->set('default', $theme)->save();
$this->drupalGet('');
$elements = $this->xpath('//div[@id = :id]', [':id' => Html::getUniqueId('block-' . $block['id'])]);
$this->assertTrue(!empty($elements), 'The block was found.');
}
}
/**
* Test block display of theme titles.
*/
public function testThemeName() {
// Enable the help block.
$this->drupalPlaceBlock('help_block', ['region' => 'help']);
$this->drupalPlaceBlock('local_tasks_block');
// Explicitly set the default and admin themes.
$theme = 'block_test_specialchars_theme';
\Drupal::service('theme_handler')->install([$theme]);
\Drupal::service('router.builder')->rebuild();
$this->drupalGet('admin/structure/block');
$this->assertEscaped('<"Cat" & \'Mouse\'>');
$this->drupalGet('admin/structure/block/list/block_test_specialchars_theme');
$this->assertEscaped('Demonstrate block regions (<"Cat" & \'Mouse\'>)');
}
/**
* Test block title display settings.
*/
public function testHideBlockTitle() {
$block_name = 'system_powered_by_block';
// Create a random title for the block.
$title = $this->randomMachineName(8);
$id = strtolower($this->randomMachineName(8));
// Enable a standard block.
$default_theme = $this->config('system.theme')->get('default');
$edit = [
'id' => $id,
'region' => 'sidebar_first',
'settings[label]' => $title,
];
$this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->drupalGet('user');
$this->assertNoText($title, 'Block title was not displayed by default.');
$edit = [
'settings[label_display]' => TRUE,
];
$this->drupalPostForm('admin/structure/block/manage/' . $id, $edit, t('Save block'));
$this->assertText('The block configuration has been saved.', 'Block was saved');
$this->drupalGet('admin/structure/block/manage/' . $id);
$this->assertFieldChecked('edit-settings-label-display', 'The display_block option has the correct default value on the configuration form.');
$this->drupalGet('user');
$this->assertText($title, 'Block title was displayed when enabled.');
}
/**
* 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'.
*/
public function moveBlockToRegion(array $block, $region) {
// Set the created block to a specific region.
$block += ['theme' => $this->config('system.theme')->get('default')];
$edit = [];
$edit['blocks[' . $block['id'] . '][region]'] = $region;
$this->drupalPostForm('admin/structure/block', $edit, t('Save blocks'));
// Confirm that the block was moved to the proper region.
$this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', [ '%region_name' => $region]));
// Confirm that the block is being displayed.
$this->drupalGet('');
$this->assertText(t($block['settings[label]']), 'Block successfully being displayed on the page.');
// Confirm that the custom block was found at the proper region.
$xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', [
':region-class' => 'region region-' . Html::getClass($region),
':block-id' => 'block-' . str_replace('_', '-', strtolower($block['id'])),
]);
$this->assertFieldByXPath($xpath, NULL, t('Block found in %region_name region.', ['%region_name' => Html::getClass($region)]));
}
/**
* 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>"
*/
public function testBlockCacheTags() {
// The page cache only works for anonymous users.
$this->drupalLogout();
// Enable page caching.
$config = $this->config('system.performance');
$config->set('cache.page.max_age', 300);
$config->save();
// Place the "Powered by Drupal" block.
$block = $this->drupalPlaceBlock('system_powered_by_block', ['id' => 'powered']);
// 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');
$cid_parts = [\Drupal::url('<front>', [], ['absolute' => TRUE]), 'html'];
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$expected_cache_tags = [
'config:block_list',
'block_view',
'config:block.block.powered',
'config:user.role.anonymous',
'http_response',
'rendered',
];
sort($expected_cache_tags);
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
$expected_cache_tags = [
'block_view',
'config:block.block.powered',
'rendered',
];
sort($expected_cache_tags);
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
// The "Powered by Drupal" block is modified; verify a cache miss.
$block->setRegion('content');
$block->save();
$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.
$block_2 = $this->drupalPlaceBlock('system_powered_by_block', ['id' => 'powered-2']);
$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');
$cid_parts = [\Drupal::url('<front>', [], ['absolute' => TRUE]), 'html'];
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$expected_cache_tags = [
'config:block_list',
'block_view',
'config:block.block.powered',
'config:block.block.powered-2',
'config:user.role.anonymous',
'http_response',
'rendered',
];
sort($expected_cache_tags);
$this->assertEqual($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = [
'block_view',
'config:block.block.powered',
'rendered',
];
sort($expected_cache_tags);
$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));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = [
'block_view',
'config:block.block.powered-2',
'rendered',
];
sort($expected_cache_tags);
$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));
$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', ['powered', 'powered-2']);
$this->drupalGet('<front>');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
}
/**
* 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');
}
/**
* 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']);
$this->config('system.theme')->set('default', 'seven')->save();
$block = $this->drupalPlaceBlock('system_powered_by_block', ['theme' => 'seven', 'region' => 'help']);
$this->drupalGet('<front>');
$this->assertText('Powered by Drupal');
$this->config('system.theme')->set('default', 'classy')->save();
$theme_handler->uninstall(['seven']);
// Ensure that the block configuration does not exist anymore.
$this->assertIdentical(NULL, Block::load($block->id()));
}
/**
* 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');
}
/**
* 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()
]);
}
}
@@ -1,329 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\Component\Utility\Html;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the block configuration UI exists and stores data correctly.
*
* @group block
*/
class BlockUiTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'block_test', 'help', 'condition_test'];
protected $regions;
/**
* The submitted block values used by this test.
*
* @var array
*/
protected $blockValues;
/**
* The block entities used by this test.
*
* @var \Drupal\block\BlockInterface[]
*/
protected $blocks;
/**
* An administrative user to configure the test environment.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
'administer blocks',
'access administration pages',
]);
$this->drupalLogin($this->adminUser);
// Enable some test blocks.
$this->blockValues = [
[
'label' => 'Tools',
'tr' => '5',
'plugin_id' => 'system_menu_block:tools',
'settings' => ['region' => 'sidebar_second', 'id' => 'tools'],
'test_weight' => '-1',
],
[
'label' => 'Powered by Drupal',
'tr' => '16',
'plugin_id' => 'system_powered_by_block',
'settings' => ['region' => 'footer', 'id' => 'powered'],
'test_weight' => '0',
],
];
$this->blocks = [];
foreach ($this->blockValues as $values) {
$this->blocks[] = $this->drupalPlaceBlock($values['plugin_id'], $values['settings']);
}
}
/**
* Test block demo page exists and functions correctly.
*/
public function testBlockDemoUiPage() {
$this->drupalPlaceBlock('help_block', ['region' => 'help']);
$this->drupalGet('admin/structure/block');
$this->clickLink(t('Demonstrate block regions (@theme)', ['@theme' => 'Classy']));
$elements = $this->xpath('//div[contains(@class, "region-highlighted")]/div[contains(@class, "block-region") and contains(text(), :title)]', [':title' => 'Highlighted']);
$this->assertTrue(!empty($elements), 'Block demo regions are shown.');
\Drupal::service('theme_handler')->install(['test_theme']);
$this->drupalGet('admin/structure/block/demo/test_theme');
$this->assertEscaped('<strong>Test theme</strong>');
\Drupal::service('theme_handler')->install(['stable']);
$this->drupalGet('admin/structure/block/demo/stable');
$this->assertResponse(404, 'Hidden themes that are not the default theme are not supported by the block demo screen');
}
/**
* Test block admin page exists and functions correctly.
*/
public function testBlockAdminUiPage() {
// Visit the blocks admin ui.
$this->drupalGet('admin/structure/block');
// Look for the blocks table.
$blocks_table = $this->xpath("//table[@id='blocks']");
$this->assertTrue(!empty($blocks_table), 'The blocks table is being rendered.');
// Look for test blocks in the table.
foreach ($this->blockValues as $delta => $values) {
$block = $this->blocks[$delta];
$label = $block->label();
$element = $this->xpath('//*[@id="blocks"]/tbody/tr[' . $values['tr'] . ']/td[1]/text()');
$this->assertTrue((string) $element[0] == $label, 'The "' . $label . '" block title is set inside the ' . $values['settings']['region'] . ' region.');
// Look for a test block region select form element.
$this->assertField('blocks[' . $values['settings']['id'] . '][region]', 'The block "' . $values['label'] . '" has a region assignment field.');
// Move the test block to the header region.
$edit['blocks[' . $values['settings']['id'] . '][region]'] = 'header';
// Look for a test block weight select form element.
$this->assertField('blocks[' . $values['settings']['id'] . '][weight]', 'The block "' . $values['label'] . '" has a weight assignment field.');
// Change the test block's weight.
$edit['blocks[' . $values['settings']['id'] . '][weight]'] = $values['test_weight'];
}
$this->drupalPostForm('admin/structure/block', $edit, t('Save blocks'));
foreach ($this->blockValues as $values) {
// Check if the region and weight settings changes have persisted.
$this->assertOptionSelected(
'edit-blocks-' . $values['settings']['id'] . '-region',
'header',
'The block "' . $label . '" has the correct region assignment (header).'
);
$this->assertOptionSelected(
'edit-blocks-' . $values['settings']['id'] . '-weight',
$values['test_weight'],
'The block "' . $label . '" has the correct weight assignment (' . $values['test_weight'] . ').'
);
}
// Add a block with a machine name the same as a region name.
$this->drupalPlaceBlock('system_powered_by_block', ['region' => 'header', 'id' => 'header']);
$this->drupalGet('admin/structure/block');
$element = $this->xpath('//tr[contains(@class, :class)]', [':class' => 'region-title-header']);
$this->assertTrue(!empty($element));
// Ensure hidden themes do not appear in the UI. Enable another non base
// theme and place the local tasks block.
$this->assertTrue(\Drupal::service('theme_handler')->themeExists('classy'), 'The classy base theme is enabled');
$this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']);
\Drupal::service('theme_installer')->install(['stable', 'stark']);
$this->drupalGet('admin/structure/block');
$theme_handler = \Drupal::service('theme_handler');
$this->assertLink($theme_handler->getName('classy'));
$this->assertLink($theme_handler->getName('stark'));
$this->assertNoLink($theme_handler->getName('stable'));
$this->drupalGet('admin/structure/block/list/stable');
$this->assertResponse(404, 'Placing blocks through UI is not possible for a hidden base theme.');
\Drupal::configFactory()->getEditable('system.theme')->set('admin', 'stable')->save();
\Drupal::service('router.builder')->rebuildIfNeeded();
$this->drupalPlaceBlock('local_tasks_block', ['region' => 'header', 'theme' => 'stable']);
$this->drupalGet('admin/structure/block');
$this->assertLink($theme_handler->getName('stable'));
$this->drupalGet('admin/structure/block/list/stable');
$this->assertResponse(200, 'Placing blocks through UI is possible for a hidden base theme that is the admin theme.');
}
/**
* Tests the block categories on the listing page.
*/
public function testCandidateBlockList() {
$arguments = [
':title' => 'Display message',
':category' => 'Block test',
':href' => 'admin/structure/block/add/test_block_instantiation/classy',
];
$pattern = '//tr[.//td/div[text()=:title] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]';
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the category for its module.');
// Trigger the custom category addition in block_test_block_alter().
$this->container->get('state')->set('block_test_info_alter', TRUE);
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$arguments[':category'] = 'Custom category';
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in a custom category controlled by block_test_block_alter().');
}
/**
* Tests the behavior of unsatisfied context-aware blocks.
*/
public function testContextAwareUnsatisfiedBlocks() {
$arguments = [
':category' => 'Block test',
':href' => 'admin/structure/block/add/test_context_aware_unsatisfied/classy',
':text' => 'Test context-aware unsatisfied block',
];
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath('//tr[.//td/div[text()=:text] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]', $arguments);
$this->assertTrue(empty($elements), 'The context-aware test block does not appear.');
$definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware_unsatisfied');
$this->assertTrue(!empty($definition), 'The context-aware test block does not exist.');
}
/**
* Tests the behavior of context-aware blocks.
*/
public function testContextAwareBlocks() {
$expected_text = '<div id="test_context_aware--username">' . \Drupal::currentUser()->getUsername() . '</div>';
$this->drupalGet('');
$this->assertNoText('Test context-aware block');
$this->assertNoRaw($expected_text);
$block_url = 'admin/structure/block/add/test_context_aware/classy';
$arguments = [
':title' => 'Test context-aware block',
':category' => 'Block test',
':href' => $block_url,
];
$pattern = '//tr[.//td/div[text()=:title] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]';
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The context-aware test block appears.');
$definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware');
$this->assertTrue(!empty($definition), 'The context-aware test block exists.');
$edit = [
'region' => 'content',
'settings[context_mapping][user]' => '@block_test.multiple_static_context:userB',
];
$this->drupalPostForm($block_url, $edit, 'Save block');
$this->drupalGet('');
$this->assertText('Test context-aware block');
$this->assertText('User context found.');
$this->assertRaw($expected_text);
// Test context mapping allows empty selection for optional contexts.
$this->drupalGet('admin/structure/block/manage/testcontextawareblock');
$edit = [
'settings[context_mapping][user]' => '',
];
$this->drupalPostForm(NULL, $edit, 'Save block');
$this->drupalGet('');
$this->assertText('No context mapping selected.');
$this->assertNoText('User context found.');
// Tests that conditions with missing context are not displayed.
$this->drupalGet('admin/structure/block/manage/testcontextawareblock');
$this->assertNoRaw('No existing type');
$this->assertNoFieldByXPath('//*[@name="visibility[condition_test_no_existing_type][negate]"]');
}
/**
* Tests that the BlockForm populates machine name correctly.
*/
public function testMachineNameSuggestion() {
$url = 'admin/structure/block/add/test_block_instantiation/classy';
$this->drupalGet($url);
$this->assertFieldByName('id', 'displaymessage', 'Block form uses raw machine name suggestion when no instance already exists.');
$edit = ['region' => 'content'];
$this->drupalPostForm($url, $edit, 'Save block');
$this->assertText('The block configuration has been saved.');
// Now, check to make sure the form starts by autoincrementing correctly.
$this->drupalGet($url);
$this->assertFieldByName('id', 'displaymessage_2', 'Block form appends _2 to plugin-suggested machine name when an instance already exists.');
$this->drupalPostForm($url, $edit, 'Save block');
$this->assertText('The block configuration has been saved.');
// And verify that it continues working beyond just the first two.
$this->drupalGet($url);
$this->assertFieldByName('id', 'displaymessage_3', 'Block form appends _3 to plugin-suggested machine name when two instances already exist.');
}
/**
* Tests the block placement indicator.
*/
public function testBlockPlacementIndicator() {
// Select the 'Powered by Drupal' block to be placed.
$block = [];
$block['id'] = strtolower($this->randomMachineName());
$block['theme'] = 'classy';
$block['region'] = 'content';
// After adding a block, it will indicate which block was just added.
$this->drupalPostForm('admin/structure/block/add/system_powered_by_block', $block, t('Save block'));
$this->assertUrl('admin/structure/block/list/classy?block-placement=' . Html::getClass($block['id']));
// Resaving the block page will remove the block indicator.
$this->drupalPostForm(NULL, [], t('Save blocks'));
$this->assertUrl('admin/structure/block/list/classy');
}
/**
* Tests if validation errors are passed plugin form to the parent form.
*/
public function testBlockValidateErrors() {
$this->drupalPostForm('admin/structure/block/add/test_settings_validation/classy', ['region' => 'content', 'settings[digits]' => 'abc'], t('Save block'));
$arguments = [':message' => 'Only digits are allowed'];
$pattern = '//div[contains(@class,"messages messages--error")]/div[contains(text()[2],:message)]';
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue($elements, 'Plugin error message found in parent form.');
$error_class_pattern = '//div[contains(@class,"form-item-settings-digits")]/input[contains(@class,"error")]';
$error_class = $this->xpath($error_class_pattern);
$this->assertTrue($error_class, 'Plugin error class found in parent form.');
}
/**
* Tests that the enable/disable routes are protected from CSRF.
*/
public function testRouteProtection() {
// Get the first block generated in our setUp method.
/** @var \Drupal\block\BlockInterface $block */
$block = reset($this->blocks);
// Ensure that the enable and disable routes are protected.
$this->drupalGet('admin/structure/block/manage/' . $block->id() . '/disable');
$this->assertResponse(403);
$this->drupalGet('admin/structure/block/manage/' . $block->id() . '/enable');
$this->assertResponse(403);
}
}
@@ -1,167 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
use Drupal\system\Entity\Menu;
use Drupal\views\Entity\View;
/**
* Tests that the block module properly escapes block descriptions.
*
* @group block
*/
class BlockXssTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'block_content', 'menu_ui', 'views'];
/**
* Tests that nothing is escaped other than the blocks explicitly tested.
*/
public function testNoUnexpectedEscaping() {
$this->drupalLogin($this->drupalCreateUser(['administer blocks', 'access administration pages']));
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertNoEscaped('<');
}
/**
* Tests XSS in title.
*/
public function testXssInTitle() {
$this->container->get('module_installer')->install(['block_test']);
$this->drupalPlaceBlock('test_xss_title', ['label' => '<script>alert("XSS label");</script>']);
\Drupal::state()->set('block_test.content', $this->randomMachineName());
$this->drupalGet('');
$this->assertNoRaw('<script>alert("XSS label");</script>', 'The block title was properly sanitized when rendered.');
$this->drupalLogin($this->drupalCreateUser(['administer blocks', 'access administration pages']));
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/list/' . $default_theme);
$this->assertNoRaw("<script>alert('XSS subject');</script>", 'The block title was properly sanitized in Block Plugin UI Admin page.');
}
/**
* Tests XSS in category.
*/
public function testXssInCategory() {
$this->container->get('module_installer')->install(['block_test']);
$this->drupalPlaceBlock('test_xss_title');
$this->drupalLogin($this->drupalCreateUser(['administer blocks', 'access administration pages']));
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertNoRaw("<script>alert('XSS category');</script>");
}
/**
* Tests various modules that provide blocks for XSS.
*/
public function testBlockXss() {
$this->drupalLogin($this->rootUser);
$this->doViewTest();
$this->doMenuTest();
$this->doBlockContentTest();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertNoRaw('&amp;lt;', 'The page does not have double escaped HTML tags.');
}
/**
* Tests XSS coming from View block labels.
*/
protected function doViewTest() {
// Create a View without a custom label for its block Display. The
// admin_label of the block then becomes just the View's label.
$view = View::create([
'id' => $this->randomMachineName(),
'label' => '<script>alert("view1");</script>',
]);
$view->addDisplay('block');
$view->save();
// Create a View with a custom label for its block Display. The
// admin_label of the block then becomes the View's label combined with
// the Display's label.
$view = View::create([
'id' => $this->randomMachineName(),
'label' => '<script>alert("view2");</script>',
]);
$view->addDisplay('block', 'Fish & chips');
$view->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
// \Drupal\views\Plugin\Derivative\ViewsBlock::getDerivativeDefinitions()
// has a different code path for an admin label based only on the View
// label versus one based on both the View label and the Display label.
// Ensure that this test is covering both code paths by asserting the
// absence of a ":" for the first View and the presence of a ":" for the
// second one. Note that the second assertion is redundant with the one
// further down which also checks for the Display label, but is included
// here for clarity.
$this->assertNoEscaped('<script>alert("view1");</script>:');
$this->assertEscaped('<script>alert("view2");</script>:');
// Assert that the blocks have their admin labels escaped and
// don't appear anywhere unescaped.
$this->assertEscaped('<script>alert("view1");</script>');
$this->assertNoRaw('<script>alert("view1");</script>');
$this->assertEscaped('<script>alert("view2");</script>: Fish & chips');
$this->assertNoRaw('<script>alert("view2");</script>');
$this->assertNoRaw('Fish & chips');
// Assert the Display label doesn't appear anywhere double escaped.
$this->assertNoRaw('Fish & chips');
$this->assertNoRaw('Fish &amp;amp; chips');
}
/**
* Tests XSS coming from Menu block labels.
*/
protected function doMenuTest() {
Menu::create([
'id' => $this->randomMachineName(),
'label' => '<script>alert("menu");</script>',
])->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertEscaped('<script>alert("menu");</script>');
$this->assertNoRaw('<script>alert("menu");</script>');
}
/**
* Tests XSS coming from Block Content block info.
*/
protected function doBlockContentTest() {
BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => TRUE,
])->save();
BlockContent::create([
'type' => 'basic',
'info' => '<script>alert("block_content");</script>',
])->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertEscaped('<script>alert("block_content");</script>');
$this->assertNoRaw('<script>alert("block_content");</script>');
}
}
@@ -1,73 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the new default theme gets blocks.
*
* @group block
*/
class NewDefaultThemeBlocksTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block');
/**
* Check the enabled Bartik blocks are correctly copied over.
*/
function testNewDefaultThemeBlocks() {
$default_theme = $this->config('system.theme')->get('default');
// Add two instances of the user login block.
$this->drupalPlaceBlock('user_login_block', array(
'id' => $default_theme . '_' . strtolower($this->randomMachineName(8)),
));
$this->drupalPlaceBlock('user_login_block', array(
'id' => $default_theme . '_' . strtolower($this->randomMachineName(8)),
));
// Add an instance of a different block.
$this->drupalPlaceBlock('system_powered_by_block', array(
'id' => $default_theme . '_' . strtolower($this->randomMachineName(8)),
));
// Install a different theme.
$new_theme = 'bartik';
$this->assertFalse($new_theme == $default_theme, 'The new theme is different from the previous default theme.');
\Drupal::service('theme_handler')->install(array($new_theme));
$this->config('system.theme')
->set('default', $new_theme)
->save();
// Ensure that the new theme has all the blocks as the previous default.
$default_block_names = $this->container->get('entity.query')->get('block')
->condition('theme', $default_theme)
->execute();
$new_blocks = $this->container->get('entity.query')->get('block')
->condition('theme', $new_theme)
->execute();
$this->assertTrue(count($default_block_names) == count($new_blocks), 'The new default theme has the same number of blocks as the previous theme.');
foreach ($default_block_names as $default_block_name) {
// Remove the matching block from the list of blocks in the new theme.
// E.g., if the old theme has block.block.stark_admin,
// unset block.block.bartik_admin.
unset($new_blocks[str_replace($default_theme . '_', $new_theme . '_', $default_block_name)]);
}
$this->assertTrue(empty($new_blocks), 'The new theme has exactly the same blocks as the previous default theme.');
// Install a hidden base theme and ensure blocks are not copied.
$base_theme = 'test_basetheme';
\Drupal::service('theme_handler')->install([$base_theme]);
$new_blocks = $this->container->get('entity.query')->get('block')
->condition('theme', $base_theme)
->execute();
$this->assertTrue(empty($new_blocks), 'Installing a hidden base theme does not copy blocks from the default theme.');
}
}
@@ -1,42 +0,0 @@
<?php
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the block administration page for a non-default theme.
*
* @group block
*/
class NonDefaultBlockAdminTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Test non-default theme admin.
*/
public function testNonDefaultBlockAdmin() {
$admin_user = $this->drupalCreateUser(['administer blocks', 'administer themes']);
$this->drupalLogin($admin_user);
$new_theme = 'bartik';
\Drupal::service('theme_handler')->install([$new_theme]);
$this->drupalGet('admin/structure/block/list/' . $new_theme);
$this->assertText('Bartik(' . t('active tab') . ')', 'Tab for non-default theme found.');
}
}
@@ -1,53 +0,0 @@
<?php
namespace Drupal\block\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for block with conditions missing context.
*
* @see https://www.drupal.org/node/2811519
*
* @group Update
*/
class BlockConditionMissingSchemaUpdateTest extends UpdatePathTestBase {
/**
* This test does not have a failed update but the configuration has missing
* schema so can not do the full post update testing offered by
* UpdatePathTestBase.
*
* @var bool
*
* @see \Drupal\system\Tests\Update\UpdatePathTestBase::runUpdates()
*/
protected $checkFailedUpdates = FALSE;
/**
* {@inheritdoc}
*/
protected static $modules = ['block_test', 'language'];
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/drupal-8.block-test-enabled-missing-schema.php',
];
}
/**
* Tests that block context mapping is updated properly.
*/
public function testUpdateHookN() {
$this->runUpdates();
$this->drupalGet('<front>');
// If the block is fixed by block_post_update_fix_negate_in_conditions()
// then it will be visible.
$this->assertText('Test missing schema on conditions');
}
}
@@ -1,20 +0,0 @@
<?php
namespace Drupal\block\Tests\Update;
/**
* Runs BlockContextMappingUpdateTest with a dump filled with content.
*
* @group Update
*/
class BlockContextMappingUpdateFilledTest extends BlockContextMappingUpdateTest {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
parent::setDatabaseDumpFiles();
$this->databaseDumpFiles[0] = __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz';
}
}
@@ -1,106 +0,0 @@
<?php
namespace Drupal\block\Tests\Update;
use Drupal\block\Entity\Block;
use Drupal\node\Entity\Node;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for block context mapping renames.
*
* @see https://www.drupal.org/node/2354889
*
* @group Update
*/
class BlockContextMappingUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_test', 'language'];
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.language-enabled.php',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-test-enabled.php',
];
}
/**
* Tests that block context mapping is updated properly.
*/
public function testUpdateHookN() {
$this->runUpdates();
$this->assertRaw('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:<ul><li>User login (Visibility: Baloney spam)</li></ul>');
// Disable maintenance mode.
\Drupal::state()->set('system.maintenance_mode', FALSE);
// We finished updating so we can log in the user now.
$this->drupalLogin($this->rootUser);
// The block that we are testing has the following visibility rules:
// - only visible on node pages
// - only visible to authenticated users.
$block_title = 'Test for 2354889';
// Create two nodes, a page and an article.
$page = Node::create([
'type' => 'page',
'title' => 'Page node',
]);
$page->save();
$article = Node::create([
'type' => 'article',
'title' => 'Article node',
]);
$article->save();
// Check that the block appears only on Page nodes for authenticated users.
$this->drupalGet('node/' . $page->id());
$this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.');
$this->drupalGet('node/' . $article->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.');
$this->drupalLogout();
// Check that the block does not appear on any page for anonymous users.
$this->drupalGet('node/' . $page->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.');
$this->drupalGet('node/' . $article->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.');
// Ensure that all the context mappings got updated properly.
$block = Block::load('testfor2354889');
$visibility = $block->get('visibility');
$this->assertEqual('@node.node_route_context:node', $visibility['node_type']['context_mapping']['node']);
$this->assertEqual('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']);
$this->assertEqual('@language.current_language_context:language_interface', $visibility['language']['context_mapping']['language']);
// Check that a block with invalid context is being disabled and that it can
// still be edited afterward.
$disabled_block = Block::load('thirdtestfor2354889');
$this->assertFalse($disabled_block->status(), 'Block with invalid context is disabled');
$this->assertEqual(['thirdtestfor2354889' => ['missing_context_ids' => ['baloney_spam' => ['node_type']], 'status' => TRUE]], \Drupal::keyValue('update_backup')->get('block_update_8001'));
$disabled_block_visibility = $disabled_block->get('visibility');
$this->assertTrue(!isset($disabled_block_visibility['node_type']), 'The problematic visibility condition has been removed.');
$admin_user = $this->drupalCreateUser(['administer blocks']);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/structure/block/manage/thirdtestfor2354889');
$this->assertResponse('200');
}
}
@@ -1,57 +0,0 @@
<?php
namespace Drupal\block\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for removal of disabled region.
*
* @see https://www.drupal.org/node/2513534
*
* @group Update
*/
class BlockRemoveDisabledRegionUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_test', 'language'];
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.update-test-block-disabled-2513534.php',
];
}
/**
* Tests that block context mapping is updated properly.
*/
public function testUpdateHookN() {
$this->runUpdates();
// Disable maintenance mode.
\Drupal::state()->set('system.maintenance_mode', FALSE);
// We finished updating so we can login the user now.
$this->drupalLogin($this->rootUser);
// Verify that a disabled block is in the default region.
$this->drupalGet('admin/structure/block');
$element = $this->xpath("//tr[contains(@data-drupal-selector, :block) and contains(@class, :status)]//select/option[@selected and @value=:region]",
[':block' => 'edit-blocks-pagetitle-1', ':status' => 'block-disabled', ':region' => 'header']);
$this->assertTrue(!empty($element));
// Verify that an enabled block is now disabled and in the default region.
$this->drupalGet('admin/structure/block');
$element = $this->xpath("//tr[contains(@data-drupal-selector, :block) and contains(@class, :status)]//select/option[@selected and @value=:region]",
[':block' => 'edit-blocks-pagetitle-2', ':status' => 'block-disabled', ':region' => 'header']);
$this->assertTrue(!empty($element));
}
}
@@ -1,373 +0,0 @@
<?php
namespace Drupal\block\Tests\Views;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\views\Entity\View;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\Core\Template\Attribute;
/**
* Tests the block display plugin.
*
* @group block
* @see \Drupal\block\Plugin\views\display\Block
*/
class DisplayBlockTest extends ViewTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['node', 'block_test_views', 'test_page_test', 'contextual', 'views_ui'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_block', 'test_view_block2'];
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), ['block_test_views']);
$this->enableViewsTestModule();
}
/**
* Tests default and custom block categories.
*/
public function testBlockCategory() {
$this->drupalLogin($this->drupalCreateUser(['administer views', 'administer blocks']));
// Create a new view in the UI.
$edit = [];
$edit['label'] = $this->randomString();
$edit['id'] = strtolower($this->randomMachineName());
$edit['show[wizard_key]'] = 'standard:views_test_data';
$edit['description'] = $this->randomString();
$edit['block[create]'] = TRUE;
$edit['block[style][row_plugin]'] = 'fields';
$this->drupalPostForm('admin/structure/views/add', $edit, t('Save and edit'));
$pattern = '//tr[.//td[text()=:category] and .//td//a[contains(@href, :href)]]';
// Test that the block was given a default category corresponding to its
// base table.
$arguments = [
':href' => \Drupal::Url('block.admin_add', [
'plugin_id' => 'views_block:' . $edit['id'] . '-block_1',
'theme' => 'classy',
]),
':category' => t('Lists (Views)'),
];
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the category for its base table.');
// Duplicate the block before changing the category.
$this->drupalPostForm('admin/structure/views/view/' . $edit['id'] . '/edit/block_1', [], t('Duplicate @display_title', ['@display_title' => 'Block']));
$this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_2');
// Change the block category to a random string.
$this->drupalGet('admin/structure/views/view/' . $edit['id'] . '/edit/block_1');
$link = $this->xpath('//a[@id="views-block-1-block-category" and normalize-space(text())=:category]', $arguments);
$this->assertTrue(!empty($link));
$this->clickLink(t('Lists (Views)'));
$category = $this->randomString();
$this->drupalPostForm(NULL, ['block_category' => $category], t('Apply'));
// Duplicate the block after changing the category.
$this->drupalPostForm(NULL, [], t('Duplicate @display_title', ['@display_title' => 'Block']));
$this->assertUrl('admin/structure/views/view/' . $edit['id'] . '/edit/block_3');
$this->drupalPostForm(NULL, [], t('Save'));
// Test that the blocks are listed under the correct categories.
$arguments[':category'] = $category;
$this->drupalGet('admin/structure/block');
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the custom category.');
$arguments = [
':href' => \Drupal::Url('block.admin_add', [
'plugin_id' => 'views_block:' . $edit['id'] . '-block_2',
'theme' => 'classy',
]),
':category' => t('Lists (Views)'),
];
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The first duplicated test block remains in the original category.');
$arguments = [
':href' => \Drupal::Url('block.admin_add', [
'plugin_id' => 'views_block:' . $edit['id'] . '-block_3',
'theme' => 'classy',
]),
':category' => $category,
];
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The second duplicated test block appears in the custom category.');
}
/**
* Tests removing a block display.
*/
public function testDeleteBlockDisplay() {
// To test all combinations possible we first place create two instances
// of the block display of the first view.
$block_1 = $this->drupalPlaceBlock('views_block:test_view_block-block_1', ['label' => 'test_view_block-block_1:1']);
$block_2 = $this->drupalPlaceBlock('views_block:test_view_block-block_1', ['label' => 'test_view_block-block_1:2']);
// Then we add one instance of blocks for each of the two displays of the
// second view.
$block_3 = $this->drupalPlaceBlock('views_block:test_view_block2-block_1', ['label' => 'test_view_block2-block_1']);
$block_4 = $this->drupalPlaceBlock('views_block:test_view_block2-block_2', ['label' => 'test_view_block2-block_2']);
$this->drupalGet('test-page');
$this->assertBlockAppears($block_1);
$this->assertBlockAppears($block_2);
$this->assertBlockAppears($block_3);
$this->assertBlockAppears($block_4);
$block_storage = $this->container->get('entity_type.manager')->getStorage('block');
// Remove the block display, so both block entities from the first view
// should both disappear.
$view = Views::getView('test_view_block');
$view->initDisplay();
$view->displayHandlers->remove('block_1');
$view->storage->save();
$this->assertFalse($block_storage->load($block_1->id()), 'The block for this display was removed.');
$this->assertFalse($block_storage->load($block_2->id()), 'The block for this display was removed.');
$this->assertTrue($block_storage->load($block_3->id()), 'A block from another view was unaffected.');
$this->assertTrue($block_storage->load($block_4->id()), 'A block from another view was unaffected.');
$this->drupalGet('test-page');
$this->assertNoBlockAppears($block_1);
$this->assertNoBlockAppears($block_2);
$this->assertBlockAppears($block_3);
$this->assertBlockAppears($block_4);
// Remove the first block display of the second view and ensure the block
// instance of the second block display still exists.
$view = Views::getView('test_view_block2');
$view->initDisplay();
$view->displayHandlers->remove('block_1');
$view->storage->save();
$this->assertFalse($block_storage->load($block_3->id()), 'The block for this display was removed.');
$this->assertTrue($block_storage->load($block_4->id()), 'A block from another display on the same view was unaffected.');
$this->drupalGet('test-page');
$this->assertNoBlockAppears($block_3);
$this->assertBlockAppears($block_4);
}
/**
* Test the block form for a Views block.
*/
public function testViewsBlockForm() {
$this->drupalLogin($this->drupalCreateUser(['administer blocks']));
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme);
$elements = $this->xpath('//input[@name="label"]');
$this->assertTrue(empty($elements), 'The label field is not found for Views blocks.');
// Test that that machine name field is hidden from display and has been
// saved as expected from the default value.
$this->assertNoFieldById('edit-machine-name', 'views_block__test_view_block_1', 'The machine name is hidden on the views block form.');
// Save the block.
$edit = ['region' => 'content'];
$this->drupalPostForm(NULL, $edit, t('Save block'));
$storage = $this->container->get('entity_type.manager')->getStorage('block');
$block = $storage->load('views_block__test_view_block_block_1');
// This will only return a result if our new block has been created with the
// expected machine name.
$this->assertTrue(!empty($block), 'The expected block was loaded.');
for ($i = 2; $i <= 3; $i++) {
// Place the same block again and make sure we have a new ID.
$this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
$block = $storage->load('views_block__test_view_block_block_1_' . $i);
// This will only return a result if our new block has been created with the
// expected machine name.
$this->assertTrue(!empty($block), 'The expected block was loaded.');
}
// Tests the override capability of items per page.
$this->drupalGet('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme);
$edit = ['region' => 'content'];
$edit['settings[override][items_per_page]'] = 10;
$this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
$block = $storage->load('views_block__test_view_block_block_1_4');
$config = $block->getPlugin()->getConfiguration();
$this->assertEqual(10, $config['items_per_page'], "'Items per page' is properly saved.");
$edit['settings[override][items_per_page]'] = 5;
$this->drupalPostForm('admin/structure/block/manage/views_block__test_view_block_block_1_4', $edit, t('Save block'));
$block = $storage->load('views_block__test_view_block_block_1_4');
$config = $block->getPlugin()->getConfiguration();
$this->assertEqual(5, $config['items_per_page'], "'Items per page' is properly saved.");
// Tests the override of the label capability.
$edit = ['region' => 'content'];
$edit['settings[views_label_checkbox]'] = 1;
$edit['settings[views_label]'] = 'Custom title';
$this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
$block = $storage->load('views_block__test_view_block_block_1_5');
$config = $block->getPlugin()->getConfiguration();
$this->assertEqual('Custom title', $config['views_label'], "'Label' is properly saved.");
}
/**
* Tests the actual rendering of the views block.
*/
public function testBlockRendering() {
// Create a block and set a custom title.
$block = $this->drupalPlaceBlock('views_block:test_view_block-block_1', ['label' => 'test_view_block-block_1:1', 'views_label' => 'Custom title']);
$this->drupalGet('');
$result = $this->xpath('//div[contains(@class, "region-sidebar-first")]/div[contains(@class, "block-views")]/h2');
$this->assertEqual((string) $result[0], 'Custom title');
// Don't override the title anymore.
$plugin = $block->getPlugin();
$plugin->setConfigurationValue('views_label', '');
$block->save();
$this->drupalGet('');
$result = $this->xpath('//div[contains(@class, "region-sidebar-first")]/div[contains(@class, "block-views")]/h2');
$this->assertEqual((string) $result[0], 'test_view_block');
// Hide the title.
$block->getPlugin()->setConfigurationValue('label_display', FALSE);
$block->save();
$this->drupalGet('');
$result = $this->xpath('//div[contains(@class, "region-sidebar-first")]/div[contains(@class, "block-views")]/h2');
$this->assertTrue(empty($result), 'The title is not visible.');
$this->assertCacheTags(array_merge($block->getCacheTags(), ['block_view', 'config:block_list', 'config:system.site', 'config:views.view.test_view_block' , 'http_response', 'rendered']));
}
/**
* Tests the various testcases of empty block rendering.
*/
public function testBlockEmptyRendering() {
$url = new Url('test_page_test.test_page');
// Remove all views_test_data entries.
\Drupal::database()->truncate('views_test_data')->execute();
/** @var \Drupal\views\ViewEntityInterface $view */
$view = View::load('test_view_block');
$view->invalidateCaches();
$block = $this->drupalPlaceBlock('views_block:test_view_block-block_1', ['label' => 'test_view_block-block_1:1', 'views_label' => 'Custom title']);
$this->drupalGet('');
$this->assertEqual(1, count($this->xpath('//div[contains(@class, "block-views-blocktest-view-block-block-1")]')));
$display = &$view->getDisplay('block_1');
$display['display_options']['block_hide_empty'] = TRUE;
$view->save();
$this->drupalGet($url);
$this->assertEqual(0, count($this->xpath('//div[contains(@class, "block-views-blocktest-view-block-block-1")]')));
// Ensure that the view cachability metadata is propagated even, for an
// empty block.
$this->assertCacheTags(array_merge($block->getCacheTags(), ['block_view', 'config:block_list', 'config:views.view.test_view_block' , 'http_response', 'rendered']));
$this->assertCacheContexts(['url.query_args:_wrapper_format']);
// Add a header displayed on empty result.
$display = &$view->getDisplay('block_1');
$display['display_options']['defaults']['header'] = FALSE;
$display['display_options']['header']['example'] = [
'field' => 'area_text_custom',
'id' => 'area_text_custom',
'table' => 'views',
'plugin_id' => 'text_custom',
'content' => 'test header',
'empty' => TRUE,
];
$view->save();
$this->drupalGet($url);
$this->assertEqual(1, count($this->xpath('//div[contains(@class, "block-views-blocktest-view-block-block-1")]')));
$this->assertCacheTags(array_merge($block->getCacheTags(), ['block_view', 'config:block_list', 'config:views.view.test_view_block' , 'http_response', 'rendered']));
$this->assertCacheContexts(['url.query_args:_wrapper_format']);
// Hide the header on empty results.
$display = &$view->getDisplay('block_1');
$display['display_options']['defaults']['header'] = FALSE;
$display['display_options']['header']['example'] = [
'field' => 'area_text_custom',
'id' => 'area_text_custom',
'table' => 'views',
'plugin_id' => 'text_custom',
'content' => 'test header',
'empty' => FALSE,
];
$view->save();
$this->drupalGet($url);
$this->assertEqual(0, count($this->xpath('//div[contains(@class, "block-views-blocktest-view-block-block-1")]')));
$this->assertCacheTags(array_merge($block->getCacheTags(), ['block_view', 'config:block_list', 'config:views.view.test_view_block', 'http_response', 'rendered']));
$this->assertCacheContexts(['url.query_args:_wrapper_format']);
// Add an empty text.
$display = &$view->getDisplay('block_1');
$display['display_options']['defaults']['empty'] = FALSE;
$display['display_options']['empty']['example'] = [
'field' => 'area_text_custom',
'id' => 'area_text_custom',
'table' => 'views',
'plugin_id' => 'text_custom',
'content' => 'test empty',
];
$view->save();
$this->drupalGet($url);
$this->assertEqual(1, count($this->xpath('//div[contains(@class, "block-views-blocktest-view-block-block-1")]')));
$this->assertCacheTags(array_merge($block->getCacheTags(), ['block_view', 'config:block_list', 'config:views.view.test_view_block', 'http_response', 'rendered']));
$this->assertCacheContexts(['url.query_args:_wrapper_format']);
}
/**
* Tests the contextual links on a Views block.
*/
public function testBlockContextualLinks() {
$this->drupalLogin($this->drupalCreateUser(['administer views', 'access contextual links', 'administer blocks']));
$block = $this->drupalPlaceBlock('views_block:test_view_block-block_1');
$cached_block = $this->drupalPlaceBlock('views_block:test_view_block-block_1');
$this->drupalGet('test-page');
$id = 'block:block=' . $block->id() . ':langcode=en|entity.view.edit_form:view=test_view_block:location=block&name=test_view_block&display_id=block_1&langcode=en';
$cached_id = 'block:block=' . $cached_block->id() . ':langcode=en|entity.view.edit_form:view=test_view_block:location=block&name=test_view_block&display_id=block_1&langcode=en';
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder()
$this->assertRaw('<div' . new Attribute(['data-contextual-id' => $id]) . '></div>', format_string('Contextual link placeholder with id @id exists.', ['@id' => $id]));
$this->assertRaw('<div' . new Attribute(['data-contextual-id' => $cached_id]) . '></div>', format_string('Contextual link placeholder with id @id exists.', ['@id' => $cached_id]));
// Get server-rendered contextual links.
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks()
$post = ['ids[0]' => $id, 'ids[1]' => $cached_id];
$response = $this->drupalPostWithFormat('contextual/render', 'json', $post, ['query' => ['destination' => 'test-page']]);
$this->assertResponse(200);
$json = Json::decode($response);
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="entityviewedit-form"><a href="' . base_path() . 'admin/structure/views/view/test_view_block/edit/block_1">Edit view</a></li></ul>');
$this->assertIdentical($json[$cached_id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $cached_block->id() . '">Configure block</a></li><li class="entityviewedit-form"><a href="' . base_path() . 'admin/structure/views/view/test_view_block/edit/block_1">Edit view</a></li></ul>');
}
}
@@ -7,8 +7,8 @@ package: Testing
dependencies:
- block
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -6,8 +6,8 @@ regions:
content: Content
help: Help
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -15,8 +15,8 @@ regions_hidden:
- sidebar_first
- sidebar_second
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -8,8 +8,8 @@ dependencies:
- block
- views
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -78,10 +78,10 @@ class BlockFilterTest extends JavascriptTestBase {
/**
* Removes any non-visible elements from the passed array.
*
* @param NodeElement[] $elements
* @param \Behat\Mink\Element\NodeElement[] $elements
* An array of node elements.
*
* @return NodeElement[]
* @return \Behat\Mink\Element\NodeElement[]
*/
protected function filterVisibleElements(array $elements) {
$elements = array_filter($elements, function (NodeElement $element) {
@@ -1,144 +0,0 @@
<?php
namespace Drupal\Tests\block\Unit\Plugin\migrate\source;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests block source plugin.
*
* @coversDefaultClass \Drupal\block\Plugin\migrate\source\Block
* @group block
*/
class BlockTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\block\Plugin\migrate\source\Block';
protected $migrationConfiguration = array(
'id' => 'test',
'source' => array(
'plugin' => 'block',
),
);
/**
* Sample block instance query results from the source.
*/
protected $expectedResults = array(
array(
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
'roles' => [2]
),
array(
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
'roles' => [2]
),
);
/**
* Sample block table.
*/
protected $expectedBlocks = array(
array(
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
),
array(
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
),
);
/**
* Sample block roles table.
*/
protected $expectedBlocksRoles = array(
array(
'module' => 'block',
'delta' => 1,
'rid' => 2,
),
array(
'module' => 'block',
'delta' => 2,
'rid' => 2,
),
array(
'module' => 'block',
'delta' => 2,
'rid' => 100,
),
);
/**
* Sample role table.
*/
protected $expectedRole = array(
array(
'rid' => 2,
'name' => 'authenticated user',
),
);
/**
* Prepopulate database contents.
*/
protected function setUp() {
$this->databaseContents['blocks'] = $this->expectedBlocks;
$this->databaseContents['blocks_roles'] = $this->expectedBlocksRoles;
$this->databaseContents['role'] = $this->expectedRole;
$this->databaseContents['system'] = array(
array(
'filename' => 'modules/system/system.module',
'name' => 'system',
'type' => 'module',
'owner' => '',
'status' => '1',
'throttle' => '0',
'bootstrap' => '0',
'schema_version' => '6055',
'weight' => '0',
'info' => 'a:0:{}',
)
);
parent::setUp();
}
}
@@ -10,8 +10,8 @@ dependencies:
- user
configure: entity.block_content.collection
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -1,57 +0,0 @@
/**
* @file
* Defines Javascript behaviors for the block_content module.
*/
(function ($, Drupal) {
'use strict';
/**
* Sets summaries about revision and translation of block content.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behaviour block content form tabs.
*
* Specifically, it updates summaries to the revision information and the
* translation options.
*/
Drupal.behaviors.blockContentDetailsSummaries = {
attach: function (context) {
var $context = $(context);
$context.find('.block-content-form-revision-information').drupalSetSummary(function (context) {
var $revisionContext = $(context);
var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
// Return 'New revision' if the 'Create new revision' checkbox is checked,
// or if the checkbox doesn't exist, but the revision log does. For users
// without the "Administer content" permission the checkbox won't appear,
// but the revision log will if the content type is set to auto-revision.
if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
return Drupal.t('New revision');
}
return Drupal.t('No revision');
});
$context.find('fieldset.block-content-translation-options').drupalSetSummary(function (context) {
var $translationContext = $(context);
var translate;
var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
if ($checkbox.size()) {
translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
}
else {
$checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
}
return translate;
});
}
};
})(jQuery, Drupal);
@@ -1,105 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the Custom Block entity's cache tags.
*
* @group block_content
*/
class BlockContentCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('block_content');
/**
* {@inheritdoc}
*/
protected function createEntity() {
$block_content_type = BlockContentType::create(array(
'id' => 'basic',
'label' => 'basic',
'revision' => FALSE
));
$block_content_type->save();
block_content_add_body_field($block_content_type->id());
// Create a "Llama" custom block.
$block_content = BlockContent::create(array(
'info' => 'Llama',
'type' => 'basic',
'body' => array(
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
),
));
$block_content->save();
return $block_content;
}
/**
* {@inheritdoc}
*
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
*/
protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
return [];
}
/**
* {@inheritdoc}
*
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return ['config:filter.format.plain_text'];
}
/**
* Tests that the block is cached with the correct contexts and tags.
*/
public function testBlock() {
$block = $this->drupalPlaceBlock('block_content:' . $this->entity->uuid());
$build = $this->container->get('entity.manager')->getViewBuilder('block')->view($block, 'block');
// Render the block.
// @todo The request stack manipulation won't be necessary once
// https://www.drupal.org/node/2367555 is fixed and the
// corresponding $request->isMethodSafe() checks are removed from
// Drupal\Core\Render\Renderer.
$request_stack = $this->container->get('request_stack');
$request_stack->push(new Request());
$this->container->get('renderer')->renderRoot($build);
$request_stack->pop();
// Expected keys, contexts, and tags for the block.
// @see \Drupal\block\BlockViewBuilder::viewMultiple()
$expected_block_cache_keys = ['entity_view', 'block', $block->id()];
$expected_block_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$expected_block_cache_tags = Cache::mergeTags(['block_view', 'rendered'], $block->getCacheTags());
$expected_block_cache_tags = Cache::mergeTags($expected_block_cache_tags, $block->getPlugin()->getCacheTags());
// Expected contexts and tags for the BlockContent entity.
// @see \Drupal\Core\Entity\EntityViewBuilder::getBuildDefaults().
$expected_entity_cache_contexts = ['theme'];
$expected_entity_cache_tags = Cache::mergeTags(['block_content_view'], $this->entity->getCacheTags());
$expected_entity_cache_tags = Cache::mergeTags($expected_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
// Verify that what was render cached matches the above expectations.
$cid = $this->createCacheId($expected_block_cache_keys, $expected_block_cache_contexts);
$redirected_cid = $this->createCacheId($expected_block_cache_keys, Cache::mergeContexts($expected_block_cache_contexts, $expected_entity_cache_contexts));
$this->verifyRenderCache($cid, Cache::mergeTags($expected_block_cache_tags, $expected_entity_cache_tags), ($cid !== $redirected_cid) ? $redirected_cid : NULL);
}
}
@@ -1,302 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Database;
/**
* Create a block and test saving it.
*
* @group block_content
*/
class BlockContentCreationTest extends BlockContentTestBase {
/**
* Modules to enable.
*
* Enable dummy module that implements hook_block_insert() for exceptions and
* field_ui to edit display settings.
*
* @var array
*/
public static $modules = array('block_content_test', 'dblog', 'field_ui');
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = array(
'administer blocks',
'administer block_content display'
);
/**
* Sets the test up.
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Creates a "Basic page" block and verifies its consistency in the database.
*/
public function testBlockContentCreation() {
$this->drupalLogin($this->adminUser);
// Create a block.
$edit = array();
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Check that the Basic block has been created.
$this->assertRaw(format_string('@block %name has been created.', array(
'@block' => 'basic',
'%name' => $edit['info[0][value]']
)), 'Basic block created.');
// Check that the view mode setting is hidden because only one exists.
$this->assertNoFieldByXPath('//select[@name="settings[view_mode]"]', NULL, 'View mode setting hidden because only one exists');
// Check that the block exists in the database.
$blocks = entity_load_multiple_by_properties('block_content', array('info' => $edit['info[0][value]']));
$block = reset($blocks);
$this->assertTrue($block, 'Custom Block found in database.');
// Check that attempting to create another block with the same value for
// 'info' returns an error.
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Check that the Basic block has been created.
$this->assertRaw(format_string('A custom block with block description %value already exists.', array(
'%value' => $edit['info[0][value]']
)));
$this->assertResponse(200);
}
/**
* Creates a "Basic page" block with multiple view modes.
*/
public function testBlockContentCreationMultipleViewModes() {
// Add a new view mode and verify if it is selected as expected.
$this->drupalLogin($this->drupalCreateUser(array('administer display modes')));
$this->drupalGet('admin/structure/display-modes/view/add/block_content');
$edit = array(
'id' => 'test_view_mode',
'label' => 'Test View Mode',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('Saved the %label view mode.', array('%label' => $edit['label'])));
$this->drupalLogin($this->adminUser);
// Create a block.
$edit = array();
$edit['info[0][value]'] = 'Test Block';
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Check that the Basic block has been created.
$this->assertRaw(format_string('@block %name has been created.', array(
'@block' => 'basic',
'%name' => $edit['info[0][value]']
)), 'Basic block created.');
// Save our block permanently
$this->drupalPostForm(NULL, NULL, t('Save block'));
// Set test_view_mode as a custom display to be available on the list.
$this->drupalGet('admin/structure/block/block-content');
$this->drupalGet('admin/structure/block/block-content/types');
$this->clickLink(t('Manage display'));
$this->drupalGet('admin/structure/block/block-content/manage/basic/display');
$custom_view_mode = array(
'display_modes_custom[test_view_mode]' => 1,
);
$this->drupalPostForm(NULL, $custom_view_mode, t('Save'));
// Go to the configure page and change the view mode.
$this->drupalGet('admin/structure/block/manage/testblock');
// Test the available view mode options.
$this->assertOption('edit-settings-view-mode', 'default', 'The default view mode is available.');
$this->assertOption('edit-settings-view-mode', 'test_view_mode', 'The test view mode is available.');
$view_mode['settings[view_mode]'] = 'test_view_mode';
$this->drupalPostForm(NULL, $view_mode, t('Save block'));
// Check that the view mode setting is shown because more than one exists.
$this->drupalGet('admin/structure/block/manage/testblock');
$this->assertFieldByXPath('//select[@name="settings[view_mode]"]', NULL, 'View mode setting shown because multiple exist');
// Change the view mode.
$view_mode['settings[view_mode]'] = 'test_view_mode';
$this->drupalPostForm(NULL, $view_mode, t('Save block'));
// Go to the configure page and verify the view mode has changed.
$this->drupalGet('admin/structure/block/manage/testblock');
$this->assertFieldByXPath('//select[@name="settings[view_mode]"]/option[@selected="selected"]/@value', 'test_view_mode', 'View mode changed to Test View Mode');
// Check that the block exists in the database.
$blocks = entity_load_multiple_by_properties('block_content', array('info' => $edit['info[0][value]']));
$block = reset($blocks);
$this->assertTrue($block, 'Custom Block found in database.');
// Check that attempting to create another block with the same value for
// 'info' returns an error.
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Check that the Basic block has been created.
$this->assertRaw(format_string('A custom block with block description %value already exists.', array(
'%value' => $edit['info[0][value]']
)));
$this->assertResponse(200);
}
/**
* Create a default custom block.
*
* Creates a custom block from defaults and ensures that the 'basic block'
* type is being used.
*/
public function testDefaultBlockContentCreation() {
$edit = array();
$edit['info[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
// Don't pass the custom block type in the url so the default is forced.
$this->drupalPostForm('block/add', $edit, t('Save'));
// Check that the block has been created and that it is a basic block.
$this->assertRaw(format_string('@block %name has been created.', array(
'@block' => 'basic',
'%name' => $edit['info[0][value]'],
)), 'Basic block created.');
// Check that the block exists in the database.
$blocks = entity_load_multiple_by_properties('block_content', array('info' => $edit['info[0][value]']));
$block = reset($blocks);
$this->assertTrue($block, 'Default Custom Block found in database.');
}
/**
* Verifies that a transaction rolls back the failed creation.
*/
public function testFailedBlockCreation() {
// Create a block.
try {
$this->createBlockContent('fail_creation');
$this->fail('Expected exception has not been thrown.');
}
catch (\Exception $e) {
$this->pass('Expected exception has been thrown.');
}
if (Database::getConnection()->supportsTransactions()) {
// Check that the block does not exist in the database.
$id = db_select('block_content_field_data', 'b')
->fields('b', array('id'))
->condition('info', 'fail_creation')
->execute()
->fetchField();
$this->assertFalse($id, 'Transactions supported, and block not found in database.');
}
else {
// Check that the block exists in the database.
$id = db_select('block_content_field_data', 'b')
->fields('b', array('id'))
->condition('info', 'fail_creation')
->execute()
->fetchField();
$this->assertTrue($id, 'Transactions not supported, and block found in database.');
// Check that the failed rollback was logged.
$records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll();
$this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.');
}
}
/**
* Test deleting a block.
*/
public function testBlockDelete() {
// Create a block.
$edit = array();
$edit['info[0][value]'] = $this->randomMachineName(8);
$body = $this->randomMachineName(16);
$edit['body[0][value]'] = $body;
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Place the block.
$instance = array(
'id' => Unicode::strtolower($edit['info[0][value]']),
'settings[label]' => $edit['info[0][value]'],
'region' => 'sidebar_first',
);
$block = BlockContent::load(1);
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
$this->drupalPostForm($url, $instance, t('Save block'));
$block = BlockContent::load(1);
// Test getInstances method.
$this->assertEqual(1, count($block->getInstances()));
// Navigate to home page.
$this->drupalGet('');
$this->assertText($body);
// Delete the block.
$this->drupalGet('block/1/delete');
$this->assertText(\Drupal::translation()->formatPlural(1, 'This will also remove 1 placed block instance.', 'This will also remove @count placed block instance.'));
$this->drupalPostForm(NULL, array(), 'Delete');
$this->assertRaw(t('The custom block %name has been deleted.', array('%name' => $edit['info[0][value]'])));
// Create another block and force the plugin cache to flush.
$edit2 = array();
$edit2['info[0][value]'] = $this->randomMachineName(8);
$body2 = $this->randomMachineName(16);
$edit2['body[0][value]'] = $body2;
$this->drupalPostForm('block/add/basic', $edit2, t('Save'));
$this->assertNoRaw('Error message');
// Create another block with no instances, and test we don't get a
// confirmation message about deleting instances.
$edit3 = array();
$edit3['info[0][value]'] = $this->randomMachineName(8);
$body = $this->randomMachineName(16);
$edit3['body[0][value]'] = $body;
$this->drupalPostForm('block/add/basic', $edit3, t('Save'));
// Show the delete confirm form.
$this->drupalGet('block/3/delete');
$this->assertNoText('This will also remove');
}
/**
* Test that placed content blocks create a dependency in the block placement.
*/
public function testConfigDependencies() {
$block = $this->createBlockContent();
// Place the block.
$block_placement_id = Unicode::strtolower($block->label());
$instance = array(
'id' => $block_placement_id,
'settings[label]' => $block->label(),
'region' => 'sidebar_first',
);
$block = BlockContent::load(1);
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
$this->drupalPostForm($url, $instance, t('Save block'));
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependentsAsEntities('content', array($block->getConfigDependencyName()));
$block_placement = reset($dependencies);
$this->assertEqual($block_placement_id, $block_placement->id(), "The block placement config entity has a dependency on the block content entity.");
}
}
@@ -1,109 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
/**
* Tests the listing of custom blocks.
*
* Tests the fallback block content list when Views is disabled.
*
* @group block_content
* @see \Drupal\block\BlockContentListBuilder
* @see \Drupal\block_content\Tests\BlockContentListViewsTest
*/
class BlockContentListTest extends BlockContentTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block', 'block_content', 'config_translation');
/**
* Tests the custom block listing page.
*/
public function testListing() {
$this->drupalLogin($this->drupalCreateUser(array('administer blocks', 'translate configuration')));
$this->drupalGet('admin/structure/block/block-content');
// Test for the page title.
$this->assertTitle(t('Custom block library') . ' | Drupal');
// Test for the table.
$element = $this->xpath('//div[@class="layout-content"]//table');
$this->assertTrue($element, 'Configuration entity list table found.');
// Test the table header.
$elements = $this->xpath('//div[@class="layout-content"]//table/thead/tr/th');
$this->assertEqual(count($elements), 2, 'Correct number of table header cells found.');
// Test the contents of each th cell.
$expected_items = array(t('Block description'), t('Operations'));
foreach ($elements as $key => $element) {
$this->assertEqual($element[0], $expected_items[$key]);
}
$label = 'Antelope';
$new_label = 'Albatross';
// Add a new entity using the operations link.
$link_text = t('Add custom block');
$this->assertLink($link_text);
$this->clickLink($link_text);
$this->assertResponse(200);
$edit = array();
$edit['info[0][value]'] = $label;
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertFieldByXpath('//td', $label, 'Label found for added block.');
// Check the number of table row cells.
$elements = $this->xpath('//div[@class="layout-content"]//table/tbody/tr[@class="odd"]/td');
$this->assertEqual(count($elements), 2, 'Correct number of table row cells found.');
// Check the contents of each row cell. The first cell contains the label,
// the second contains the machine name, and the third contains the
// operations list.
$this->assertIdentical((string) $elements[0], $label);
// Edit the entity using the operations link.
$blocks = $this->container
->get('entity.manager')
->getStorage('block_content')
->loadByProperties(array('info' => $label));
$block = reset($blocks);
if (!empty($block)) {
$this->assertLinkByHref('block/' . $block->id());
$this->clickLink(t('Edit'));
$this->assertResponse(200);
$this->assertTitle(strip_tags(t('Edit custom block %label', array('%label' => $label)) . ' | Drupal'));
$edit = array('info[0][value]' => $new_label);
$this->drupalPostForm(NULL, $edit, t('Save'));
}
else {
$this->fail('Did not find Albatross block in the database.');
}
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertFieldByXpath('//td', $new_label, 'Label found for updated custom block.');
// Delete the added entity using the operations link.
$this->assertLinkByHref('block/' . $block->id() . '/delete');
$delete_text = t('Delete');
$this->clickLink($delete_text);
$this->assertResponse(200);
$this->assertTitle(strip_tags(t('Are you sure you want to delete the custom block %label?', array('%label' => $new_label)) . ' | Drupal'));
$this->drupalPostForm(NULL, array(), $delete_text);
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertNoFieldByXpath('//td', $new_label, 'No label found for deleted custom block.');
// Confirm that the empty text is displayed.
$this->assertText(t('There is no Custom block yet.'));
}
}
@@ -1,117 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
/**
* Tests the Views-powered listing of custom blocks.
*
* @group block_content
* @see \Drupal\block\BlockContentListBuilder
* @see \Drupal\block_content\Tests\BlockContentListTest
*/
class BlockContentListViewsTest extends BlockContentTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'block_content', 'config_translation', 'views'];
/**
* Tests the custom block listing page.
*/
public function testListing() {
$this->drupalLogin($this->drupalCreateUser(array('administer blocks', 'translate configuration')));
$this->drupalGet('admin/structure/block/block-content');
// Test for the page title.
$this->assertTitle(t('Custom block library') . ' | Drupal');
// Test for the exposed filters.
$this->assertFieldByName('info');
$this->assertFieldByName('type');
// Test for the table.
$element = $this->xpath('//div[@class="layout-content"]//table');
$this->assertTrue($element, 'Views table found.');
// Test the table header.
$elements = $this->xpath('//div[@class="layout-content"]//table/thead/tr/th');
$this->assertEqual(count($elements), 4, 'Correct number of table header cells found.');
// Test the contents of each th cell.
$expected_items = ['Block description', 'Block type', 'Updated', 'Operations'];
foreach ($elements as $key => $element) {
if ($element->xpath('a')) {
$this->assertIdentical(trim((string) $element->xpath('a')[0]), $expected_items[$key]);
}
else {
$this->assertIdentical(trim((string) $element[0]), $expected_items[$key]);
}
}
$label = 'Antelope';
$new_label = 'Albatross';
// Add a new entity using the operations link.
$link_text = t('Add custom block');
$this->assertLink($link_text);
$this->clickLink($link_text);
$this->assertResponse(200);
$edit = array();
$edit['info[0][value]'] = $label;
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertFieldByXpath('//td/a', $label, 'Label found for added block.');
// Check the number of table row cells.
$elements = $this->xpath('//div[@class="layout-content"]//table/tbody/tr/td');
$this->assertEqual(count($elements), 4, 'Correct number of table row cells found.');
// Check the contents of each row cell. The first cell contains the label,
// the second contains the machine name, and the third contains the
// operations list.
$this->assertIdentical((string) $elements[0]->xpath('a')[0], $label);
// Edit the entity using the operations link.
$blocks = $this->container
->get('entity.manager')
->getStorage('block_content')
->loadByProperties(array('info' => $label));
$block = reset($blocks);
if (!empty($block)) {
$this->assertLinkByHref('block/' . $block->id());
$this->clickLink(t('Edit'));
$this->assertResponse(200);
$this->assertTitle(strip_tags(t('Edit custom block %label', array('%label' => $label)) . ' | Drupal'));
$edit = array('info[0][value]' => $new_label);
$this->drupalPostForm(NULL, $edit, t('Save'));
}
else {
$this->fail('Did not find Albatross block in the database.');
}
// Confirm that once the user returns to the listing, the text of the label
// (versus elsewhere on the page).
$this->assertFieldByXpath('//td/a', $new_label, 'Label found for updated custom block.');
// Delete the added entity using the operations link.
$this->assertLinkByHref('block/' . $block->id() . '/delete');
$delete_text = t('Delete');
$this->clickLink($delete_text);
$this->assertResponse(200);
$this->assertTitle(strip_tags(t('Are you sure you want to delete the custom block %label?', array('%label' => $new_label)) . ' | Drupal'));
$this->drupalPostForm(NULL, array(), $delete_text);
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertNoFieldByXpath('//td', $new_label, 'No label found for deleted custom block.');
// Confirm that the empty text is displayed.
$this->assertText('There are no custom blocks available.');
$this->assertLink('custom block');
}
}
@@ -1,35 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
/**
* Create a block and test block access by attempting to view the block.
*
* @group block_content
*/
class BlockContentPageViewTest extends BlockContentTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block_content_test');
/**
* Checks block edit and fallback functionality.
*/
public function testPageEdit() {
$this->drupalLogin($this->adminUser);
$block = $this->createBlockContent();
// Attempt to view the block.
$this->drupalGet('block-content/' . $block->id());
// Assert response was '200' and not '403 Access denied'.
$this->assertResponse('200', 'User was able the view the block');
$this->drupalGet('<front>');
$this->assertRaw(t('This block is broken or missing. You may be missing content or you might need to enable the original module.'));
}
}
@@ -1,92 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
/**
* Create a block with revisions.
*
* @group block_content
*/
class BlockContentRevisionsTest extends BlockContentTestBase {
/**
* Stores blocks created during the test.
* @var array
*/
protected $blocks;
/**
* Stores log messages used during the test.
* @var array
*/
protected $revisionLogs;
/**
* Sets the test up.
*/
protected function setUp() {
parent::setUp();
// Create initial block.
$block = $this->createBlockContent('initial');
$blocks = array();
$logs = array();
// Get original block.
$blocks[] = $block->getRevisionId();
$logs[] = '';
// Create three revisions.
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$block->setNewRevision(TRUE);
$block->setRevisionLog($this->randomMachineName(32));
$logs[] = $block->getRevisionLog();
$block->save();
$blocks[] = $block->getRevisionId();
}
$this->blocks = $blocks;
$this->revisionLogs = $logs;
}
/**
* Checks block revision related operations.
*/
public function testRevisions() {
$blocks = $this->blocks;
$logs = $this->revisionLogs;
foreach ($blocks as $delta => $revision_id) {
// Confirm the correct revision text appears.
$loaded = entity_revision_load('block_content', $revision_id);
// Verify revision log is the same.
$this->assertEqual($loaded->getRevisionLog(), $logs[$delta], format_string('Correct log message found for revision @revision', array(
'@revision' => $loaded->getRevisionId(),
)));
}
// Confirm that this is the default revision.
$this->assertTrue($loaded->isDefaultRevision(), 'Third block revision is the default one.');
// Make a new revision and set it to not be default.
// This will create a new revision that is not "front facing".
// Save this as a non-default revision.
$loaded->setNewRevision();
$loaded->isDefaultRevision(FALSE);
$loaded->body = $this->randomMachineName(8);
$loaded->save();
$this->drupalGet('block/' . $loaded->id());
$this->assertNoText($loaded->body->value, 'Revision body text is not present on default version of block.');
// Verify that the non-default revision id is greater than the default
// revision id.
$default_revision = BlockContent::load($loaded->id());
$this->assertTrue($loaded->getRevisionId() > $default_revision->getRevisionId(), 'Revision id is greater than default revision id.');
}
}
@@ -1,102 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
/**
* Tests $block_content->save() for saving content.
*
* @group block_content
*/
class BlockContentSaveTest extends BlockContentTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block_content_test');
/**
* Sets the test up.
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Checks whether custom block IDs are saved properly during an import.
*/
public function testImport() {
// Custom block ID must be a number that is not in the database.
$max_id = db_query('SELECT MAX(id) FROM {block_content}')->fetchField();
$test_id = $max_id + mt_rand(1000, 1000000);
$info = $this->randomMachineName(8);
$block_array = array(
'info' => $info,
'body' => array('value' => $this->randomMachineName(32)),
'type' => 'basic',
'id' => $test_id
);
$block = BlockContent::create($block_array);
$block->enforceIsNew(TRUE);
$block->save();
// Verify that block_submit did not wipe the provided id.
$this->assertEqual($block->id(), $test_id, 'Block imported using provide id');
// Test the import saved.
$block_by_id = BlockContent::load($test_id);
$this->assertTrue($block_by_id, 'Custom block load by block ID.');
$this->assertIdentical($block_by_id->body->value, $block_array['body']['value']);
}
/**
* Tests determining changes in hook_block_presave().
*
* Verifies the static block load cache is cleared upon save.
*/
public function testDeterminingChanges() {
// Initial creation.
$block = $this->createBlockContent('test_changes');
$this->assertEqual($block->getChangedTime(), REQUEST_TIME, 'Creating a block sets default "changed" timestamp.');
// Update the block without applying changes.
$block->save();
$this->assertEqual($block->label(), 'test_changes', 'No changes have been determined.');
// Apply changes.
$block->setInfo('updated');
$block->save();
// The hook implementations block_content_test_block_content_presave() and
// block_content_test_block_content_update() determine changes and change
// the title as well as programmatically set the 'changed' timestamp.
$this->assertEqual($block->label(), 'updated_presave_update', 'Changes have been determined.');
$this->assertEqual($block->getChangedTime(), 979534800, 'Saving a custom block uses "changed" timestamp set in presave hook.');
// Test the static block load cache to be cleared.
$block = BlockContent::load($block->id());
$this->assertEqual($block->label(), 'updated_presave', 'Static cache has been cleared.');
}
/**
* Tests saving a block on block insert.
*
* This test ensures that a block has been fully saved when
* hook_block_content_insert() is invoked, so that the block can be saved again
* in a hook implementation without errors.
*
* @see block_test_block_insert()
*/
public function testBlockContentSaveOnInsert() {
// block_content_test_block_content_insert() triggers a save on insert if the
// title equals 'new'.
$block = $this->createBlockContent('new');
$this->assertEqual($block->label(), 'BlockContent ' . $block->id(), 'Custom block saved on block insert.');
}
}
@@ -16,6 +16,8 @@ abstract class BlockContentTestBase extends WebTestBase {
/**
* Profile to use.
*
* @var string
*/
protected $profile = 'testing';
@@ -1,202 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Utility\Unicode;
use Drupal\content_translation\Tests\ContentTranslationUITestBase;
/**
* Tests the block content translation UI.
*
* @group block_content
*/
class BlockContentTranslationUITest extends ContentTranslationUITestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'language',
'content_translation',
'block',
'field_ui',
'block_content'
);
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'session',
'theme',
'url.path',
'url.query_args',
'user.permissions',
'user.roles:authenticated',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityTypeId = 'block_content';
$this->bundle = 'basic';
$this->testLanguageSelector = FALSE;
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* {@inheritdoc}
*/
protected function setupBundle() {
// Create the basic bundle since it is provided by standard.
$bundle = BlockContentType::create(array(
'id' => $this->bundle,
'label' => $this->bundle,
'revision' => FALSE
));
$bundle->save();
}
/**
* {@inheritdoc}
*/
public function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array(
'translate any entity',
'access administration pages',
'administer blocks',
'administer block_content fields'
));
}
/**
* Creates a custom block.
*
* @param bool|string $title
* (optional) Title of block. When no value is given uses a random name.
* Defaults to FALSE.
* @param bool|string $bundle
* (optional) Bundle name. When no value is given, defaults to
* $this->bundle. Defaults to FALSE.
*
* @return \Drupal\block_content\Entity\BlockContent
* Created custom block.
*/
protected function createBlockContent($title = FALSE, $bundle = FALSE) {
$title = $title ?: $this->randomMachineName();
$bundle = $bundle ?: $this->bundle;
$block_content = BlockContent::create(array(
'info' => $title,
'type' => $bundle,
'langcode' => 'en'
));
$block_content->save();
return $block_content;
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return array('info' => Unicode::strtolower($this->randomMachineName())) + parent::getNewEntityValues($langcode);
}
/**
* Returns an edit array containing the values to be posted.
*/
protected function getEditValues($values, $langcode, $new = FALSE) {
$edit = parent::getEditValues($values, $langcode, $new);
foreach ($edit as $property => $value) {
if ($property == 'info') {
$edit['info[0][value]'] = $value;
unset($edit[$property]);
}
}
return $edit;
}
/**
* {@inheritdoc}
*/
protected function doTestBasicTranslation() {
parent::doTestBasicTranslation();
// Ensure that a block translation can be created using the same description
// as in the original language.
$default_langcode = $this->langcodes[0];
$values = $this->getNewEntityValues($default_langcode);
$storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->create(array('type' => 'basic') + $values);
$entity->save();
$entity->addTranslation('it', $values);
try {
$message = 'Blocks can have translations with the same "info" value.';
$entity->save();
$this->pass($message);
}
catch (\Exception $e) {
$this->fail($message);
}
// Check that the translate operation link is shown.
$this->drupalGet('admin/structure/block/block-content');
$this->assertLinkByHref('block/' . $entity->id() . '/translations');
}
/**
* Test that no metadata is stored for a disabled bundle.
*/
public function testDisabledBundle() {
// Create a bundle that does not have translation enabled.
$disabled_bundle = $this->randomMachineName();
$bundle = BlockContentType::create(array(
'id' => $disabled_bundle,
'label' => $disabled_bundle,
'revision' => FALSE
));
$bundle->save();
// Create a block content for each bundle.
$enabled_block_content = $this->createBlockContent();
$disabled_block_content = $this->createBlockContent(FALSE, $bundle->id());
// Make sure that only a single row was inserted into the block table.
$rows = db_query('SELECT * FROM {block_content_field_data} WHERE id = :id', array(':id' => $enabled_block_content->id()))->fetchAll();
$this->assertEqual(1, count($rows));
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = array('language' => $languages[$langcode]);
$url = $entity->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('<em>Edit @type</em> @title [%language translation]', array(
'@type' => $entity->bundle(),
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
));
$this->assertRaw($title);
}
}
}
}
@@ -1,250 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\system\Tests\Menu\AssertBreadcrumbTrait;
/**
* Ensures that custom block type functions work correctly.
*
* @group block_content
*/
class BlockContentTypeTest extends BlockContentTestBase {
use AssertBreadcrumbTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field_ui'];
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer blocks',
'administer block_content fields'
];
/**
* Whether or not to create an initial block type.
*
* @var bool
*/
protected $autoCreateBasicBlockType = FALSE;
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests creating a block type programmatically and via a form.
*/
public function testBlockContentTypeCreation() {
// Log in a test user.
$this->drupalLogin($this->adminUser);
// Test the page with no block-types.
$this->drupalGet('block/add');
$this->assertResponse(200);
$this->assertText('You have not created any block types yet');
$this->clickLink('block type creation page');
// Create a block type via the user interface.
$edit = [
'id' => 'foo',
'label' => 'title for foo',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$block_type = BlockContentType::load('foo');
$this->assertTrue($block_type, 'The new block type has been created.');
$field_definitions = \Drupal::entityManager()->getFieldDefinitions('block_content', 'foo');
$this->assertTrue(isset($field_definitions['body']), 'Body field created when using the UI to create block content types.');
// Check that the block type was created in site default language.
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->assertEqual($block_type->language()->getId(), $default_langcode);
// Create block types programmatically.
$this->createBlockContentType('basic', TRUE);
$field_definitions = \Drupal::entityManager()->getFieldDefinitions('block_content', 'basic');
$this->assertTrue(isset($field_definitions['body']), "Body field for 'basic' block type created when using the testing API to create block content types.");
$this->createBlockContentType('other');
$field_definitions = \Drupal::entityManager()->getFieldDefinitions('block_content', 'other');
$this->assertFalse(isset($field_definitions['body']), "Body field for 'other' block type not created when using the testing API to create block content types.");
$block_type = BlockContentType::load('other');
$this->assertTrue($block_type, 'The new block type has been created.');
$this->drupalGet('block/add/' . $block_type->id());
$this->assertResponse(200);
}
/**
* Tests editing a block type using the UI.
*/
public function testBlockContentTypeEditing() {
$this->drupalPlaceBlock('system_breadcrumb_block');
// Now create an initial block-type.
$this->createBlockContentType('basic', TRUE);
$this->drupalLogin($this->adminUser);
// We need two block types to prevent /block/add redirecting.
$this->createBlockContentType('other');
$field_definitions = \Drupal::entityManager()->getFieldDefinitions('block_content', 'other');
$this->assertFalse(isset($field_definitions['body']), 'Body field was not created when using the API to create block content types.');
// Verify that title and body fields are displayed.
$this->drupalGet('block/add/basic');
$this->assertRaw('Block description', 'Block info field was found.');
$this->assertRaw('Body', 'Body field was found.');
// Change the block type name.
$edit = [
'label' => 'Bar',
];
$this->drupalGet('admin/structure/block/block-content/manage/basic');
$this->assertTitle(format_string('Edit @type custom block type | Drupal', ['@type' => 'basic']));
$this->drupalPostForm(NULL, $edit, t('Save'));
$front_page_path = Url::fromRoute('<front>')->toString();
$this->assertBreadcrumb('admin/structure/block/block-content/manage/basic/fields', [
$front_page_path => 'Home',
'admin/structure/block' => 'Block layout',
'admin/structure/block/block-content' => 'Custom block library',
'admin/structure/block/block-content/manage/basic' => 'Bar',
]);
\Drupal::entityManager()->clearCachedFieldDefinitions();
$this->drupalGet('block/add');
$this->assertRaw('Bar', 'New name was displayed.');
$this->clickLink('Bar');
$this->assertUrl(\Drupal::url('block_content.add_form', ['block_content_type' => 'basic'], ['absolute' => TRUE]), [], 'Original machine name was used in URL.');
// Remove the body field.
$this->drupalPostForm('admin/structure/block/block-content/manage/basic/fields/block_content.basic.body/delete', [], t('Delete'));
// Resave the settings for this type.
$this->drupalPostForm('admin/structure/block/block-content/manage/basic', [], t('Save'));
// Check that the body field doesn't exist.
$this->drupalGet('block/add/basic');
$this->assertNoRaw('Body', 'Body field was not found.');
}
/**
* Tests deleting a block type that still has content.
*/
public function testBlockContentTypeDeletion() {
// Now create an initial block-type.
$this->createBlockContentType('basic', TRUE);
// Create a block type programmatically.
$type = $this->createBlockContentType('foo');
$this->drupalLogin($this->adminUser);
// Add a new block of this type.
$block = $this->createBlockContent(FALSE, 'foo');
// Attempt to delete the block type, which should not be allowed.
$this->drupalGet('admin/structure/block/block-content/manage/' . $type->id() . '/delete');
$this->assertRaw(
t('%label is used by 1 custom block on your site. You can not remove this block type until you have removed all of the %label blocks.', ['%label' => $type->label()]),
'The block type will not be deleted until all blocks of that type are removed.'
);
$this->assertNoText(t('This action cannot be undone.'), 'The block type deletion confirmation form is not available.');
// Delete the block.
$block->delete();
// Attempt to delete the block type, which should now be allowed.
$this->drupalGet('admin/structure/block/block-content/manage/' . $type->id() . '/delete');
$this->assertRaw(
t('Are you sure you want to delete the custom block type %type?', ['%type' => $type->id()]),
'The block type is available for deletion.'
);
$this->assertText(t('This action cannot be undone.'), 'The custom block type deletion confirmation form is available.');
}
/**
* Tests that redirects work as expected when multiple block types exist.
*/
public function testsBlockContentAddTypes() {
// Now create an initial block-type.
$this->createBlockContentType('basic', TRUE);
$this->drupalLogin($this->adminUser);
// Create two block types programmatically.
$type = $this->createBlockContentType('foo');
$type = $this->createBlockContentType('bar');
// Get the custom block storage.
$storage = $this->container
->get('entity.manager')
->getStorage('block_content');
// Install all themes.
\Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
$theme_settings = $this->config('system.theme');
foreach (['bartik', 'seven', 'stark'] as $default_theme) {
// Change the default theme.
$theme_settings->set('default', $default_theme)->save();
\Drupal::service('router.builder')->rebuild();
// For each installed theme, go to its block page and test the redirects.
foreach (['bartik', 'seven', 'stark'] as $theme) {
// Test that adding a block from the 'place blocks' form sends you to the
// block configure form.
$path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
$this->drupalGet($path);
$this->clickLinkPartialName('Place block');
$this->clickLink(t('Add custom block'));
// The seven theme has markup inside the link, we cannot use clickLink().
if ($default_theme == 'seven') {
$options = $theme != $default_theme ? ['query' => ['theme' => $theme]] : [];
$this->assertLinkByHref(\Drupal::url('block_content.add_form', ['block_content_type' => 'foo'], $options));
$this->drupalGet('block/add/foo', $options);
}
else {
$this->clickLink('foo');
}
// Create a new block.
$edit = ['info[0][value]' => $this->randomMachineName(8)];
$this->drupalPostForm(NULL, $edit, t('Save'));
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
if (!empty($blocks)) {
$block = reset($blocks);
$this->assertUrl(\Drupal::url('block.admin_add', ['plugin_id' => 'block_content:' . $block->uuid(), 'theme' => $theme], ['absolute' => TRUE]));
$this->drupalPostForm(NULL, ['region' => 'content'], t('Save block'));
$this->assertUrl(\Drupal::url('block.admin_display_theme', ['theme' => $theme], ['absolute' => TRUE, 'query' => ['block-placement' => Html::getClass($edit['info[0][value]'])]]));
}
else {
$this->fail('Could not load created block.');
}
}
}
// Test that adding a block from the 'custom blocks list' doesn't send you
// to the block configure form.
$this->drupalGet('admin/structure/block/block-content');
$this->clickLink(t('Add custom block'));
$this->clickLink('foo');
$edit = ['info[0][value]' => $this->randomMachineName(8)];
$this->drupalPostForm(NULL, $edit, t('Save'));
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
if (!empty($blocks)) {
$this->assertUrl(\Drupal::url('entity.block_content.collection', [], ['absolute' => TRUE]));
}
else {
$this->fail('Could not load created block.');
}
}
}
@@ -1,46 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests update functions for the Block Content module.
*
* @group Update
*/
class BlockContentUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
}
/**
* Tests the revision metadata fields and revision data table additions.
*/
public function testSimpleUpdates() {
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$entity_type = $entity_definition_update_manager->getEntityType('block_content');
$this->assertNull($entity_type->getRevisionDataTable());
$this->runUpdates();
$post_revision_created = $entity_definition_update_manager->getFieldStorageDefinition('revision_created', 'block_content');
$post_revision_user = $entity_definition_update_manager->getFieldStorageDefinition('revision_user', 'block_content');
$this->assertTrue($post_revision_created instanceof BaseFieldDefinition, "Revision created field found");
$this->assertTrue($post_revision_user instanceof BaseFieldDefinition, "Revision user field found");
$this->assertEqual('created', $post_revision_created->getType(), "Field is type created");
$this->assertEqual('entity_reference', $post_revision_user->getType(), "Field is type entity_reference");
$entity_type = $entity_definition_update_manager->getEntityType('block_content');
$this->assertEqual('block_content_field_revision', $entity_type->getRevisionDataTable());
}
}
@@ -1,40 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
/**
* Tests block content validation constraints.
*
* @group block_content
*/
class BlockContentValidationTest extends BlockContentTestBase {
/**
* Tests the block content validation constraints.
*/
public function testValidation() {
// Add a block.
$description = $this->randomMachineName();
$block = $this->createBlockContent($description, 'basic');
// Validate the block.
$violations = $block->validate();
// Make sure we have no violations.
$this->assertEqual(count($violations), 0);
// Save the block.
$block->save();
// Add another block with the same description.
$block = $this->createBlockContent($description, 'basic');
// Validate this block.
$violations = $block->validate();
// Make sure we have 1 violation.
$this->assertEqual(count($violations), 1);
// Make sure the violation is on the info property
$this->assertEqual($violations[0]->getPropertyPath(), 'info');
// Make sure the message is correct.
$this->assertEqual($violations[0]->getMessage(), format_string('A custom block with block description %value already exists.', [
'%value' => $block->label(),
]));
}
}
@@ -1,71 +0,0 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Component\Utility\Unicode;
/**
* Create a block and test block edit functionality.
*
* @group block_content
*/
class PageEditTest extends BlockContentTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Checks block edit functionality.
*/
public function testPageEdit() {
$this->drupalLogin($this->adminUser);
$title_key = 'info[0][value]';
$body_key = 'body[0][value]';
// Create block to edit.
$edit = array();
$edit['info[0][value]'] = Unicode::strtolower($this->randomMachineName(8));
$edit[$body_key] = $this->randomMachineName(16);
$this->drupalPostForm('block/add/basic', $edit, t('Save'));
// Check that the block exists in the database.
$blocks = \Drupal::entityQuery('block_content')->condition('info', $edit['info[0][value]'])->execute();
$block = BlockContent::load(reset($blocks));
$this->assertTrue($block, 'Custom block found in database.');
// Load the edit page.
$this->drupalGet('block/' . $block->id());
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
// Edit the content of the block.
$edit = array();
$edit[$title_key] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
// Stay on the current page, without reloading.
$this->drupalPostForm(NULL, $edit, t('Save'));
// Edit the same block, creating a new revision.
$this->drupalGet("block/" . $block->id());
$edit = array();
$edit['info[0][value]'] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$edit['revision'] = TRUE;
$this->drupalPostForm(NULL, $edit, t('Save'));
// Ensure that the block revision has been created.
\Drupal::entityManager()->getStorage('block_content')->resetCache(array($block->id()));
$revised_block = BlockContent::load($block->id());
$this->assertNotIdentical($block->getRevisionId(), $revised_block->getRevisionId(), 'A new revision has been created.');
// Test deleting the block.
$this->drupalGet("block/" . $revised_block->id());
$this->clickLink(t('Delete'));
$this->assertText(format_string('Are you sure you want to delete the custom block @label?', array('@label' => $revised_block->label())));
}
}
@@ -1,109 +0,0 @@
<?php
namespace Drupal\block_content\Tests\Views;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests block_content field filters with translations.
*
* @group block_content
*/
class BlockContentFieldFilterTest extends BlockContentTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('language');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_field_filters');
/**
* List of block_content infos by language.
*
* @var array
*/
public $blockContentInfos = [];
/**
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Make the body field translatable. The info is already translatable by
// definition.
$field_storage = FieldStorageConfig::loadByName('block_content', 'body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
// Set up block_content infos.
$this->blockContentInfos = array(
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nouriture en Paris',
);
// Create block_content with translations.
$block_content = $this->createBlockContent(['info' => $this->blockContentInfos['en'], 'langcode' => 'en', 'type' => 'basic', 'body' => [['value' => $this->blockContentInfos['en']]]]);
foreach (array('es', 'fr') as $langcode) {
$translation = $block_content->addTranslation($langcode, ['info' => $this->blockContentInfos[$langcode]]);
$translation->body->value = $this->blockContentInfos[$langcode];
}
$block_content->save();
}
/**
* Tests body and info filters.
*/
public function testFilters() {
// Test the info filter page, which filters for info contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-info-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida info filter');
// Test the body filter page, which filters for body contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-body-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida body filter');
// Test the info Paris filter page, which filters for info contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-info-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris info filter');
// Test the body Paris filter page, which filters for body contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-body-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris body filter');
}
/**
* Asserts that the given block_content translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*/
protected function assertPageCounts($path, $counts, $message) {
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
foreach ($counts as $langcode => $count) {
$this->assertEqual(substr_count($text, $this->blockContentInfos[$langcode]), $count, 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
}
@@ -1,67 +0,0 @@
<?php
namespace Drupal\block_content\Tests\Views;
/**
* Tests the block_content integration into views.
*
* @group block_content
*/
class BlockContentIntegrationTest extends BlockContentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_block_content_view');
/**
* Tests basic block_content view with a block_content_type argument.
*/
public function testBlockContentViewTypeArgument() {
// Create two content types with three block_contents each.
$types = array();
$all_ids = array();
$block_contents = array();
for ($i = 0; $i < 2; $i++) {
$type = $this->createBlockContentType();
$types[] = $type;
for ($j = 0; $j < 5; $j++) {
// Ensure the right order of the block_contents.
$block_content = $this->createBlockContent(array('type' => $type->id()));
$block_contents[$type->id()][$block_content->id()] = $block_content;
$all_ids[] = $block_content->id();
}
}
$this->drupalGet('test-block_content-view');
$this->assertResponse(404);
$this->drupalGet('test-block_content-view/all');
$this->assertResponse(200);
$this->assertIds($all_ids);
/* @var \Drupal\block_content\Entity\BlockContentType[] $types*/
foreach ($types as $type) {
$this->drupalGet("test-block_content-view/{$type->id()}");
$this->assertIds(array_keys($block_contents[$type->id()]));
}
}
/**
* Ensures that a list of block_contents appear on the page.
*
* @param array $expected_ids
* An array of block_content IDs.
*/
protected function assertIds(array $expected_ids = array()) {
$result = $this->xpath('//span[@class="field-content"]');
$ids = array();
foreach ($result as $element) {
$ids[] = (int) $element;
}
$this->assertEqual($ids, $expected_ids);
}
}
@@ -1,37 +0,0 @@
<?php
namespace Drupal\block_content\Tests\Views;
use Drupal\views\Views;
/**
* Tests the Drupal\block_content\Plugin\views\field\Type handler.
*
* @group block_content
*/
class FieldTypeTest extends BlockContentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_field_type');
public function testFieldType() {
$block_content = $this->createBlockContent();
$expected_result[] = array(
'id' => $block_content->id(),
'type' => $block_content->bundle(),
);
$column_map = array(
'id' => 'id',
'type:target_id' => 'type',
);
$view = Views::getView('test_field_type');
$this->executeView($view);
$this->assertIdenticalResultset($view, $expected_result, $column_map, 'The correct block_content type was displayed.');
}
}
@@ -1,92 +0,0 @@
<?php
namespace Drupal\block_content\Tests\Views;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\block_content\Entity\BlockContent;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the integration of block_content_revision table of block_content module.
*
* @group block_content
*/
class RevisionRelationshipsTest extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block_content' , 'block_content_test_views');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_block_content_revision_id', 'test_block_content_revision_revision_id');
protected function setUp() {
parent::setUp();
BlockContentType::create(array(
'id' => 'basic',
'label' => 'basic',
'revision' => TRUE,
));
ViewTestData::createTestViews(get_class($this), array('block_content_test_views'));
}
/**
* Create a block_content with revision and rest result count for both views.
*/
public function testBlockContentRevisionRelationship() {
$block_content = BlockContent::create(array(
'info' => $this->randomMachineName(),
'type' => 'basic',
'langcode' => 'en',
));
$block_content->save();
// Create revision of the block_content.
$block_content_revision = clone $block_content;
$block_content_revision->setNewRevision();
$block_content_revision->save();
$column_map = array(
'revision_id' => 'revision_id',
'id_1' => 'id_1',
'block_content_field_data_block_content_field_revision_id' => 'block_content_field_data_block_content_field_revision_id',
);
// Here should be two rows.
$view_id = Views::getView('test_block_content_revision_id');
$this->executeView($view_id, array($block_content->id()));
$resultset_id = array(
array(
'revision_id' => '1',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
array(
'revision_id' => '2',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
);
$this->assertIdenticalResultset($view_id, $resultset_id, $column_map);
// There should be only one row with active revision 2.
$view_revision_id = Views::getView('test_block_content_revision_revision_id');
$this->executeView($view_revision_id, array($block_content->id()));
$resultset_revision_id = array(
array(
'revision_id' => '2',
'id_1' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
);
$this->assertIdenticalResultset($view_revision_id, $resultset_revision_id, $column_map);
}
}
@@ -7,8 +7,8 @@ package: Testing
dependencies:
- block_content
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -8,8 +8,8 @@ dependencies:
- block_content
- views
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
datestamp: 1515021228
@@ -31,7 +31,7 @@ class BlockContentRevisionsTest extends BlockContentTestBase {
protected function setUp() {
parent::setUp();
/** @var UserInterface $user */
/** @var \Drupal\user\Entity\UserInterface $user */
$user = User::load(1);
// Create initial block.

Some files were not shown because too many files have changed in this diff Show More