Initial Drupal 11 with DDEV setup
This commit is contained in:
		@ -0,0 +1,5 @@
 | 
			
		||||
name: 'BigPipe bypass JS'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Prevents the loading of Big Pipe JavaScript. Used for testing preview templates.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_bypass_js\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for big_pipe_bypass_js.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeBypassJsHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_library_info_alter().
 | 
			
		||||
   *
 | 
			
		||||
   * Disables Big Pipe JavaScript by removing the js file from the library.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('library_info_alter')]
 | 
			
		||||
  public function libraryInfoAlter(&$libraries, $extension): void {
 | 
			
		||||
    if ($extension === 'big_pipe') {
 | 
			
		||||
      unset($libraries['big_pipe']['js']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
name: 'BigPipe messages test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Forces the messages placeholder to go via the big pipe strategy for testing purposes.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_messages_test\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\Security\Attribute\TrustedCallback;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for big_pipe_test.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeMessagesHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_element_info_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('element_info_alter')]
 | 
			
		||||
  public function elementInfoAlter(array &$info): void {
 | 
			
		||||
    $info['status_messages']['#pre_render'][] = static::class . '::preRenderMessages';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Pre render callback.
 | 
			
		||||
   *
 | 
			
		||||
   * Removes #placeholder_strategy from the messages element to force the
 | 
			
		||||
   * messages placeholder to go via the big pipe strategy for testing purposes.
 | 
			
		||||
   */
 | 
			
		||||
  #[TrustedCallback]
 | 
			
		||||
  public static function preRenderMessages(array $element): array {
 | 
			
		||||
    if (isset($element['#attached']['placeholders'])) {
 | 
			
		||||
      $key = key($element['#attached']['placeholders']);
 | 
			
		||||
      unset($element['#attached']['placeholders'][$key]['#placeholder_strategy_denylist']);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($element['messages']['#attached']['placeholders'])) {
 | 
			
		||||
      $key = key($element['messages']['#attached']['placeholders']);
 | 
			
		||||
      unset($element['messages']['#attached']['placeholders'][$key]['#placeholder_strategy_denylist']);
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
name: 'BigPipe regression test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for BigPipe regression testing.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
big_pipe_regression_test.2678662:
 | 
			
		||||
  path: '/big_pipe_regression_test/2678662'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2678662'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_regression_test.2802923:
 | 
			
		||||
  path: '/big_pipe_regression_test/2802923'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2802923'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_large_content:
 | 
			
		||||
  path: '/big_pipe_test_large_content'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::largeContent'
 | 
			
		||||
    _title: 'BigPipe test large content'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_multiple_replacements:
 | 
			
		||||
  path: '/big_pipe_test_multiple_replacements'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::multipleReplacements'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
@ -0,0 +1,121 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_regression_test;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeMarkup;
 | 
			
		||||
use Drupal\Component\Utility\Random;
 | 
			
		||||
use Drupal\Core\Security\TrustedCallbackInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controller for BigPipe regression tests.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeRegressionTestController implements TrustedCallbackInterface {
 | 
			
		||||
 | 
			
		||||
  const MARKER_2678662 = '<script>var hitsTheFloor = "</body>";</script>';
 | 
			
		||||
 | 
			
		||||
  const PLACEHOLDER_COUNT = 2000;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleBodies_2678662()
 | 
			
		||||
   */
 | 
			
		||||
  public function regression2678662() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => BigPipeMarkup::create(self::MARKER_2678662),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleBodies_2678662()
 | 
			
		||||
   */
 | 
			
		||||
  public function regression2802923() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#prefix' => BigPipeMarkup::create('<p>Hi, my train will arrive at '),
 | 
			
		||||
      'time' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::currentTime', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      '#suffix' => BigPipeMarkup::create(' — will I still be able to catch the connection to the center?</p>'),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A page with large content.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
 | 
			
		||||
   */
 | 
			
		||||
  public function largeContent() {
 | 
			
		||||
    return [
 | 
			
		||||
      'item1' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::largeContentBuilder', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A page with multiple nodes.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleReplacements
 | 
			
		||||
   */
 | 
			
		||||
  public function multipleReplacements() {
 | 
			
		||||
    $build = [];
 | 
			
		||||
    foreach (range(1, self::PLACEHOLDER_COUNT) as $length) {
 | 
			
		||||
      $build[] = [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::renderRandomSentence', [$length]],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders large content.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
 | 
			
		||||
   */
 | 
			
		||||
  public static function largeContentBuilder() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#theme' => 'big_pipe_test_large_content',
 | 
			
		||||
      '#cache' => ['max-age' => 0],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: Builds <time> markup with current time.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is assigned as a #lazy_builder callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Render array with a <time> markup with current time and cache settings.
 | 
			
		||||
   */
 | 
			
		||||
  public static function currentTime() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => '<time datetime="' . date('Y-m-d', time()) . '"></time>',
 | 
			
		||||
      '#cache' => ['max-age' => 0],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a random length sentence.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $length
 | 
			
		||||
   *   The sentence length.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Render array.
 | 
			
		||||
   */
 | 
			
		||||
  public static function renderRandomSentence(int $length): array {
 | 
			
		||||
    return ['#cache' => ['max-age' => 0], '#markup' => (new Random())->sentences($length)];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function trustedCallbacks() {
 | 
			
		||||
    return ['currentTime', 'largeContentBuilder', 'renderRandomSentence'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_regression_test\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for big_pipe_regression_test.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeRegressionTestHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_theme().
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('theme')]
 | 
			
		||||
  public function theme() : array {
 | 
			
		||||
    return ['big_pipe_test_large_content' => ['variables' => []]];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
<div id="big-pipe-large-content">
 | 
			
		||||
    {% for i in 0..130000 %}
 | 
			
		||||
        boing
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
name: 'BigPipe test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for BigPipe testing.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_multi_occurrence:
 | 
			
		||||
  path: '/big_pipe_test_multi_occurrence'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_test\BigPipeTestController::multiOccurrence'
 | 
			
		||||
    _title: 'BigPipe test multiple occurrences of the same placeholder'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_preview:
 | 
			
		||||
  path: '/big_pipe_test_preview'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_test\BigPipeTestController::placeholderPreview'
 | 
			
		||||
    _title: 'Test placeholder previews'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_trusted_redirect:
 | 
			
		||||
  path: '/big_pipe_test_trusted_redirect'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_test\BigPipeTestController::trustedRedirectLazyBuilder'
 | 
			
		||||
    _title: 'BigPipe test trusted redirect'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
big_pipe_test_untrusted_redirect:
 | 
			
		||||
  path: '/big_pipe_test_untrusted_redirect'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\big_pipe_test\BigPipeTestController::untrustedRedirectLazyBuilder'
 | 
			
		||||
    _title: 'BigPipe test untrusted redirect'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
services:
 | 
			
		||||
  _defaults:
 | 
			
		||||
    autoconfigure: true
 | 
			
		||||
  big_pipe_test_subscriber:
 | 
			
		||||
    class: Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber
 | 
			
		||||
@ -0,0 +1,511 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
// cspell:ignore divpiggydiv timecurrent timetime
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_test;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeMarkup;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
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\Tests\big_pipe\Functional\BigPipeTest::testBigPipe()
 | 
			
		||||
 * - Integration test for BigPipe with JS off:
 | 
			
		||||
 *   \Drupal\Tests\big_pipe\Functional\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_test\BigPipePlaceholderTestCase[]
 | 
			
		||||
   *   An array of placeholder test cases.
 | 
			
		||||
   */
 | 
			
		||||
  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'],
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '<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],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    // cspell:disable-next-line
 | 
			
		||||
    $status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
 | 
			
		||||
    $status_messages->bigPipePlaceholderRenderArray = [
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '#prefix' => '<span data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA">',
 | 
			
		||||
      'interface_preview' => [
 | 
			
		||||
        '#theme' => 'big_pipe_interface_preview',
 | 
			
		||||
        '#callback' => 'Drupal\Core\Render\Element\StatusMessages::renderMessages',
 | 
			
		||||
        '#arguments' => [NULL],
 | 
			
		||||
      ],
 | 
			
		||||
      '#suffix' => '</span>',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'library' => ['big_pipe/big_pipe'],
 | 
			
		||||
        'drupalSettings' => [
 | 
			
		||||
          'bigPipePlaceholderIds' => [
 | 
			
		||||
            // cspell:disable-next-line
 | 
			
		||||
            'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
        'big_pipe_placeholders' => [
 | 
			
		||||
          // cspell:disable-next-line
 | 
			
		||||
          'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // cspell:disable-next-line
 | 
			
		||||
    $status_messages->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>';
 | 
			
		||||
    $status_messages->bigPipeNoJsPlaceholderRenderArray = [
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '#markup' => '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'big_pipe_nojs_placeholders' => [
 | 
			
		||||
          // cspell:disable-next-line
 | 
			
		||||
          '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>' => $status_messages->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    if ($container && $user) {
 | 
			
		||||
      $status_messages->embeddedAjaxResponseCommands = [
 | 
			
		||||
        [
 | 
			
		||||
          'command' => 'insert',
 | 
			
		||||
          'method' => 'replaceWith',
 | 
			
		||||
          // cspell:disable-next-line
 | 
			
		||||
          'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
 | 
			
		||||
          'data' => '<div data-drupal-messages>' . "\n" . ' <div role="contentinfo" aria-label="Status message">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n" . '</div>' . "\n",
 | 
			
		||||
          'settings' => NULL,
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      $status_messages->embeddedHtmlResponse = '<div data-drupal-messages-fallback class="hidden"></div><div data-drupal-messages>' . "\n" . '  <div role="contentinfo" aria-label="Status message">' . "\n" . '              <h2 class="visually-hidden">Status message</h2>' . "\n" . '              Hello from BigPipe!' . "\n" . '          </div>' . "\n" . '</div>' . "\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::helloOrHi', []]],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '<hello',
 | 
			
		||||
      [
 | 
			
		||||
        '#lazy_builder' => [
 | 
			
		||||
          // We specifically test an invalid callback here. We need to let
 | 
			
		||||
          // PHPStan ignore it.
 | 
			
		||||
          // @phpstan-ignore-next-line
 | 
			
		||||
          'hello_or_hi',
 | 
			
		||||
          [],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $hello->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:<hello';
 | 
			
		||||
    $hello->bigPipeNoJsPlaceholderRenderArray = [
 | 
			
		||||
      '#markup' => 'big_pipe_nojs_placeholder_attribute_safe:<hello',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_only,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'big_pipe_nojs_placeholders' => [
 | 
			
		||||
          'big_pipe_nojs_placeholder_attribute_safe:<hello' => $hello->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $hello->embeddedHtmlResponse = '<marquee>Llamas forever!</marquee>';
 | 
			
		||||
 | 
			
		||||
    // 5. Edge case: non-#lazy_builder placeholder that calls Fiber::suspend().
 | 
			
		||||
    $piggy = new BigPipePlaceholderTestCase(
 | 
			
		||||
      [
 | 
			
		||||
        '#markup' => BigPipeMarkup::create('<div>piggy</div>'),
 | 
			
		||||
        '#attached' => [
 | 
			
		||||
          'placeholders' => [
 | 
			
		||||
            '<div>piggy</div>' => [
 | 
			
		||||
              '#pre_render' => [
 | 
			
		||||
                '\Drupal\big_pipe_test\BigPipeTestController::piggy',
 | 
			
		||||
              ],
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '<div>piggy</div>',
 | 
			
		||||
      []
 | 
			
		||||
    );
 | 
			
		||||
    $piggy->bigPipePlaceholderId = 'divpiggydiv';
 | 
			
		||||
    $piggy->bigPipePlaceholderRenderArray = [
 | 
			
		||||
      '#prefix' => '<span data-big-pipe-placeholder-id="divpiggydiv">',
 | 
			
		||||
      'interface_preview' => [],
 | 
			
		||||
      '#suffix' => '</span>',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'library' => ['big_pipe/big_pipe'],
 | 
			
		||||
        'drupalSettings' => [
 | 
			
		||||
          'bigPipePlaceholderIds' => [
 | 
			
		||||
            'divpiggydiv' => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
        'big_pipe_placeholders' => [
 | 
			
		||||
          'divpiggydiv' => $piggy->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $piggy->embeddedAjaxResponseCommands = [
 | 
			
		||||
      [
 | 
			
		||||
        'command' => 'insert',
 | 
			
		||||
        'method' => 'replaceWith',
 | 
			
		||||
        'selector' => '[data-big-pipe-placeholder-id="divpiggydiv"]',
 | 
			
		||||
        'data' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>',
 | 
			
		||||
        'settings' => NULL,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $piggy->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="divpiggydiv></span>';
 | 
			
		||||
    $piggy->bigPipeNoJsPlaceholderRenderArray = [
 | 
			
		||||
      '#markup' => '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'big_pipe_nojs_placeholders' => [
 | 
			
		||||
          '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>' => $piggy->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $piggy->embeddedHtmlResponse = '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>';
 | 
			
		||||
 | 
			
		||||
    // 6. 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>',
 | 
			
		||||
      [
 | 
			
		||||
        // We specifically test an invalid callback here. We need to let
 | 
			
		||||
        // PHPStan ignore it.
 | 
			
		||||
        // @phpstan-ignore-next-line
 | 
			
		||||
        '#pre_render' => ['current_time'],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $current_time->bigPipePlaceholderId = 'timecurrent-timetime';
 | 
			
		||||
    $current_time->bigPipePlaceholderRenderArray = [
 | 
			
		||||
      '#prefix' => '<span data-big-pipe-placeholder-id="timecurrent-timetime">',
 | 
			
		||||
      'interface_preview' => [],
 | 
			
		||||
      '#suffix' => '</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>';
 | 
			
		||||
 | 
			
		||||
    // 7. Edge case: #lazy_builder that throws an exception.
 | 
			
		||||
    $exception = new BigPipePlaceholderTestCase(
 | 
			
		||||
      [
 | 
			
		||||
        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&1=suck" token="uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></drupal-render-placeholder>',
 | 
			
		||||
      [
 | 
			
		||||
        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    // cspell:disable-next-line
 | 
			
		||||
    $exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
 | 
			
		||||
    $exception->bigPipePlaceholderRenderArray = [
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '#prefix' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU">',
 | 
			
		||||
      'interface_preview' => [
 | 
			
		||||
        '#theme' => 'big_pipe_interface_preview',
 | 
			
		||||
        '#callback' => '\Drupal\big_pipe_test\BigPipeTestController::exception',
 | 
			
		||||
        '#arguments' => ['llamas', 'suck'],
 | 
			
		||||
      ],
 | 
			
		||||
      '#suffix' => '</span>',
 | 
			
		||||
      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
 | 
			
		||||
      '#attached' => [
 | 
			
		||||
        'library' => ['big_pipe/big_pipe'],
 | 
			
		||||
        'drupalSettings' => [
 | 
			
		||||
          'bigPipePlaceholderIds' => [
 | 
			
		||||
            // cspell:disable-next-line
 | 
			
		||||
            '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' => [
 | 
			
		||||
          // cspell:disable-next-line
 | 
			
		||||
          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $exception->embeddedAjaxResponseCommands = NULL;
 | 
			
		||||
    // cspell:disable-next-line
 | 
			
		||||
    $exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&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;
 | 
			
		||||
 | 
			
		||||
    // cSpell:disable-next-line.
 | 
			
		||||
    $token = 'PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU';
 | 
			
		||||
    // 8. 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="' . $token . ' "></drupal-render-placeholder>',
 | 
			
		||||
      [
 | 
			
		||||
        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=' . $token;
 | 
			
		||||
    $embedded_response_exception->bigPipePlaceholderRenderArray = [
 | 
			
		||||
      // cspell:disable-next-line
 | 
			
		||||
      '#prefix' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU">',
 | 
			
		||||
      'interface_preview' => [
 | 
			
		||||
        '#theme' => 'big_pipe_interface_preview',
 | 
			
		||||
        '#callback' => '\Drupal\big_pipe_test\BigPipeTestController::responseException',
 | 
			
		||||
        '#arguments' => [],
 | 
			
		||||
      ],
 | 
			
		||||
      '#suffix' => '</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=' . $token => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
        'big_pipe_placeholders' => [
 | 
			
		||||
          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=' . $token => $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&&token=' . $token . '"></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_suspend' => $piggy,
 | 
			
		||||
      'edge_case__html_non_lazy_builder' => $current_time,
 | 
			
		||||
      'exception__lazy_builder' => $exception,
 | 
			
		||||
      'exception__embedded_response' => $embedded_response_exception,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a placeholder for the BigPipe placeholder test cases.
 | 
			
		||||
 */
 | 
			
		||||
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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,307 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_test;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeMarkup;
 | 
			
		||||
use Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber;
 | 
			
		||||
use Drupal\Core\Form\EnforcedResponseException;
 | 
			
		||||
use Drupal\Core\Security\TrustedCallbackInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RedirectResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns responses for Big Pipe routes.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeTestController implements TrustedCallbackInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all BigPipe placeholder test case render arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Render array containing various Big Pipe placeholder test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public function test() {
 | 
			
		||||
    $has_session = \Drupal::service('session_configuration')->hasSession(\Drupal::requestStack()->getMainRequest());
 | 
			
		||||
 | 
			
		||||
    $build = [];
 | 
			
		||||
 | 
			
		||||
    $cases = 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.
 | 
			
		||||
    if ($has_session) {
 | 
			
		||||
      // Only set a message if a session already exists, otherwise we always
 | 
			
		||||
      // trigger a session, which means we can't test no-session requests.
 | 
			
		||||
      \Drupal::messenger()->addStatus('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 that suspends.
 | 
			
		||||
    $build['edge_case__html_non_lazy_builder_suspend'] = $cases['edge_case__html_non_lazy_builder_suspend']->renderArray;
 | 
			
		||||
 | 
			
		||||
    // 6. Edge case: non-#lazy_builder placeholder.
 | 
			
		||||
    $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray;
 | 
			
		||||
 | 
			
		||||
    // 7. Exception: #lazy_builder that throws an exception.
 | 
			
		||||
    $build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray;
 | 
			
		||||
 | 
			
		||||
    // 8. Exception: placeholder that causes response filter to throw exception.
 | 
			
		||||
    $build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray;
 | 
			
		||||
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   List of all BigPipe placeholder test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function nope() {
 | 
			
		||||
    return ['#markup' => '<p>Nope.</p>'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A page with multiple occurrences of the same placeholder.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\Functional\BigPipeTest::testBigPipeMultiOccurrencePlaceholders()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Render array with multiple placeholders using a lazy builder.
 | 
			
		||||
   */
 | 
			
		||||
  public function multiOccurrence() {
 | 
			
		||||
    return [
 | 
			
		||||
      'item1' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::counter', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'item2' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::counter', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'item3' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::counter', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A page with placeholder preview.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array[]
 | 
			
		||||
   *   A render array with two containers:
 | 
			
		||||
   *   - 'user_container': Loads the user’s display name via a lazy builder.
 | 
			
		||||
   *   - 'user_links_container': Loads user links with a placeholder preview.
 | 
			
		||||
   */
 | 
			
		||||
  public function placeholderPreview() {
 | 
			
		||||
    return [
 | 
			
		||||
      'user_container' => [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#attributes' => ['id' => 'placeholder-preview-twig-container'],
 | 
			
		||||
        'user' => [
 | 
			
		||||
          '#lazy_builder' => ['user.toolbar_link_builder:renderDisplayName', []],
 | 
			
		||||
          '#create_placeholder' => TRUE,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'user_links_container' => [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#attributes' => ['id' => 'placeholder-render-array-container'],
 | 
			
		||||
        'user_links' => [
 | 
			
		||||
          '#lazy_builder' => [static::class . '::helloOrHi', []],
 | 
			
		||||
          '#create_placeholder' => TRUE,
 | 
			
		||||
          '#lazy_builder_preview' => [
 | 
			
		||||
            '#attributes' => ['id' => 'render-array-preview'],
 | 
			
		||||
            '#type' => 'container',
 | 
			
		||||
            '#markup' => 'There is a lamb and there is a puppy',
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: Builds <time> markup with current time.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is assigned as a #lazy_builder callback.
 | 
			
		||||
   *
 | 
			
		||||
   * Note: does not actually use current time, that would complicate testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array containing a <time> element with a predefined date
 | 
			
		||||
   *   and disabled caching for dynamic rendering.
 | 
			
		||||
   */
 | 
			
		||||
  public static function currentTime() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => '<time datetime="' . date('Y-m-d', 668948400) . '"></time>',
 | 
			
		||||
      '#cache' => ['max-age' => 0],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: Suspends its own execution then returns markup.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is assigned as a #lazy_builder callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array with a pig-themed message wrapped in a <span>,
 | 
			
		||||
   *   and caching disabled to ensure dynamic rendering.
 | 
			
		||||
   */
 | 
			
		||||
  public static function piggy(): array {
 | 
			
		||||
    // Immediately call Fiber::suspend(), so that other placeholders are
 | 
			
		||||
    // executed next. When this is resumed, it will immediately return the
 | 
			
		||||
    // render array.
 | 
			
		||||
    if (\Fiber::getCurrent() !== NULL) {
 | 
			
		||||
      \Fiber::suspend();
 | 
			
		||||
    }
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>',
 | 
			
		||||
      '#cache' => ['max-age' => 0],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: Says "hello" or "hi".
 | 
			
		||||
   *
 | 
			
		||||
   * This function is assigned as a #lazy_builder callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array with a marquee message using BigPipeMarkup,
 | 
			
		||||
   *   with caching disabled and a custom cache tag.
 | 
			
		||||
   */
 | 
			
		||||
  public static function helloOrHi() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => BigPipeMarkup::create('<marquee>llamas forever!</marquee>'),
 | 
			
		||||
      '#cache' => [
 | 
			
		||||
        'max-age' => 0,
 | 
			
		||||
        'tags' => ['cache_tag_set_in_lazy_builder'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The #lazy_builder callback; throws exception.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   */
 | 
			
		||||
  public static function exception() {
 | 
			
		||||
    throw new \Exception('You are not allowed to say llamas are not cool!');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The #lazy_builder callback; returns content that will trigger an exception.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber::onRespondTriggerException()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array with plain text for testing BigPipe error handling.
 | 
			
		||||
   */
 | 
			
		||||
  public static function responseException() {
 | 
			
		||||
    return ['#plain_text' => BigPipeTestSubscriber::CONTENT_TRIGGER_EXCEPTION];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The #lazy_builder callback; returns the current count.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\big_pipe\Functional\BigPipeTest::testBigPipeMultiOccurrencePlaceholders()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array.
 | 
			
		||||
   */
 | 
			
		||||
  public static function counter() {
 | 
			
		||||
    // Lazy builders are not allowed to build their own state like this function
 | 
			
		||||
    // does, but in this case we're intentionally doing that for testing
 | 
			
		||||
    // purposes: so we can ensure that each lazy builder is only ever called
 | 
			
		||||
    // once with the same parameters.
 | 
			
		||||
    static $count;
 | 
			
		||||
 | 
			
		||||
    if (!isset($count)) {
 | 
			
		||||
      $count = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $count++;
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => BigPipeMarkup::create("<p>The count is $count.</p>"),
 | 
			
		||||
      '#cache' => ['max-age' => 0],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Route callback to test a trusted lazy builder redirect response.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The lazy builder callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function trustedRedirectLazyBuilder(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      'redirect' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::redirectTrusted', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Supports Big Pipe testing of the enforced redirect response.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Core\Form\EnforcedResponseException
 | 
			
		||||
   *   Trigger catch of Big Pipe enforced redirect response exception.
 | 
			
		||||
   */
 | 
			
		||||
  public static function redirectTrusted(): void {
 | 
			
		||||
    $response = new RedirectResponse('/big_pipe_test');
 | 
			
		||||
    throw new EnforcedResponseException($response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Route callback to test an untrusted lazy builder redirect response.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The lazy builder callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function untrustedRedirectLazyBuilder(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      'redirect' => [
 | 
			
		||||
        '#lazy_builder' => [static::class . '::redirectUntrusted', []],
 | 
			
		||||
        '#create_placeholder' => TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Supports Big Pipe testing of an untrusted external URL.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Core\Form\EnforcedResponseException
 | 
			
		||||
   *   Trigger catch of Big Pipe enforced redirect response exception.
 | 
			
		||||
   */
 | 
			
		||||
  public static function redirectUntrusted(): void {
 | 
			
		||||
    $response = new RedirectResponse('https://example.com');
 | 
			
		||||
    throw new EnforcedResponseException($response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function trustedCallbacks() {
 | 
			
		||||
    return [
 | 
			
		||||
      'currentTime',
 | 
			
		||||
      'piggy',
 | 
			
		||||
      'helloOrHi',
 | 
			
		||||
      'exception',
 | 
			
		||||
      'responseException',
 | 
			
		||||
      'counter',
 | 
			
		||||
      'redirectTrusted',
 | 
			
		||||
      'redirectUntrusted',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,89 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_test\EventSubscriber;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\AttachmentsInterface;
 | 
			
		||||
use Drupal\Core\Render\HtmlResponse;
 | 
			
		||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
 | 
			
		||||
use Symfony\Component\HttpKernel\KernelEvents;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a test BigPipe subscriber that checks whether the session is empty.
 | 
			
		||||
 */
 | 
			
		||||
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.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
 | 
			
		||||
   *   The event to process.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\big_pipe_test\BigPipeTestController::responseException()
 | 
			
		||||
   */
 | 
			
		||||
  public function onRespondTriggerException(ResponseEvent $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 (str_contains($response->getContent(), static::CONTENT_TRIGGER_EXCEPTION)) {
 | 
			
		||||
        throw new \Exception('Oh noes!');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Exposes all BigPipe placeholders (JS and no-JS) via headers for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
 | 
			
		||||
   *   The event to process.
 | 
			
		||||
   */
 | 
			
		||||
  public function onRespondSetBigPipeDebugPlaceholderHeaders(ResponseEvent $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(): array {
 | 
			
		||||
    // 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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_test\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form to test BigPipe.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
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'] = [
 | 
			
		||||
      '#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) {}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\big_pipe_test\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for big_pipe_test.
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeTestHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_page_top().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('page_top')]
 | 
			
		||||
  public function pageTop(array &$page_top): void {
 | 
			
		||||
    // Ensure this hook is invoked on every page load.
 | 
			
		||||
    $page_top['#cache']['max-age'] = 0;
 | 
			
		||||
    $request = \Drupal::request();
 | 
			
		||||
    if ($request->query->get('trigger_session')) {
 | 
			
		||||
      $request->getSession()->set('big_pipe_test', TRUE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										580
									
								
								web/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										580
									
								
								web/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,580 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Functional;
 | 
			
		||||
 | 
			
		||||
use Behat\Mink\Element\NodeElement;
 | 
			
		||||
use Drupal\big_pipe\Render\Placeholder\BigPipeStrategy;
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipe;
 | 
			
		||||
use Drupal\big_pipe_test\BigPipePlaceholderTestCases;
 | 
			
		||||
use Drupal\Component\Serialization\Json;
 | 
			
		||||
use Drupal\Component\Utility\Html;
 | 
			
		||||
use Drupal\Component\Utility\UrlHelper;
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\Logger\RfcLogLevel;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 BrowserTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['big_pipe', 'big_pipe_messages_test', 'big_pipe_test', 'dblog'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaultTheme = 'stark';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    // Ignore the <meta> refresh that big_pipe.module sets. It causes a redirect
 | 
			
		||||
    // to a page that sets another cookie, which causes BrowserTestBase 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(): void {
 | 
			
		||||
    $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(): void {
 | 
			
		||||
    $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('0');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
    $this->assertSession()->responseNotContains($no_js_to_js_markup);
 | 
			
		||||
 | 
			
		||||
    // 2. Session (authenticated).
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test'));
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
    $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . UrlHelper::encodePath(base_path() . 'big_pipe_test') . '" />' . "\n" . '</noscript>');
 | 
			
		||||
    $this->assertSession()->responseNotContains($no_js_to_js_markup);
 | 
			
		||||
    $this->assertBigPipeNoJsMetaRefreshRedirect();
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('1');
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
    $this->assertSession()->responseContains($no_js_to_js_markup);
 | 
			
		||||
    $this->drupalLogout();
 | 
			
		||||
 | 
			
		||||
    // Close the prior connection and remove the collected state.
 | 
			
		||||
    $this->getSession()->reset();
 | 
			
		||||
 | 
			
		||||
    // 3. Session (anonymous).
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('user.login', [], ['query' => ['trigger_session' => 1]]));
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test'));
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
    $this->assertSession()->responseContains('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'big_pipe_test" />' . "\n" . '</noscript>');
 | 
			
		||||
    $this->assertSession()->responseNotContains($no_js_to_js_markup);
 | 
			
		||||
    $this->assertBigPipeNoJsMetaRefreshRedirect();
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('1');
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
    $this->assertSession()->responseContains($no_js_to_js_markup);
 | 
			
		||||
 | 
			
		||||
    // Close the prior connection and remove the collected state.
 | 
			
		||||
    $this->getSession()->reset();
 | 
			
		||||
 | 
			
		||||
    // Edge case: route with '_no_big_pipe' option.
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('no_big_pipe'));
 | 
			
		||||
    $this->assertSessionCookieExists('0');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
    $this->assertSession()->responseNotContains($no_js_to_js_markup);
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('no_big_pipe'));
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
    $this->assertSession()->responseNotContains($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_test\BigPipePlaceholderTestCases
 | 
			
		||||
   */
 | 
			
		||||
  public function testBigPipe(): void {
 | 
			
		||||
    // Simulate production.
 | 
			
		||||
    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
 | 
			
		||||
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
 | 
			
		||||
    $connection = Database::getConnection();
 | 
			
		||||
    $log_count = $connection->select('watchdog')->countQuery()->execute()->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->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', '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_suspend']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder_suspend']->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['html']->bigPipePlaceholderId,
 | 
			
		||||
      1 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
 | 
			
		||||
      // The suspended placeholder is replaced after the non-suspended
 | 
			
		||||
      // placeholder even though it appears first in the page.
 | 
			
		||||
      // @see Drupal\big_pipe\Render\BigPipe\Render::sendPlaceholders()
 | 
			
		||||
      2 => $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $this->assertSession()->responseContains('</body>');
 | 
			
		||||
 | 
			
		||||
    // Verifying BigPipe assets are present.
 | 
			
		||||
    $this->assertNotEmpty($this->getDrupalSettings());
 | 
			
		||||
    $this->assertContains('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->assertEquals($log_count + 2, (int) $connection->select('watchdog')->countQuery()->execute()->fetchField(), 'Two new watchdog entries.');
 | 
			
		||||
    // Using dynamic select queries with the method range() allows contrib
 | 
			
		||||
    // database drivers the ability to insert their own limit and offset
 | 
			
		||||
    // functionality.
 | 
			
		||||
    $records = $connection->select('watchdog', 'w')->fields('w')->orderBy('wid', 'DESC')->range(0, 2)->execute()->fetchAll();
 | 
			
		||||
    $this->assertEquals(RfcLogLevel::WARNING, $records[0]->severity);
 | 
			
		||||
    $this->assertStringContainsString('Oh noes!', (string) unserialize($records[0]->variables)['@message']);
 | 
			
		||||
    $this->assertEquals(RfcLogLevel::WARNING, $records[1]->severity);
 | 
			
		||||
    $this->assertStringContainsString('You are not allowed to say llamas are not cool!', (string) unserialize($records[1]->variables)['@message']);
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
    // Verifying BigPipe provides useful error output when an error occurs
 | 
			
		||||
    // while rendering a placeholder if verbose error logging is enabled.
 | 
			
		||||
    $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->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later');
 | 
			
		||||
    $this->assertSession()->pageTextContains('You are not allowed to say llamas are not cool!');
 | 
			
		||||
    // Check that stop signal and closing body tag are absent.
 | 
			
		||||
    $this->assertSession()->responseNotContains(BigPipe::STOP_SIGNAL);
 | 
			
		||||
    $this->assertSession()->responseNotContains('</body>');
 | 
			
		||||
    // The exception is expected. Do not interpret it as a test failure.
 | 
			
		||||
    unlink($this->root . '/' . $this->siteDirectory . '/error.log');
 | 
			
		||||
 | 
			
		||||
    // Tests the enforced redirect response exception handles redirecting to
 | 
			
		||||
    // a trusted redirect.
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_trusted_redirect'));
 | 
			
		||||
    $this->assertSession()->responseContains('application/vnd.drupal-ajax');
 | 
			
		||||
    $this->assertSession()->responseContains('[{"command":"redirect","url":"\/big_pipe_test"}]');
 | 
			
		||||
 | 
			
		||||
    // Test that it rejects an untrusted redirect.
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_untrusted_redirect'));
 | 
			
		||||
    $this->assertSession()->responseContains('Redirects to external URLs are not allowed by default');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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_test\BigPipePlaceholderTestCases
 | 
			
		||||
   */
 | 
			
		||||
  public function testBigPipeNoJs(): void {
 | 
			
		||||
    // Simulate production.
 | 
			
		||||
    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
 | 
			
		||||
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
 | 
			
		||||
    // 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('1');
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test'));
 | 
			
		||||
    $this->assertBigPipeResponseHeadersPresent();
 | 
			
		||||
    $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', '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,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Verifying there are no BigPipe placeholders & replacements.
 | 
			
		||||
    $this->assertSession()->responseHeaderEquals('BigPipe-Test-Placeholders', '<none>');
 | 
			
		||||
    // Verifying BigPipe start/stop signals are absent.
 | 
			
		||||
    $this->assertSession()->responseNotContains(BigPipe::START_SIGNAL);
 | 
			
		||||
    $this->assertSession()->responseNotContains(BigPipe::STOP_SIGNAL);
 | 
			
		||||
 | 
			
		||||
    // Verifying BigPipe assets are absent.
 | 
			
		||||
    $this->assertArrayNotHasKey('bigPipePlaceholderIds', $this->getDrupalSettings());
 | 
			
		||||
    $this->assertArrayNotHasKey('ajaxPageState', $this->getDrupalSettings());
 | 
			
		||||
    $this->assertSession()->responseContains('</body>');
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
    // Verifying BigPipe provides useful error output when an error occurs
 | 
			
		||||
    // while rendering a placeholder if verbose error logging is enabled.
 | 
			
		||||
    $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->assertSession()->pageTextContains('The website encountered an unexpected error. Try again later');
 | 
			
		||||
    $this->assertSession()->pageTextContains('You are not allowed to say llamas are not cool!');
 | 
			
		||||
    $this->assertSession()->responseNotContains('</body>');
 | 
			
		||||
    // The exception is expected. Do not interpret it as a test failure.
 | 
			
		||||
    unlink($this->root . '/' . $this->siteDirectory . '/error.log');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests BigPipe with a multi-occurrence placeholder.
 | 
			
		||||
   */
 | 
			
		||||
  public function testBigPipeMultiOccurrencePlaceholders(): void {
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
 | 
			
		||||
    // 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'));
 | 
			
		||||
    $this->assertSession()->pageTextContains('The count is 1.');
 | 
			
		||||
    $this->assertSession()->responseNotContains('The count is 2.');
 | 
			
		||||
    $this->assertSession()->responseNotContains('The count is 3.');
 | 
			
		||||
 | 
			
		||||
    // 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('1');
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
 | 
			
		||||
    $this->assertSession()->pageTextContains('The count is 1.');
 | 
			
		||||
    $this->assertSession()->responseNotContains('The count is 2.');
 | 
			
		||||
    $this->assertSession()->responseNotContains('The count is 3.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertBigPipeResponseHeadersPresent(): void {
 | 
			
		||||
    // Check that Cache-Control header set to "private".
 | 
			
		||||
    $this->assertSession()->responseHeaderContains('Cache-Control', 'private');
 | 
			
		||||
    $this->assertSession()->responseHeaderEquals('Surrogate-Control', 'no-store, content="BigPipe/1.0"');
 | 
			
		||||
    $this->assertSession()->responseHeaderEquals('X-Accel-Buffering', 'no');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_placeholders): void {
 | 
			
		||||
    $this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->getSession()->getResponseHeader('BigPipe-Test-No-Js-Placeholders'))));
 | 
			
		||||
    foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
 | 
			
		||||
      // Checking whether the replacement for the BigPipe no-JS placeholder
 | 
			
		||||
      // $big_pipe_nojs_placeholder is present.
 | 
			
		||||
      $this->assertSession()->responseNotContains($big_pipe_nojs_placeholder);
 | 
			
		||||
      if ($expected_replacement !== NULL) {
 | 
			
		||||
        $this->assertSession()->responseContains($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.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order): void {
 | 
			
		||||
    $this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->getSession()->getResponseHeader('BigPipe-Test-Placeholders')));
 | 
			
		||||
    $placeholder_positions = [];
 | 
			
		||||
    $placeholder_replacement_positions = [];
 | 
			
		||||
    foreach ($expected_big_pipe_placeholders as $big_pipe_placeholder_id => $expected_ajax_response) {
 | 
			
		||||
      // Verify expected placeholder.
 | 
			
		||||
      $expected_placeholder_html = '<span data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '">';
 | 
			
		||||
      $this->assertSession()->responseContains($expected_placeholder_html);
 | 
			
		||||
      $pos = strpos($this->getSession()->getPage()->getContent(), $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 . '">';
 | 
			
		||||
      $xpath = '//script[@data-big-pipe-replacement-for-placeholder-with-id="' . Html::decodeEntities($big_pipe_placeholder_id) . '"]';
 | 
			
		||||
      if ($expected_ajax_response === NULL) {
 | 
			
		||||
        $this->assertSession()->elementNotExists('xpath', $xpath);
 | 
			
		||||
        $this->assertSession()->responseNotContains($expected_placeholder_replacement);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $this->assertSession()->elementTextContains('xpath', $xpath, $expected_ajax_response);
 | 
			
		||||
      $this->assertSession()->responseContains($expected_placeholder_replacement);
 | 
			
		||||
      $pos = strpos($this->getSession()->getPage()->getContent(), $expected_placeholder_replacement);
 | 
			
		||||
      $placeholder_replacement_positions[$pos] = $big_pipe_placeholder_id;
 | 
			
		||||
    }
 | 
			
		||||
    ksort($placeholder_positions, SORT_NUMERIC);
 | 
			
		||||
    $this->assertEquals(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
 | 
			
		||||
    $placeholders = array_map(function (NodeElement $element) {
 | 
			
		||||
      return $element->getAttribute('data-big-pipe-placeholder-id');
 | 
			
		||||
    }, $this->cssSelect('[data-big-pipe-placeholder-id]'));
 | 
			
		||||
    $this->assertSameSize($expected_big_pipe_placeholders, 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->assertEquals($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->assertSame(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->getSession()->getPage()->getContent()));
 | 
			
		||||
 | 
			
		||||
    // Verifying BigPipe start/stop signals.
 | 
			
		||||
    $this->assertSession()->responseContains(BigPipe::START_SIGNAL);
 | 
			
		||||
    $this->assertSession()->responseContains(BigPipe::STOP_SIGNAL);
 | 
			
		||||
    $start_signal_position = strpos($this->getSession()->getPage()->getContent(), BigPipe::START_SIGNAL);
 | 
			
		||||
    $stop_signal_position = strpos($this->getSession()->getPage()->getContent(), BigPipe::STOP_SIGNAL);
 | 
			
		||||
    $this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
 | 
			
		||||
 | 
			
		||||
    // Verifying BigPipe placeholder replacements and start/stop signals were
 | 
			
		||||
    // streamed in the correct order.
 | 
			
		||||
    $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->assertEquals($expected_stream_order, array_values($actual_stream_order));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures CSRF tokens can be generated for the current user's session.
 | 
			
		||||
   */
 | 
			
		||||
  protected function setCsrfTokenSeedInTestEnvironment(): void {
 | 
			
		||||
    // Retrieve the CSRF token from the child site from its serialized session
 | 
			
		||||
    // record in the database.
 | 
			
		||||
    $session_data = $this->container->get('session_handler.write_safe')->read($this->getSession()->getCookie($this->getSessionName()));
 | 
			
		||||
    $csrf_token_seed = unserialize(explode('_sf2_meta|', $session_data)[1])['s'];
 | 
			
		||||
 | 
			
		||||
    // Ensure that the session is started before accessing a session bag.
 | 
			
		||||
    // Otherwise the value stored in the bag is lost when subsequent session
 | 
			
		||||
    // access triggers a session start automatically.
 | 
			
		||||
 | 
			
		||||
    /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
 | 
			
		||||
    $request_stack = $this->container->get('request_stack');
 | 
			
		||||
    $session = $request_stack->getSession();
 | 
			
		||||
    if (!$session->isStarted()) {
 | 
			
		||||
      $session->start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Store the CSRF token in the test runners session metadata bag.
 | 
			
		||||
    $this->container->get('session_manager.metadata_bag')->setCsrfTokenSeed($csrf_token_seed);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return \Drupal\big_pipe_test\BigPipePlaceholderTestCase[]
 | 
			
		||||
   *   An array of test cases.
 | 
			
		||||
   */
 | 
			
		||||
  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.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo This method is broken. Fix it in
 | 
			
		||||
   *   https://www.drupal.org/project/drupal/issues/3144926
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertSetsEqual(array $a, array $b): void {
 | 
			
		||||
    count($a) == count($b) && !array_diff_assoc($a, $b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts whether a BigPipe no-JS cookie exists or not.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertBigPipeNoJsCookieExists(string $expected): void {
 | 
			
		||||
    $this->assertCookieExists('big_pipe_nojs', $expected, 'BigPipe no-JS');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts whether a session cookie exists or not.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertSessionCookieExists(string $expected): void {
 | 
			
		||||
    $this->assertCookieExists($this->getSessionName(), $expected, 'Session');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts whether a cookie exists on the client or not.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertCookieExists(string $cookie_name, string $expected, string $cookie_label): void {
 | 
			
		||||
    $this->assertEquals($expected, !empty($this->getSession()->getCookie($cookie_name)), $expected ? "$cookie_label cookie exists." : "$cookie_label cookie does not exist.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calls ::performMetaRefresh() and asserts the responses.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertBigPipeNoJsMetaRefreshRedirect(): void {
 | 
			
		||||
    $original_url = $this->getSession()->getCurrentUrl();
 | 
			
		||||
 | 
			
		||||
    // Disable automatic following of redirects by the HTTP client, so that this
 | 
			
		||||
    // test can analyze the response headers of each redirect response.
 | 
			
		||||
    $this->getSession()->getDriver()->getClient()->followRedirects(FALSE);
 | 
			
		||||
    $this->performMetaRefresh();
 | 
			
		||||
    $headers[0] = $this->getSession()->getResponseHeaders();
 | 
			
		||||
    $statuses[0] = $this->getSession()->getStatusCode();
 | 
			
		||||
    $this->performMetaRefresh();
 | 
			
		||||
    $headers[1] = $this->getSession()->getResponseHeaders();
 | 
			
		||||
    $statuses[1] = $this->getSession()->getStatusCode();
 | 
			
		||||
    $this->getSession()->getDriver()->getClient()->followRedirects(TRUE);
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($original_url, $this->getSession()->getCurrentUrl(), 'Redirected back to the original location.');
 | 
			
		||||
 | 
			
		||||
    // First response: redirect.
 | 
			
		||||
    $this->assertEquals(302, $statuses[0], 'The first response was a 302 (redirect).');
 | 
			
		||||
    $this->assertStringStartsWith('big_pipe_nojs=1', $headers[0]['Set-Cookie'][0], 'The first response sets the big_pipe_nojs cookie.');
 | 
			
		||||
    $this->assertEquals($original_url, $headers[0]['Location'][0], 'The first response redirected back to the original page.');
 | 
			
		||||
    $this->assertEmpty(
 | 
			
		||||
      array_diff([
 | 
			
		||||
        'cookies:big_pipe_nojs',
 | 
			
		||||
        'session.exists',
 | 
			
		||||
      ], explode(' ', $headers[0]['X-Drupal-Cache-Contexts'][0])),
 | 
			
		||||
      '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->assertEquals(200, $statuses[1], 'The second response was a 200.');
 | 
			
		||||
    $this->assertEmpty(
 | 
			
		||||
      array_diff([
 | 
			
		||||
        'cookies:big_pipe_nojs',
 | 
			
		||||
        'session.exists',
 | 
			
		||||
      ], explode(' ', $headers[0]['X-Drupal-Cache-Contexts'][0])),
 | 
			
		||||
      'The second response varies by the "cookies:big_pipe_nojs" and "session.exists" cache contexts.'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals('no-store, content="BigPipe/1.0"', $headers[1]['Surrogate-Control'][0], 'The second response has a "Surrogate-Control" header.');
 | 
			
		||||
 | 
			
		||||
    // Check that the <meta> refresh is absent, only one redirect ever happens.
 | 
			
		||||
    $this->assertSession()->responseNotContains('<noscript><meta http-equiv="Refresh" content="0; URL=');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that response contains cacheability debug comments.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDebugCacheability(): void {
 | 
			
		||||
    $this->drupalLogin($this->rootUser);
 | 
			
		||||
    $this->assertSessionCookieExists('1');
 | 
			
		||||
    $this->assertBigPipeNoJsCookieExists('0');
 | 
			
		||||
 | 
			
		||||
    // With debug_cacheability_headers enabled.
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('<front>'));
 | 
			
		||||
    $this->assertBigPipeResponseHeadersPresent();
 | 
			
		||||
    $this->assertSession()->responseContains('<!-- big_pipe cache tags:  -->');
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->responseContains('<!-- big_pipe cache contexts: languages:language_interface theme user.permissions -->');
 | 
			
		||||
 | 
			
		||||
    // With debug_cacheability_headers disabled.
 | 
			
		||||
    $this->setContainerParameter('http.response.debug_cacheability_headers', FALSE);
 | 
			
		||||
    $this->rebuildContainer();
 | 
			
		||||
    $this->resetAll();
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('<front>'));
 | 
			
		||||
    $this->assertSession()->responseNotContains('<!-- big_pipe cache tags:');
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->responseNotContains('<!-- big_pipe cache contexts:');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generic module test for big_pipe.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class GenericTest extends GenericModuleTestBase {}
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\FunctionalJavascript;
 | 
			
		||||
 | 
			
		||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests placeholder preview functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class BigPipePreviewTest extends WebDriverTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'big_pipe',
 | 
			
		||||
    'user',
 | 
			
		||||
    'big_pipe_bypass_js',
 | 
			
		||||
    'big_pipe_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaultTheme = 'big_pipe_test_theme';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test preview functionality within placeholders.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLazyLoaderPreview(): void {
 | 
			
		||||
    $user = $this->drupalCreateUser([]);
 | 
			
		||||
    $display_name = $user->getDisplayName();
 | 
			
		||||
    $this->drupalLogin($user);
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet('big_pipe_test_preview');
 | 
			
		||||
 | 
			
		||||
    // This test begins with the big_pipe_bypass_js module enabled, which blocks
 | 
			
		||||
    // Big Pipe's JavaScript from loading. Without that JavaScript, the
 | 
			
		||||
    // placeholder and previews are not replaced and we can reliably test their
 | 
			
		||||
    // presence.
 | 
			
		||||
    $this->assertSession()->elementExists('css', '#placeholder-preview-twig-container [data-big-pipe-placeholder-id] > .i-am-taking-up-space');
 | 
			
		||||
    $this->assertSession()->elementTextEquals('css', '#placeholder-preview-twig-container [data-big-pipe-placeholder-id] > .i-am-taking-up-space', 'LOOK AT ME I AM CONSUMING SPACE FOR LATER');
 | 
			
		||||
    $this->assertSession()->elementTextNotContains('css', '#placeholder-preview-twig-container', $display_name);
 | 
			
		||||
 | 
			
		||||
    $this->assertSession()->pageTextContains('There is a lamb and there is a puppy');
 | 
			
		||||
    $this->assertSession()->elementTextEquals('css', '#placeholder-render-array-container [data-big-pipe-placeholder-id] > #render-array-preview', 'There is a lamb and there is a puppy');
 | 
			
		||||
    $this->assertSession()->elementTextNotContains('css', '#placeholder-render-array-container', 'Llamas forever!');
 | 
			
		||||
 | 
			
		||||
    // Uninstall big_pipe_bypass_js.
 | 
			
		||||
    \Drupal::service('module_installer')->uninstall(['big_pipe_bypass_js']);
 | 
			
		||||
    $this->rebuildAll();
 | 
			
		||||
    $this->drupalGet('big_pipe_test_preview');
 | 
			
		||||
    $this->assertSession()->waitForElementRemoved('css', '[data-big-pipe-placeholder-id]', 20000);
 | 
			
		||||
    $this->assertSession()->elementTextContains('css', '#placeholder-preview-twig-container', $display_name);
 | 
			
		||||
    $this->assertSession()->pageTextNotContains('LOOK AT ME I AM CONSUMING SPACE FOR LATER');
 | 
			
		||||
 | 
			
		||||
    $this->assertSession()->elementTextContains('css', '#placeholder-render-array-container marquee', 'Llamas forever!');
 | 
			
		||||
    $this->assertSession()->pageTextNotContains('There is a lamb and there is a puppy');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,187 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\FunctionalJavascript;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipe;
 | 
			
		||||
use Drupal\big_pipe_regression_test\BigPipeRegressionTestController;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BigPipe regression tests.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 * @group #slow
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeRegressionTest extends WebDriverTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'big_pipe',
 | 
			
		||||
    'big_pipe_messages_test',
 | 
			
		||||
    'big_pipe_regression_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaultTheme = 'stark';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    // Use the big_pipe_test_theme theme.
 | 
			
		||||
    $this->container->get('theme_installer')->install(['big_pipe_test_theme']);
 | 
			
		||||
    $this->container->get('config.factory')->getEditable('system.theme')->set('default', 'big_pipe_test_theme')->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure BigPipe works despite inline JS containing the string "</body>".
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/node/2678662
 | 
			
		||||
   */
 | 
			
		||||
  public function testMultipleClosingBodies_2678662(): void {
 | 
			
		||||
    $this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
 | 
			
		||||
 | 
			
		||||
    $this->drupalLogin($this->drupalCreateUser());
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_regression_test.2678662'));
 | 
			
		||||
 | 
			
		||||
    // Confirm that AJAX behaviors were instantiated, if not, this points to a
 | 
			
		||||
    // JavaScript syntax error and the JS variable has the appropriate content.
 | 
			
		||||
    $javascript = <<<JS
 | 
			
		||||
    (function(){
 | 
			
		||||
      return Object.keys(Drupal.ajax.instances).length > 0 && hitsTheFloor === "</body>";
 | 
			
		||||
    }())
 | 
			
		||||
JS;
 | 
			
		||||
    $this->assertJsCondition($javascript);
 | 
			
		||||
 | 
			
		||||
    // Besides verifying there is no JavaScript syntax error, also verify the
 | 
			
		||||
    // HTML structure.
 | 
			
		||||
    // The BigPipe stop signal is present just before the closing </body> and
 | 
			
		||||
    // </html> tags.
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->responseContains(BigPipe::STOP_SIGNAL . "\n\n\n</body></html>");
 | 
			
		||||
    $js_code_until_closing_body_tag = substr(BigPipeRegressionTestController::MARKER_2678662, 0, strpos(BigPipeRegressionTestController::MARKER_2678662, '</body>'));
 | 
			
		||||
    // The BigPipe start signal does NOT start at the closing </body> tag string
 | 
			
		||||
    // in an inline script.
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->responseNotContains($js_code_until_closing_body_tag . "\n" . BigPipe::START_SIGNAL);
 | 
			
		||||
    // But the inline script itself should not be altered.
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->responseContains(BigPipeRegressionTestController::MARKER_2678662);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure messages set in placeholders always appear.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/node/2712935
 | 
			
		||||
   */
 | 
			
		||||
  public function testMessages_2712935(): void {
 | 
			
		||||
    $this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
 | 
			
		||||
 | 
			
		||||
    $this->drupalLogin($this->drupalCreateUser());
 | 
			
		||||
    $messages_markup = '<div class="messages messages--status" role="status"';
 | 
			
		||||
    $test_routes = [
 | 
			
		||||
      // Messages placeholder rendered first.
 | 
			
		||||
      'render_placeholder_message_test.first',
 | 
			
		||||
      // Messages placeholder rendered after one, before another.
 | 
			
		||||
      'render_placeholder_message_test.middle',
 | 
			
		||||
      // Messages placeholder rendered last.
 | 
			
		||||
      'render_placeholder_message_test.last',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $assert = $this->assertSession();
 | 
			
		||||
    foreach ($test_routes as $route) {
 | 
			
		||||
      // Verify that we start off with zero messages queued.
 | 
			
		||||
      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
 | 
			
		||||
      $assert->responseNotContains($messages_markup);
 | 
			
		||||
 | 
			
		||||
      // Verify the test case at this route behaves as expected.
 | 
			
		||||
      $this->drupalGet(Url::fromRoute($route));
 | 
			
		||||
      $assert->elementContains('css', 'p.logged-message:nth-of-type(1)', 'Message: P1');
 | 
			
		||||
      $assert->elementContains('css', 'p.logged-message:nth-of-type(2)', 'Message: P2');
 | 
			
		||||
      $assert->responseContains($messages_markup);
 | 
			
		||||
      $assert->elementExists('css', 'div[aria-label="Status message"]');
 | 
			
		||||
      $assert->responseContains('aria-label="Status message">P1');
 | 
			
		||||
      $assert->responseContains('aria-label="Status message">P2');
 | 
			
		||||
 | 
			
		||||
      // Verify that we end with all messages printed, hence again zero queued.
 | 
			
		||||
      $this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
 | 
			
		||||
      $assert->responseNotContains($messages_markup);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests edge cases with placeholder HTML.
 | 
			
		||||
   */
 | 
			
		||||
  public function testPlaceholderHtmlEdgeCases(): void {
 | 
			
		||||
    $this->drupalLogin($this->drupalCreateUser());
 | 
			
		||||
    $this->doTestPlaceholderInParagraph_2802923();
 | 
			
		||||
    $this->doTestBigPipeLargeContent();
 | 
			
		||||
    $this->doTestMultipleReplacements();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure default BigPipe placeholder HTML cannot split paragraphs.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/node/2802923
 | 
			
		||||
   */
 | 
			
		||||
  protected function doTestPlaceholderInParagraph_2802923(): void {
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_regression_test.2802923'));
 | 
			
		||||
 | 
			
		||||
    $this->assertJsCondition('document.querySelectorAll(\'p\').length === 1');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests BigPipe large content.
 | 
			
		||||
   *
 | 
			
		||||
   * Repeat loading of same page for two times, after second time the page is
 | 
			
		||||
   * cached and the bug consistently reproducible.
 | 
			
		||||
   */
 | 
			
		||||
  public function doTestBigPipeLargeContent(): void {
 | 
			
		||||
    $assert_session = $this->assertSession();
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_large_content'));
 | 
			
		||||
    $this->assertNotNull($assert_session->waitForElement('css', 'script[data-big-pipe-event="stop"]'));
 | 
			
		||||
    $this->assertCount(0, $this->getDrupalSettings()['bigPipePlaceholderIds']);
 | 
			
		||||
    $this->assertCount(2, $this->getSession()->getPage()->findAll('css', 'script[data-big-pipe-replacement-for-placeholder-with-id]'));
 | 
			
		||||
    $assert_session->elementExists('css', '#big-pipe-large-content');
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_large_content'));
 | 
			
		||||
    $this->assertNotNull($assert_session->waitForElement('css', 'script[data-big-pipe-event="stop"]'));
 | 
			
		||||
    $this->assertCount(0, $this->getDrupalSettings()['bigPipePlaceholderIds']);
 | 
			
		||||
    $this->assertCount(2, $this->getSession()->getPage()->findAll('css', 'script[data-big-pipe-replacement-for-placeholder-with-id]'));
 | 
			
		||||
    $assert_session->elementExists('css', '#big-pipe-large-content');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test BigPipe replacement of multiple complex replacements.
 | 
			
		||||
   *
 | 
			
		||||
   * In some situations with either a large number of replacements or multiple
 | 
			
		||||
   * replacements involving complex operations, some replacements were not
 | 
			
		||||
   * completed. This is a simulation of such a situation by rendering a lot of
 | 
			
		||||
   * placeholders on a page.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/node/3390178
 | 
			
		||||
   */
 | 
			
		||||
  protected function doTestMultipleReplacements(): void {
 | 
			
		||||
    $user = $this->drupalCreateUser();
 | 
			
		||||
    $this->drupalLogin($user);
 | 
			
		||||
 | 
			
		||||
    $assert_session = $this->assertSession();
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet(Url::fromRoute('big_pipe_test_multiple_replacements'));
 | 
			
		||||
    $this->assertNotNull($assert_session->waitForElement('css', 'script[data-big-pipe-event="stop"]'));
 | 
			
		||||
    $this->assertCount(0, $this->getDrupalSettings()['bigPipePlaceholderIds']);
 | 
			
		||||
    $this->assertCount(0, $this->getSession()->getPage()->findAll('css', 'span[data-big-pipe-placeholder-id]'));
 | 
			
		||||
    $this->assertCount(BigPipeRegressionTestController::PLACEHOLDER_COUNT + 1, $this->getSession()->getPage()->findAll('css', 'script[data-big-pipe-replacement-for-placeholder-with-id]'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,92 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Kernel;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\block\Entity\Block;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the big_pipe_theme_suggestions_big_pipe_interface_preview() function.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeInterfacePreviewThemeSuggestionsTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['block', 'big_pipe', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block being tested.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\block\Entity\BlockInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $block;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $controller;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block view builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\block\BlockViewBuilder
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockViewBuilder;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->controller = $this->container
 | 
			
		||||
      ->get('entity_type.manager')
 | 
			
		||||
      ->getStorage('block');
 | 
			
		||||
 | 
			
		||||
    $this->blockViewBuilder = $this->container
 | 
			
		||||
      ->get('entity_type.manager')
 | 
			
		||||
      ->getViewBuilder('block');
 | 
			
		||||
 | 
			
		||||
    $this->container->get('theme_installer')->install(['stark']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests template suggestions from big_pipe_theme_suggestions_big_pipe_interface_preview().
 | 
			
		||||
   */
 | 
			
		||||
  public function testBigPipeThemeHookSuggestions(): void {
 | 
			
		||||
    $entity = $this->controller->create([
 | 
			
		||||
      'id' => 'test_block1',
 | 
			
		||||
      'theme' => 'stark',
 | 
			
		||||
      'plugin' => 'system_powered_by_block',
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
 | 
			
		||||
    // Test the rendering of a block.
 | 
			
		||||
    $block = Block::load('test_block1');
 | 
			
		||||
    // Using the BlockViewBuilder we will be able to get a lovely
 | 
			
		||||
    // #lazy_builder callback assigned.
 | 
			
		||||
    $build = $this->blockViewBuilder->view($block);
 | 
			
		||||
 | 
			
		||||
    $variables = [];
 | 
			
		||||
    // In turn this is what createBigPipeJsPlaceholder() uses to
 | 
			
		||||
    // build the BigPipe JS placeholder render array which is used as input
 | 
			
		||||
    // for big_pipe_theme_suggestions_big_pipe_interface_preview().
 | 
			
		||||
    $variables['callback'] = $build['#lazy_builder'][0];
 | 
			
		||||
    $variables['arguments'] = $build['#lazy_builder'][1];
 | 
			
		||||
    $suggestions = big_pipe_theme_suggestions_big_pipe_interface_preview($variables);
 | 
			
		||||
    $suggested_id = preg_replace('/[^a-zA-Z0-9]/', '_', $block->id());
 | 
			
		||||
    $this->assertSame([
 | 
			
		||||
      'big_pipe_interface_preview__block',
 | 
			
		||||
      'big_pipe_interface_preview__block__' . $suggested_id,
 | 
			
		||||
      'big_pipe_interface_preview__block__full',
 | 
			
		||||
    ], $suggestions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Kernel;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeResponse;
 | 
			
		||||
use Drupal\Core\Render\HtmlResponse;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that big_pipe responses can be serialized.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class SerializeResponseTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['big_pipe'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that big_pipe responses can be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   */
 | 
			
		||||
  public function testSerialize(): void {
 | 
			
		||||
    $response = new BigPipeResponse(new HtmlResponse());
 | 
			
		||||
    $this->assertIsString(serialize($response));
 | 
			
		||||
 | 
			
		||||
    // Checks that the response can be serialized after the big_pipe service is injected.
 | 
			
		||||
    $response->setBigPipeService($this->container->get('big_pipe'));
 | 
			
		||||
    $this->assertIsString(serialize($response));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,164 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
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\Language\LanguageManagerInterface;
 | 
			
		||||
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 Drupal\TestTools\Random;
 | 
			
		||||
use Prophecy\Argument;
 | 
			
		||||
use Prophecy\Prophecy\ObjectProphecy;
 | 
			
		||||
use Prophecy\Prophet;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class BigPipeResponseAttachmentsProcessorTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::processAttachments
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider nonHtmlResponseProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testNonHtmlResponse($response_class): void {
 | 
			
		||||
    $big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($this->prophesize(AttachmentsResponseProcessorInterface::class));
 | 
			
		||||
 | 
			
		||||
    $non_html_response = new $response_class();
 | 
			
		||||
    $this->expectException(\AssertionError::class);
 | 
			
		||||
    $big_pipe_response_attachments_processor->processAttachments($non_html_response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides data to testNonHtmlResponse().
 | 
			
		||||
   */
 | 
			
		||||
  public static function nonHtmlResponseProvider() {
 | 
			
		||||
    return [
 | 
			
		||||
      'AjaxResponse, which implements AttachmentsInterface' => [AjaxResponse::class],
 | 
			
		||||
      'A dummy that implements AttachmentsInterface' => [get_class((new Prophet())->prophesize(AttachmentsInterface::class)->reveal())],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::processAttachments
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider attachmentsProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testHtmlResponse(array $attachments): void {
 | 
			
		||||
    $big_pipe_response = new BigPipeResponse(new HtmlResponse('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.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides data to testHtmlResponse().
 | 
			
		||||
   */
 | 
			
		||||
  public static 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] = Random::machineName();
 | 
			
		||||
    }
 | 
			
		||||
    $random_attachments = ['random' . Random::machineName() => Random::machineName()];
 | 
			
		||||
    $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' => [Random::machineName()]];
 | 
			
		||||
    $big_pipe_nojs_placeholder_attachments = ['big_pipe_nojs_placeholders' => [Random::machineName()]];
 | 
			
		||||
    $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(),
 | 
			
		||||
      $this->prophesize(LanguageManagerInterface::class)->reveal()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,145 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Unit\Render;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipe;
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeResponse;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\Core\Render\ElementInfoManagerInterface;
 | 
			
		||||
use Drupal\Core\Render\HtmlResponse;
 | 
			
		||||
use Drupal\Core\Render\PlaceholderGeneratorInterface;
 | 
			
		||||
use Drupal\Core\Render\RenderCacheInterface;
 | 
			
		||||
use Drupal\Core\Render\Renderer;
 | 
			
		||||
use Drupal\Core\Routing\RequestContext;
 | 
			
		||||
use Drupal\Core\Security\TrustedCallbackInterface;
 | 
			
		||||
use Drupal\Core\Theme\ThemeManagerInterface;
 | 
			
		||||
use Drupal\Core\Utility\CallableResolver;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
use Prophecy\Argument;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
 | 
			
		||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\big_pipe\Render\BigPipe
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class FiberPlaceholderTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\big_pipe\Render\BigPipe::sendPlaceholders
 | 
			
		||||
   */
 | 
			
		||||
  public function testLongPlaceholderFiberSuspendingLoop(): void {
 | 
			
		||||
    $request_stack = $this->prophesize(RequestStack::class);
 | 
			
		||||
    $request_stack->getMainRequest()
 | 
			
		||||
      ->willReturn(new Request());
 | 
			
		||||
    $request_stack->getCurrentRequest()
 | 
			
		||||
      ->willReturn(new Request());
 | 
			
		||||
 | 
			
		||||
    $callableResolver = $this->prophesize(CallableResolver::class);
 | 
			
		||||
    $callableResolver->getCallableFromDefinition(Argument::any())
 | 
			
		||||
      ->willReturn([TurtleLazyBuilder::class, 'turtle']);
 | 
			
		||||
    $renderer = new Renderer(
 | 
			
		||||
      $callableResolver->reveal(),
 | 
			
		||||
      $this->prophesize(ThemeManagerInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(ElementInfoManagerInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(PlaceholderGeneratorInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(RenderCacheInterface::class)->reveal(),
 | 
			
		||||
      $request_stack->reveal(),
 | 
			
		||||
      [
 | 
			
		||||
        'required_cache_contexts' => [
 | 
			
		||||
          'languages:language_interface',
 | 
			
		||||
          'theme',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $session = $this->prophesize(SessionInterface::class);
 | 
			
		||||
    $session->start()->willReturn(TRUE);
 | 
			
		||||
 | 
			
		||||
    $bigpipe = new BigPipe(
 | 
			
		||||
      $renderer,
 | 
			
		||||
      $session->reveal(),
 | 
			
		||||
      $request_stack->reveal(),
 | 
			
		||||
      $this->prophesize(HttpKernelInterface::class)->reveal(),
 | 
			
		||||
      $this->createMock(EventDispatcherInterface::class),
 | 
			
		||||
      $this->prophesize(ConfigFactoryInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(MessengerInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(RequestContext::class)->reveal(),
 | 
			
		||||
      $this->prophesize(LoggerInterface::class)->reveal(),
 | 
			
		||||
    );
 | 
			
		||||
    $response = new BigPipeResponse(new HtmlResponse());
 | 
			
		||||
 | 
			
		||||
    $attachments = [
 | 
			
		||||
      'library' => [],
 | 
			
		||||
      'drupalSettings' => [
 | 
			
		||||
        'ajaxPageState' => [],
 | 
			
		||||
      ],
 | 
			
		||||
      'big_pipe_placeholders' => [
 | 
			
		||||
        // cspell:disable-next-line
 | 
			
		||||
        'callback=%5CDrupal%5CTests%5Cbig_pipe%5CUnit%5CRender%5CTurtleLazyBuilder%3A%3Aturtle&&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => [
 | 
			
		||||
          '#lazy_builder' => [
 | 
			
		||||
            '\Drupal\Tests\big_pipe\Unit\Render\TurtleLazyBuilder::turtle',
 | 
			
		||||
            [],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $response->setAttachments($attachments);
 | 
			
		||||
 | 
			
		||||
    // Construct minimal HTML response.
 | 
			
		||||
    // cspell:disable-next-line
 | 
			
		||||
    $content = '<html><body><span data-big-pipe-placeholder-id="callback=%5CDrupal%5CTests%5Cbig_pipe%5CUnit%5CRender%5CTurtleLazyBuilder%3A%3Aturtle&&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></body></html>';
 | 
			
		||||
    $response->setContent($content);
 | 
			
		||||
 | 
			
		||||
    // Capture the result to avoid PHPUnit complaining.
 | 
			
		||||
    ob_start();
 | 
			
		||||
    $fiber = new \Fiber(function () use ($bigpipe, $response) {
 | 
			
		||||
      $bigpipe->sendContent($response);
 | 
			
		||||
    });
 | 
			
		||||
    $fiber->start();
 | 
			
		||||
    $this->assertFalse($fiber->isTerminated(), 'Placeholder fibers with long execution time supposed to return control before terminating');
 | 
			
		||||
    ob_get_clean();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test class for testing fiber placeholders.
 | 
			
		||||
 */
 | 
			
		||||
class TurtleLazyBuilder implements TrustedCallbackInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render API callback: Suspends execution twice to simulate a long operation.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is assigned as a #lazy_builder callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The lazy builder callback.
 | 
			
		||||
   */
 | 
			
		||||
  public static function turtle(): array {
 | 
			
		||||
    if (\Fiber::getCurrent() !== NULL) {
 | 
			
		||||
      \Fiber::suspend();
 | 
			
		||||
    }
 | 
			
		||||
    if (\Fiber::getCurrent() !== NULL) {
 | 
			
		||||
      \Fiber::suspend();
 | 
			
		||||
    }
 | 
			
		||||
    return [
 | 
			
		||||
      '#markup' => '<span>Turtle is finally here. But how?</span>',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function trustedCallbacks() {
 | 
			
		||||
    return ['turtle'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Unit\Render;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipe;
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeResponse;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Messenger\MessengerInterface;
 | 
			
		||||
use Drupal\Core\Render\HtmlResponse;
 | 
			
		||||
use Drupal\Core\Render\RendererInterface;
 | 
			
		||||
use Drupal\Core\Routing\RequestContext;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\big_pipe\Render\BigPipe
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 */
 | 
			
		||||
class ManyPlaceholderTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders
 | 
			
		||||
   */
 | 
			
		||||
  public function testManyNoJsPlaceHolders(): void {
 | 
			
		||||
    $session = $this->prophesize(SessionInterface::class);
 | 
			
		||||
    $session->start()->willReturn(TRUE);
 | 
			
		||||
    $session->save()->shouldBeCalled();
 | 
			
		||||
    $bigpipe = new BigPipe(
 | 
			
		||||
      $this->prophesize(RendererInterface::class)->reveal(),
 | 
			
		||||
      $session->reveal(),
 | 
			
		||||
      $this->prophesize(RequestStack::class)->reveal(),
 | 
			
		||||
      $this->prophesize(HttpKernelInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(EventDispatcherInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(ConfigFactoryInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(MessengerInterface::class)->reveal(),
 | 
			
		||||
      $this->prophesize(RequestContext::class)->reveal(),
 | 
			
		||||
      $this->prophesize(LoggerInterface::class)->reveal(),
 | 
			
		||||
    );
 | 
			
		||||
    $response = new BigPipeResponse(new HtmlResponse());
 | 
			
		||||
 | 
			
		||||
    // Add many placeholders.
 | 
			
		||||
    $many_placeholders = [];
 | 
			
		||||
    for ($i = 0; $i < 400; $i++) {
 | 
			
		||||
      $many_placeholders[$this->randomMachineName(80)] = $this->randomMachineName(80);
 | 
			
		||||
    }
 | 
			
		||||
    $attachments = [
 | 
			
		||||
      'library' => [],
 | 
			
		||||
      'big_pipe_nojs_placeholders' => $many_placeholders,
 | 
			
		||||
    ];
 | 
			
		||||
    $response->setAttachments($attachments);
 | 
			
		||||
 | 
			
		||||
    // Construct minimal HTML response.
 | 
			
		||||
    $content = '<html><body>content<drupal-big-pipe-scripts-bottom-marker>script-bottom<drupal-big-pipe-scripts-bottom-marker></body></html>';
 | 
			
		||||
    $response->setContent($content);
 | 
			
		||||
 | 
			
		||||
    // Capture the result to avoid PHPUnit complaining.
 | 
			
		||||
    ob_start();
 | 
			
		||||
    $bigpipe->sendContent($response);
 | 
			
		||||
    $result = ob_get_clean();
 | 
			
		||||
 | 
			
		||||
    $this->assertNotEmpty($result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,183 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Unit\Render\Placeholder;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\Placeholder\BigPipeStrategy;
 | 
			
		||||
use Drupal\big_pipe_test\BigPipePlaceholderTestCases;
 | 
			
		||||
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, $method, $route_match_has_no_big_pipe_option, $request_has_session, $request_has_big_pipe_nojs_cookie, array $expected_big_pipe_placeholders): void {
 | 
			
		||||
    $request = new Request();
 | 
			
		||||
    $request->setMethod($method);
 | 
			
		||||
    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 ($request->isMethodCacheable() && !$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->assertCount(0, $processed_placeholders);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides the test data for testProcessPlaceholders().
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\big_pipe_test\BigPipePlaceholderTestCases
 | 
			
		||||
   */
 | 
			
		||||
  public static function placeholdersProvider() {
 | 
			
		||||
    $cases = 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,
 | 
			
		||||
        'GET',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe absent, no session, no-JS cookie present' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe present, no session, no-JS cookie absent' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe present, no session, no-JS cookie present' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe present, session, no-JS cookie absent' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe present, session, no-JS cookie present' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe absent, session, no-JS cookie absent: (JS-powered) BigPipe placeholder used for HTML placeholders' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        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 absent: (JS-powered) BigPipe placeholder used for HTML placeholders — but unsafe method' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'POST',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        FALSE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe absent, session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'GET',
 | 
			
		||||
        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,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '_no_big_pipe absent, session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders — but unsafe method' => [
 | 
			
		||||
        $placeholders,
 | 
			
		||||
        'POST',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\big_pipe\Unit\StackMiddleware;
 | 
			
		||||
 | 
			
		||||
use Drupal\big_pipe\Render\BigPipeResponse;
 | 
			
		||||
use Drupal\big_pipe\StackMiddleware\ContentLength;
 | 
			
		||||
use Drupal\Core\Render\HtmlResponse;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a test for ContentLength middleware.
 | 
			
		||||
 *
 | 
			
		||||
 * @group big_pipe
 | 
			
		||||
 * @coversDefaultClass \Drupal\big_pipe\StackMiddleware\ContentLength
 | 
			
		||||
 */
 | 
			
		||||
final class ContentLengthTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::handle
 | 
			
		||||
   * @dataProvider providerTestSetContentLengthHeader
 | 
			
		||||
   */
 | 
			
		||||
  public function testHandle(false|int $expected_header, Response $response): void {
 | 
			
		||||
    $kernel = $this->prophesize(HttpKernelInterface::class);
 | 
			
		||||
    $request = Request::create('/');
 | 
			
		||||
    $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, TRUE)->willReturn($response);
 | 
			
		||||
    $middleware = new ContentLength($kernel->reveal());
 | 
			
		||||
    $response = $middleware->handle($request);
 | 
			
		||||
    if ($expected_header === FALSE) {
 | 
			
		||||
      $this->assertFalse($response->headers->has('Content-Length'));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertSame((string) $expected_header, $response->headers->get('Content-Length'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides data for testHandle().
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerTestSetContentLengthHeader() {
 | 
			
		||||
    $response = new Response('Test content', 200);
 | 
			
		||||
    $response->headers->set('Content-Length', (string) strlen('Test content'));
 | 
			
		||||
    return [
 | 
			
		||||
      '200 ok' => [
 | 
			
		||||
        12,
 | 
			
		||||
        $response,
 | 
			
		||||
      ],
 | 
			
		||||
      'Big pipe' => [
 | 
			
		||||
        FALSE,
 | 
			
		||||
        new BigPipeResponse(new HtmlResponse('Test content')),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
name: 'BigPipe test theme'
 | 
			
		||||
type: theme
 | 
			
		||||
base theme: stable9
 | 
			
		||||
description: 'Theme for testing BigPipe edge cases.'
 | 
			
		||||
version: VERSION
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
<span class="i-am-taking-up-space">LOOK AT ME I AM CONSUMING SPACE FOR LATER</span>
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
{#
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Test that comments still work with the form above instead of below.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testCommentForm_2698811()
 | 
			
		||||
 */
 | 
			
		||||
#}
 | 
			
		||||
<section{{ attributes }}>
 | 
			
		||||
  {{ comment_form }}
 | 
			
		||||
 | 
			
		||||
  {{ comments }}
 | 
			
		||||
</section>
 | 
			
		||||
		Reference in New Issue
	
	Block a user