Update to Drupal 8.1.0. For more information, see https://www.drupal.org/drupal-8.1.0-release-notes

This commit is contained in:
Pantheon Automation 2016-04-20 09:56:34 -07:00 committed by Greg Anderson
parent b11a755ba8
commit c0a0d5a94c
6920 changed files with 64395 additions and 57312 deletions

View file

@ -0,0 +1,6 @@
name: 'BigPipe test'
type: module
description: 'Support module for BigPipe testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,18 @@
<?php
/**
* @file
* Support module for BigPipe testing.
*/
/**
* Implements hook_page_top().
*/
function big_pipe_test_page_top(array &$page_top) {
// Ensure this hook is invoked on every page load.
$page_top['#cache']['max-age'] = 0;
if (\Drupal::request()->query->get('trigger_session')) {
$_SESSION['big_pipe_test'] = TRUE;
}
}

View file

@ -0,0 +1,17 @@
big_pipe_test:
path: '/big_pipe_test'
defaults:
_controller: '\Drupal\big_pipe_test\BigPipeTestController::test'
_title: 'BigPipe test'
requirements:
_access: 'TRUE'
no_big_pipe:
path: '/no_big_pipe'
defaults:
_controller: '\Drupal\big_pipe_test\BigPipeTestController::nope'
_title: '_no_big_pipe route option test'
options:
_no_big_pipe: TRUE
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,5 @@
services:
big_pipe_test_subscriber:
class: Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,100 @@
<?php
namespace Drupal\big_pipe_test;
use Drupal\big_pipe\Render\BigPipeMarkup;
use Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber;
class BigPipeTestController {
/**
* Returns a all BigPipe placeholder test case render arrays.
*
* @return array
*/
public function test() {
$build = [];
$cases = \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases::cases(\Drupal::getContainer());
// 1. HTML placeholder: status messages. Drupal renders those automatically,
// so all that we need to do in this controller is set a message.
drupal_set_message('Hello from BigPipe!');
$build['html'] = $cases['html']->renderArray;
// 2. HTML attribute value placeholder: form action.
$build['html_attribute_value'] = $cases['html_attribute_value']->renderArray;
// 3. HTML attribute value subset placeholder: CSRF token in link.
$build['html_attribute_value_subset'] = $cases['html_attribute_value_subset']->renderArray;
// 4. Edge case: custom string to be considered as a placeholder that
// happens to not be valid HTML.
$build['edge_case__invalid_html'] = $cases['edge_case__invalid_html']->renderArray;
// 5. Edge case: non-#lazy_builder placeholder.
$build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray;
// 6. Exception: #lazy_builder that throws an exception.
$build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray;
// 7. Exception: placeholder that causes response filter to throw exception.
$build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray;
return $build;
}
/**
* @return array
*/
public static function nope() {
return ['#markup' => '<p>Nope.</p>'];
}
/**
* #lazy_builder callback; builds <time> markup with current time.
*
* Note: does not actually use current time, that would complicate testing.
*
* @return array
*/
public static function currentTime() {
return [
'#markup' => '<time datetime=' . date('Y-m-d', 668948400) . '"></time>',
'#cache' => ['max-age' => 0]
];
}
/**
* #lazy_builder callback; says "hello" or "yarhar".
*
* @return array
*/
public static function helloOrYarhar() {
return [
'#markup' => BigPipeMarkup::create('<marquee>Yarhar llamas forever!</marquee>'),
'#cache' => ['max-age' => 0],
];
}
/**
* #lazy_builder callback; throws exception.
*
* @throws \Exception
*/
public static function exception() {
throw new \Exception('You are not allowed to say llamas are not cool!');
}
/**
* #lazy_builder callback; returns content that will trigger an exception.
*
* @see \Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber::onRespondTriggerException()
*
* @return array
*/
public static function responseException() {
return ['#plain_text' => BigPipeTestSubscriber::CONTENT_TRIGGER_EXCEPTION];
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\big_pipe_test\EventSubscriber;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\HtmlResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class BigPipeTestSubscriber implements EventSubscriberInterface {
/**
* @see \Drupal\big_pipe_test\BigPipeTestController::responseException()
*
* @var string
*/
const CONTENT_TRIGGER_EXCEPTION = 'NOPE!NOPE!NOPE!';
/**
* Triggers exception for embedded HTML/AJAX responses with certain content.
*
* @see \Drupal\big_pipe_test\BigPipeTestController::responseException()
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*
* @throws \Exception
*/
public function onRespondTriggerException(FilterResponseEvent $event) {
$response = $event->getResponse();
if (!$response instanceof AttachmentsInterface) {
return;
}
$attachments = $response->getAttachments();
if (!isset($attachments['big_pipe_placeholders']) && !isset($attachments['big_pipe_nojs_placeholders'])) {
if (strpos($response->getContent(), static::CONTENT_TRIGGER_EXCEPTION) !== FALSE) {
throw new \Exception('Oh noes!');
}
}
}
/**
* Exposes all BigPipe placeholders (JS and no-JS) via headers for testing.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onRespondSetBigPipeDebugPlaceholderHeaders(FilterResponseEvent $event) {
$response = $event->getResponse();
if (!$response instanceof HtmlResponse) {
return;
}
$attachments = $response->getAttachments();
$response->headers->set('BigPipe-Test-Placeholders', '<none>');
$response->headers->set('BigPipe-Test-No-Js-Placeholders', '<none>');
if (!empty($attachments['big_pipe_placeholders'])) {
$response->headers->set('BigPipe-Test-Placeholders', implode(' ', array_keys($attachments['big_pipe_placeholders'])));
}
if (!empty($attachments['big_pipe_nojs_placeholders'])) {
$response->headers->set('BigPipe-Test-No-Js-Placeholders', implode(' ', array_map('rawurlencode', array_keys($attachments['big_pipe_nojs_placeholders']))));
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Run just before \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::onRespond().
$events[KernelEvents::RESPONSE][] = ['onRespondSetBigPipeDebugPlaceholderHeaders', -9999];
// Run just after \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::onRespond().
$events[KernelEvents::RESPONSE][] = ['onRespondTriggerException', -10001];
return $events;
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\big_pipe_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class BigPipeTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'big_pipe_test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#token'] = FALSE;
$form['big_pipe'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('BigPipe works…'),
'#options' => [
'js' => $this->t('… with JavaScript'),
'nojs' => $this->t('… without JavaScript'),
],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) { }
}

View file

@ -0,0 +1,143 @@
<?php
namespace Drupal\Tests\big_pipe\Unit\Render;
use Drupal\big_pipe\Render\BigPipeResponse;
use Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\Core\Asset\AssetResolverInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\RendererInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @coversDefaultClass \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
* @group big_pipe
*/
class BigPipeResponseAttachmentsProcessorTest extends UnitTestCase {
/**
* @covers ::processAttachments
*
* @dataProvider nonHtmlResponseProvider
*
* @expectedException \AssertionError
*/
public function testNonHtmlResponse($response_class) {
$big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($this->prophesize(AttachmentsResponseProcessorInterface::class));
$non_html_response = new $response_class();
$big_pipe_response_attachments_processor->processAttachments($non_html_response);
}
function nonHtmlResponseProvider() {
return [
'AjaxResponse, which implements AttachmentsInterface' => [AjaxResponse::class],
'A dummy that implements AttachmentsInterface' => [get_class($this->prophesize(AttachmentsInterface::class)->reveal())],
];
}
/**
* @covers ::processAttachments
*
* @dataProvider attachmentsProvider
*/
public function testHtmlResponse(array $attachments) {
$big_pipe_response = new BigPipeResponse('original');
$big_pipe_response->setAttachments($attachments);
// This mock is the main expectation of this test: verify that the decorated
// service (that is this mock) never receives BigPipe placeholder
// attachments, because it doesn't know (nor should it) how to handle them.
$html_response_attachments_processor = $this->prophesize(AttachmentsResponseProcessorInterface::class);
$html_response_attachments_processor->processAttachments(Argument::that(function ($response) {
return $response instanceof HtmlResponse && empty(array_intersect(['big_pipe_placeholders', 'big_pipe_nojs_placeholders'], array_keys($response->getAttachments())));
}))
->will(function ($args) {
/** @var \Symfony\Component\HttpFoundation\Response|\Drupal\Core\Render\AttachmentsInterface $response */
$response = $args[0];
// Simulate its actual behavior.
$attachments = array_diff_key($response->getAttachments(), ['html_response_attachment_placeholders' => TRUE]);
$response->setContent('processed');
$response->setAttachments($attachments);
return $response;
})
->shouldBeCalled();
$big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($html_response_attachments_processor);
$processed_big_pipe_response = $big_pipe_response_attachments_processor->processAttachments($big_pipe_response);
// The secondary expectation of this test: the original (passed in) response
// object remains unchanged, the processed (returned) response object has
// the expected values.
$this->assertSame($attachments, $big_pipe_response->getAttachments(), 'Attachments of original response object MUST NOT be changed.');
$this->assertEquals('original', $big_pipe_response->getContent(), 'Content of original response object MUST NOT be changed.');
$this->assertEquals(array_diff_key($attachments, ['html_response_attachment_placeholders' => TRUE]), $processed_big_pipe_response->getAttachments(), 'Attachments of returned (processed) response object MUST be changed.');
$this->assertEquals('processed', $processed_big_pipe_response->getContent(), 'Content of returned (processed) response object MUST be changed.');
}
public function attachmentsProvider() {
$typical_cases = [
'no attachments' => [[]],
'libraries' => [['library' => ['core/drupal']]],
'libraries + drupalSettings' => [['library' => ['core/drupal'], 'drupalSettings' => ['foo' => 'bar']]],
];
$official_attachment_types = ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'placeholders', 'drupalSettings', 'html_response_attachment_placeholders'];
$official_attachments_with_random_values = [];
foreach ($official_attachment_types as $type) {
$official_attachments_with_random_values[$type] = $this->randomMachineName();
}
$random_attachments = ['random' . $this->randomMachineName() => $this->randomMachineName()];
$edge_cases = [
'all official attachment types, with random assigned values, even if technically not valid, to prove BigPipeResponseAttachmentsProcessor is a perfect decorator' => [$official_attachments_with_random_values],
'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_cases = [
'only big_pipe_placeholders' => [$big_pipe_placeholder_attachments],
'only big_pipe_nojs_placeholders' => [$big_pipe_nojs_placeholder_attachments],
'big_pipe_placeholders + big_pipe_nojs_placeholders' => [$big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
];
$combined_cases = [
'all official attachment types + big_pipe_placeholders + big_pipe_nojs_placeholders' => [$official_attachments_with_random_values + $big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
'random attachment types + big_pipe_placeholders + big_pipe_nojs_placeholders' => [$random_attachments + $big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
];
return $typical_cases + $edge_cases + $big_pipe_cases + $combined_cases;
}
/**
* Creates a BigPipeResponseAttachmentsProcessor with mostly dummies.
*
* @param \Prophecy\Prophecy\ObjectProphecy $decorated_html_response_attachments_processor
* An object prophecy implementing AttachmentsResponseProcessorInterface.
*
* @return \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
* The BigPipeResponseAttachmentsProcessor to test.
*/
protected function createBigPipeResponseAttachmentsProcessor(ObjectProphecy $decorated_html_response_attachments_processor) {
return new BigPipeResponseAttachmentsProcessor(
$decorated_html_response_attachments_processor->reveal(),
$this->prophesize(AssetResolverInterface::class)->reveal(),
$this->prophesize(ConfigFactoryInterface::class)->reveal(),
$this->prophesize(AssetCollectionRendererInterface::class)->reveal(),
$this->prophesize(AssetCollectionRendererInterface::class)->reveal(),
$this->prophesize(RequestStack::class)->reveal(),
$this->prophesize(RendererInterface::class)->reveal(),
$this->prophesize(ModuleHandlerInterface::class)->reveal()
);
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\Tests\big_pipe\Unit\Render\Placeholder;
use Drupal\big_pipe\Render\Placeholder\BigPipeStrategy;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\SessionConfigurationInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
* @group big_pipe
*/
class BigPipeStrategyTest extends UnitTestCase {
/**
* @covers ::processPlaceholders
*
* @dataProvider placeholdersProvider
*/
public function testProcessPlaceholders(array $placeholders, $route_match_has_no_big_pipe_option, $request_has_session, $request_has_big_pipe_nojs_cookie, array $expected_big_pipe_placeholders) {
$request = new Request();
if ($request_has_big_pipe_nojs_cookie) {
$request->cookies->set(BigPipeStrategy::NOJS_COOKIE, 1);
}
$request_stack = $this->prophesize(RequestStack::class);
$request_stack->getCurrentRequest()
->willReturn($request);
$session_configuration = $this->prophesize(SessionConfigurationInterface::class);
$session_configuration->hasSession(Argument::type(Request::class))
->willReturn($request_has_session);
$route = $this->prophesize(Route::class);
$route->getOption('_no_big_pipe')
->willReturn($route_match_has_no_big_pipe_option);
$route_match = $this->prophesize(RouteMatchInterface::class);
$route_match->getRouteObject()
->willReturn($route);
$big_pipe_strategy = new BigPipeStrategy($session_configuration->reveal(), $request_stack->reveal(), $route_match->reveal());
$processed_placeholders = $big_pipe_strategy->processPlaceholders($placeholders);
if (!$route_match_has_no_big_pipe_option && $request_has_session) {
$this->assertSameSize($expected_big_pipe_placeholders, $processed_placeholders, 'BigPipe is able to deliver all placeholders.');
foreach (array_keys($placeholders) as $placeholder) {
$this->assertSame($expected_big_pipe_placeholders[$placeholder], $processed_placeholders[$placeholder], "Verifying how BigPipeStrategy handles the placeholder '$placeholder'");
}
}
else {
$this->assertSame(0, count($processed_placeholders));
}
}
/**
* @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
*/
public function placeholdersProvider() {
$cases = \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases::cases();
// Generate $placeholders variable as expected by
// \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface::processPlaceholders().
$placeholders = [
$cases['html']->placeholder => $cases['html']->placeholderRenderArray,
$cases['html_attribute_value']->placeholder => $cases['html_attribute_value']->placeholderRenderArray,
$cases['html_attribute_value_subset']->placeholder => $cases['html_attribute_value_subset']->placeholderRenderArray,
$cases['edge_case__invalid_html']->placeholder => $cases['edge_case__invalid_html']->placeholderRenderArray,
$cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->placeholderRenderArray,
$cases['exception__lazy_builder']->placeholder => $cases['exception__lazy_builder']->placeholderRenderArray,
$cases['exception__embedded_response']->placeholder => $cases['exception__embedded_response']->placeholderRenderArray,
];
return [
'_no_big_pipe absent, no session, no-JS cookie absent' => [$placeholders, FALSE, FALSE, FALSE, []],
'_no_big_pipe absent, no session, no-JS cookie present' => [$placeholders, FALSE, FALSE, TRUE, []],
'_no_big_pipe present, no session, no-JS cookie absent' => [$placeholders, TRUE, FALSE, FALSE, []],
'_no_big_pipe present, no session, no-JS cookie present' => [$placeholders, TRUE, FALSE, TRUE, []],
'_no_big_pipe present, session, no-JS cookie absent' => [$placeholders, TRUE, TRUE, FALSE, []],
'_no_big_pipe present, session, no-JS cookie present' => [$placeholders, TRUE, TRUE, TRUE, []],
'_no_big_pipe absent, session, no-JS cookie absent: (JS-powered) BigPipe placeholder used for HTML placeholders' => [$placeholders, FALSE, TRUE, FALSE, [
$cases['html']->placeholder => $cases['html']->bigPipePlaceholderRenderArray,
$cases['html_attribute_value']->placeholder => $cases['html_attribute_value']->bigPipeNoJsPlaceholderRenderArray,
$cases['html_attribute_value_subset']->placeholder => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
$cases['edge_case__invalid_html']->placeholder => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
$cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderRenderArray,
$cases['exception__lazy_builder']->placeholder => $cases['exception__lazy_builder']->bigPipePlaceholderRenderArray,
$cases['exception__embedded_response']->placeholder => $cases['exception__embedded_response']->bigPipePlaceholderRenderArray,
]],
'_no_big_pipe absent, session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders' => [$placeholders, FALSE, TRUE, TRUE, [
$cases['html']->placeholder => $cases['html']->bigPipeNoJsPlaceholderRenderArray,
$cases['html_attribute_value']->placeholder => $cases['html_attribute_value']->bigPipeNoJsPlaceholderRenderArray,
$cases['html_attribute_value_subset']->placeholder => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
$cases['edge_case__invalid_html']->placeholder => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
$cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholderRenderArray,
$cases['exception__lazy_builder']->placeholder => $cases['exception__lazy_builder']->bigPipeNoJsPlaceholderRenderArray,
$cases['exception__embedded_response']->placeholder => $cases['exception__embedded_response']->bigPipeNoJsPlaceholderRenderArray,
]],
];
}
}