1<?php 2 3namespace Drupal\Tests\big_pipe\Unit\Render; 4 5use Drupal\big_pipe\Render\BigPipeResponse; 6use Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor; 7use Drupal\Core\Ajax\AjaxResponse; 8use Drupal\Core\Asset\AssetCollectionRendererInterface; 9use Drupal\Core\Asset\AssetResolverInterface; 10use Drupal\Core\Config\ConfigFactoryInterface; 11use Drupal\Core\Extension\ModuleHandlerInterface; 12use Drupal\Core\Render\AttachmentsInterface; 13use Drupal\Core\Render\AttachmentsResponseProcessorInterface; 14use Drupal\Core\Render\HtmlResponse; 15use Drupal\Core\Render\RendererInterface; 16use Drupal\Tests\UnitTestCase; 17use Prophecy\Argument; 18use Prophecy\Prophecy\ObjectProphecy; 19use Symfony\Component\HttpFoundation\RequestStack; 20 21/** 22 * @coversDefaultClass \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor 23 * @group big_pipe 24 */ 25class BigPipeResponseAttachmentsProcessorTest extends UnitTestCase { 26 27 /** 28 * @covers ::processAttachments 29 * 30 * @dataProvider nonHtmlResponseProvider 31 */ 32 public function testNonHtmlResponse($response_class) { 33 $big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($this->prophesize(AttachmentsResponseProcessorInterface::class)); 34 35 $non_html_response = new $response_class(); 36 $this->expectException(\AssertionError::class); 37 $big_pipe_response_attachments_processor->processAttachments($non_html_response); 38 } 39 40 public function nonHtmlResponseProvider() { 41 return [ 42 'AjaxResponse, which implements AttachmentsInterface' => [AjaxResponse::class], 43 'A dummy that implements AttachmentsInterface' => [get_class($this->prophesize(AttachmentsInterface::class)->reveal())], 44 ]; 45 } 46 47 /** 48 * @covers ::processAttachments 49 * 50 * @dataProvider attachmentsProvider 51 */ 52 public function testHtmlResponse(array $attachments) { 53 $big_pipe_response = new BigPipeResponse(new HtmlResponse('original')); 54 $big_pipe_response->setAttachments($attachments); 55 56 // This mock is the main expectation of this test: verify that the decorated 57 // service (that is this mock) never receives BigPipe placeholder 58 // attachments, because it doesn't know (nor should it) how to handle them. 59 $html_response_attachments_processor = $this->prophesize(AttachmentsResponseProcessorInterface::class); 60 $html_response_attachments_processor->processAttachments(Argument::that(function ($response) { 61 return $response instanceof HtmlResponse && empty(array_intersect(['big_pipe_placeholders', 'big_pipe_nojs_placeholders'], array_keys($response->getAttachments()))); 62 })) 63 ->will(function ($args) { 64 /** @var \Symfony\Component\HttpFoundation\Response|\Drupal\Core\Render\AttachmentsInterface $response */ 65 $response = $args[0]; 66 // Simulate its actual behavior. 67 $attachments = array_diff_key($response->getAttachments(), ['html_response_attachment_placeholders' => TRUE]); 68 $response->setContent('processed'); 69 $response->setAttachments($attachments); 70 return $response; 71 }) 72 ->shouldBeCalled(); 73 74 $big_pipe_response_attachments_processor = $this->createBigPipeResponseAttachmentsProcessor($html_response_attachments_processor); 75 $processed_big_pipe_response = $big_pipe_response_attachments_processor->processAttachments($big_pipe_response); 76 77 // The secondary expectation of this test: the original (passed in) response 78 // object remains unchanged, the processed (returned) response object has 79 // the expected values. 80 $this->assertSame($attachments, $big_pipe_response->getAttachments(), 'Attachments of original response object MUST NOT be changed.'); 81 $this->assertEquals('original', $big_pipe_response->getContent(), 'Content of original response object MUST NOT be changed.'); 82 $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.'); 83 $this->assertEquals('processed', $processed_big_pipe_response->getContent(), 'Content of returned (processed) response object MUST be changed.'); 84 } 85 86 public function attachmentsProvider() { 87 $typical_cases = [ 88 'no attachments' => [[]], 89 'libraries' => [['library' => ['core/drupal']]], 90 'libraries + drupalSettings' => [['library' => ['core/drupal'], 'drupalSettings' => ['foo' => 'bar']]], 91 ]; 92 93 $official_attachment_types = ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'placeholders', 'drupalSettings', 'html_response_attachment_placeholders']; 94 $official_attachments_with_random_values = []; 95 foreach ($official_attachment_types as $type) { 96 $official_attachments_with_random_values[$type] = $this->randomMachineName(); 97 } 98 $random_attachments = ['random' . $this->randomMachineName() => $this->randomMachineName()]; 99 $edge_cases = [ 100 '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], 101 'random attachment type (unofficial), with random assigned value, to prove BigPipeResponseAttachmentsProcessor is a perfect decorator' => [$random_attachments], 102 ]; 103 104 $big_pipe_placeholder_attachments = ['big_pipe_placeholders' => [$this->randomMachineName()]]; 105 $big_pipe_nojs_placeholder_attachments = ['big_pipe_nojs_placeholders' => [$this->randomMachineName()]]; 106 $big_pipe_cases = [ 107 'only big_pipe_placeholders' => [$big_pipe_placeholder_attachments], 108 'only big_pipe_nojs_placeholders' => [$big_pipe_nojs_placeholder_attachments], 109 'big_pipe_placeholders + big_pipe_nojs_placeholders' => [$big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments], 110 ]; 111 112 $combined_cases = [ 113 '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], 114 'random attachment types + big_pipe_placeholders + big_pipe_nojs_placeholders' => [$random_attachments + $big_pipe_placeholder_attachments + $big_pipe_nojs_placeholder_attachments], 115 ]; 116 117 return $typical_cases + $edge_cases + $big_pipe_cases + $combined_cases; 118 } 119 120 /** 121 * Creates a BigPipeResponseAttachmentsProcessor with mostly dummies. 122 * 123 * @param \Prophecy\Prophecy\ObjectProphecy $decorated_html_response_attachments_processor 124 * An object prophecy implementing AttachmentsResponseProcessorInterface. 125 * 126 * @return \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor 127 * The BigPipeResponseAttachmentsProcessor to test. 128 */ 129 protected function createBigPipeResponseAttachmentsProcessor(ObjectProphecy $decorated_html_response_attachments_processor) { 130 return new BigPipeResponseAttachmentsProcessor( 131 $decorated_html_response_attachments_processor->reveal(), 132 $this->prophesize(AssetResolverInterface::class)->reveal(), 133 $this->prophesize(ConfigFactoryInterface::class)->reveal(), 134 $this->prophesize(AssetCollectionRendererInterface::class)->reveal(), 135 $this->prophesize(AssetCollectionRendererInterface::class)->reveal(), 136 $this->prophesize(RequestStack::class)->reveal(), 137 $this->prophesize(RendererInterface::class)->reveal(), 138 $this->prophesize(ModuleHandlerInterface::class)->reveal() 139 ); 140 } 141 142} 143