--- /dev/null
+<?php
+
+namespace Drupal\Tests\big_pipe\Unit\Render;
+
+use Drupal\big_pipe\Render\BigPipeResponse;
+use Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Asset\AssetCollectionRendererInterface;
+use Drupal\Core\Asset\AssetResolverInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * @coversDefaultClass \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
+ * @group big_pipe
+ */
+class BigPipeResponseAttachmentsProcessorTest extends UnitTestCase {
+
+ /**
+ * @covers ::processAttachments
+ *
+ * @dataProvider nonHtmlResponseProvider
+ */
+ public function testNonHtmlResponse($response_class) {
+ $big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($this->prophesize(AttachmentsResponseProcessorInterface::class));
+
+ $non_html_response = new $response_class();
+ $this->setExpectedException(\AssertionError::class);
+ $big_pipe_response_attachments_processor->processAttachments($non_html_response);
+ }
+
+ public function nonHtmlResponseProvider() {
+ return [
+ 'AjaxResponse, which implements AttachmentsInterface' => [AjaxResponse::class],
+ 'A dummy that implements AttachmentsInterface' => [get_class($this->prophesize(AttachmentsInterface::class)->reveal())],
+ ];
+ }
+
+ /**
+ * @covers ::processAttachments
+ *
+ * @dataProvider attachmentsProvider
+ */
+ public function testHtmlResponse(array $attachments) {
+ $big_pipe_response = new BigPipeResponse(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.');
+ }
+
+ public function attachmentsProvider() {
+ $typical_cases = [
+ 'no attachments' => [[]],
+ 'libraries' => [['library' => ['core/drupal']]],
+ 'libraries + drupalSettings' => [['library' => ['core/drupal'], 'drupalSettings' => ['foo' => 'bar']]],
+ ];
+
+ $official_attachment_types = ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'placeholders', 'drupalSettings', 'html_response_attachment_placeholders'];
+ $official_attachments_with_random_values = [];
+ foreach ($official_attachment_types as $type) {
+ $official_attachments_with_random_values[$type] = $this->randomMachineName();
+ }
+ $random_attachments = ['random' . $this->randomMachineName() => $this->randomMachineName()];
+ $edge_cases = [
+ 'all official attachment types, with random assigned values, even if technically not valid, to prove BigPipeResponseAttachmentsProcessor is a perfect decorator' => [$official_attachments_with_random_values],
+ 'random attachment type (unofficial), with random assigned value, to prove BigPipeResponseAttachmentsProcessor is a perfect decorator' => [$random_attachments],
+ ];
+
+ $big_pipe_placeholder_attachments = ['big_pipe_placeholders' => $this->randomMachineName()];
+ $big_pipe_nojs_placeholder_attachments = ['big_pipe_nojs_placeholders' => $this->randomMachineName()];
+ $big_pipe_cases = [
+ 'only big_pipe_placeholders' => [$big_pipe_placeholder_attachments],
+ 'only big_pipe_nojs_placeholders' => [$big_pipe_nojs_placeholder_attachments],
+ 'big_pipe_placeholders + big_pipe_nojs_placeholders' => [$big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
+ ];
+
+ $combined_cases = [
+ 'all official attachment types + big_pipe_placeholders + big_pipe_nojs_placeholders' => [$official_attachments_with_random_values + $big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
+ 'random attachment types + big_pipe_placeholders + big_pipe_nojs_placeholders' => [$random_attachments + $big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments],
+ ];
+
+ return $typical_cases + $edge_cases + $big_pipe_cases + $combined_cases;
+ }
+
+ /**
+ * Creates a BigPipeResponseAttachmentsProcessor with mostly dummies.
+ *
+ * @param \Prophecy\Prophecy\ObjectProphecy $decorated_html_response_attachments_processor
+ * An object prophecy implementing AttachmentsResponseProcessorInterface.
+ *
+ * @return \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
+ * The BigPipeResponseAttachmentsProcessor to test.
+ */
+ protected function createBigPipeResponseAttachmentsProcessor(ObjectProphecy $decorated_html_response_attachments_processor) {
+ return new BigPipeResponseAttachmentsProcessor(
+ $decorated_html_response_attachments_processor->reveal(),
+ $this->prophesize(AssetResolverInterface::class)->reveal(),
+ $this->prophesize(ConfigFactoryInterface::class)->reveal(),
+ $this->prophesize(AssetCollectionRendererInterface::class)->reveal(),
+ $this->prophesize(AssetCollectionRendererInterface::class)->reveal(),
+ $this->prophesize(RequestStack::class)->reveal(),
+ $this->prophesize(RendererInterface::class)->reveal(),
+ $this->prophesize(ModuleHandlerInterface::class)->reveal()
+ );
+ }
+
+}