Skip to content

Commit e38b1a1

Browse files
committed
#27270 Further improvements to @krzkrz PR with changes from @sshymko, my change related to reattaching the plugin only to Layout and Pages (ancestor of Layout)
1 parent 9687370 commit e38b1a1

File tree

3 files changed

+69
-45
lines changed

3 files changed

+69
-45
lines changed

app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
namespace Magento\Theme\Controller\Result;
99

1010
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Framework\App\Response\HttpInterface as HttpResponseInterface;
12+
use Magento\Framework\App\ResponseInterface;
13+
use Magento\Framework\View\Result\Layout;
1114
use Magento\Store\Model\ScopeInterface;
12-
use Magento\Framework\App\Response\Http;
1315

1416
/**
1517
* Plugin for putting all JavaScript tags to the end of body.
@@ -34,26 +36,32 @@ public function __construct(ScopeConfigInterface $scopeConfig)
3436
/**
3537
* Moves all JavaScript tags to the end of body if this feature is enabled.
3638
*
37-
* @param Http $subject
38-
* @return void
39+
* @param Layout $subject
40+
* @param Layout $result
41+
* @param HttpResponseInterface|ResponseInterface $httpResponse
42+
* @return Layout (That should be void, actually)
43+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
3944
*/
40-
public function beforeSendResponse(Http $subject)
45+
public function afterRenderResult(Layout $subject, Layout $result, ResponseInterface $httpResponse)
4146
{
42-
$content = (string)$subject->getContent();
47+
if (!$this->isDeferEnabled()) {
48+
return $result;
49+
}
50+
51+
$content = (string)$httpResponse->getContent();
4352
$bodyEndTag = '</body';
44-
$isEndBodyTagFound = strpos($content, $bodyEndTag) !== false;
45-
$shouldMoveJsToBottom = $this->scopeConfig->isSetFlag(
46-
self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
47-
ScopeInterface::SCOPE_STORE
48-
);
53+
$bodyEndTagFound = strrpos($content, $bodyEndTag) !== false;
4954

50-
if ($isEndBodyTagFound && $shouldMoveJsToBottom) {
55+
if ($bodyEndTagFound) {
5156
$scripts = $this->extractScriptTags($content);
5257
if ($scripts) {
53-
$content = str_replace($bodyEndTag, "$scripts\n$bodyEndTag", $content);
54-
$subject->setContent($content);
58+
$newBodyEndTagPosition = strrpos($content, $bodyEndTag);
59+
$content = substr_replace($content, $scripts . "\n", $newBodyEndTagPosition, 0);
60+
$httpResponse->setContent($content);
5561
}
5662
}
63+
64+
return $result;
5765
}
5866

5967
/**
@@ -86,4 +94,17 @@ private function extractScriptTags(&$content): string
8694

8795
return $scripts;
8896
}
97+
98+
/**
99+
* Returns information whether moving JS to footer is enabled
100+
*
101+
* @return bool
102+
*/
103+
private function isDeferEnabled(): bool
104+
{
105+
return $this->scopeConfig->isSetFlag(
106+
self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
107+
ScopeInterface::SCOPE_STORE
108+
);
109+
}
89110
}

app/code/Magento/Theme/Test/Unit/Controller/Result/JsFooterPluginTest.php

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77

88
namespace Magento\Theme\Test\Unit\Controller\Result;
99

10-
use Magento\Theme\Controller\Result\JsFooterPlugin;
11-
use Magento\Framework\App\Response\Http;
12-
use PHPUnit\Framework\TestCase;
13-
use PHPUnit\Framework\MockObject\MockObject;
1410
use Magento\Framework\App\Config\ScopeConfigInterface;
15-
use Magento\Store\Model\ScopeInterface;
11+
use Magento\Framework\App\Response\Http;
1612
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
13+
use Magento\Framework\View\Result\Layout;
14+
use Magento\Store\Model\ScopeInterface;
15+
use Magento\Theme\Controller\Result\JsFooterPlugin;
16+
use PHPUnit\Framework\MockObject\MockObject;
17+
use PHPUnit\Framework\TestCase;
1718

1819
/**
1920
* Unit test for Magento\Theme\Test\Unit\Controller\Result\JsFooterPlugin.
@@ -22,21 +23,18 @@ class JsFooterPluginTest extends TestCase
2223
{
2324
const STUB_XML_PATH_DEV_MOVE_JS_TO_BOTTOM = 'dev/js/move_script_to_bottom';
2425

25-
/**
26-
* @var JsFooterPlugin
27-
*/
26+
/** @var JsFooterPlugin */
2827
private $plugin;
2928

30-
/**
31-
* @var ScopeConfigInterface|MockObject
32-
*/
29+
/** @var ScopeConfigInterface|MockObject */
3330
private $scopeConfigMock;
3431

35-
/**
36-
* @var Http|MockObject
37-
*/
32+
/** @var Http|MockObject */
3833
private $httpMock;
3934

35+
/** @var Layout|MockObject */
36+
private $layoutMock;
37+
4038
/**
4139
* @inheritdoc
4240
*/
@@ -48,6 +46,7 @@ protected function setUp(): void
4846
->getMockForAbstractClass();
4947

5048
$this->httpMock = $this->createMock(Http::class);
49+
$this->layoutMock = $this->createMock(Layout::class);
5150

5251
$objectManager = new ObjectManagerHelper($this);
5352
$this->plugin = $objectManager->getObject(
@@ -59,11 +58,11 @@ protected function setUp(): void
5958
}
6059

6160
/**
62-
* Data Provider for testBeforeSendResponse()
61+
* Data Provider for testAfterRenderResult()
6362
*
6463
* @return array
6564
*/
66-
public function sendResponseDataProvider(): array
65+
public function renderResultDataProvider(): array
6766
{
6867
return [
6968
'content_with_script_tag' => [
@@ -98,31 +97,29 @@ public function sendResponseDataProvider(): array
9897
* @param bool $isSetFlag
9998
* @param string $result
10099
* @return void
101-
* @dataProvider sendResponseDataProvider
100+
* @dataProvider renderResultDataProvider
102101
*/
103-
public function testBeforeSendResponse($content, $isSetFlag, $result): void
102+
public function testAfterRenderResult($content, $isSetFlag, $result): void
104103
{
105-
$this->httpMock->expects($this->once())
106-
->method('getContent')
104+
// Given (context)
105+
$this->httpMock->method('getContent')
107106
->willReturn($content);
108107

109-
$this->scopeConfigMock->expects($this->once())
110-
->method('isSetFlag')
111-
->with(
112-
self::STUB_XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
113-
ScopeInterface::SCOPE_STORE
114-
)
108+
$this->scopeConfigMock->method('isSetFlag')
109+
->with(self::STUB_XML_PATH_DEV_MOVE_JS_TO_BOTTOM, ScopeInterface::SCOPE_STORE)
115110
->willReturn($isSetFlag);
116111

112+
// Expects
117113
$this->httpMock->expects($this->any())
118114
->method('setContent')
119115
->with($result);
120116

121-
$this->plugin->beforeSendResponse($this->httpMock);
117+
// When
118+
$this->plugin->afterRenderResult($this->layoutMock, $this->layoutMock, $this->httpMock);
122119
}
123120

124121
/**
125-
* Data Provider for testBeforeSendResponseIfGetContentIsNotAString()
122+
* Data Provider for testAfterRenderResultIfGetContentIsNotAString()
126123
*
127124
* @return array
128125
*/
@@ -136,21 +133,25 @@ public function ifGetContentIsNotAStringDataProvider(): array
136133
}
137134

138135
/**
139-
* Test BeforeSendResponse if content is not a string
136+
* Test AfterRenderResult if content is not a string
140137
*
141138
* @param string $content
142139
* @return void
143140
* @dataProvider ifGetContentIsNotAStringDataProvider
144141
*/
145-
public function testBeforeSendResponseIfGetContentIsNotAString($content): void
142+
public function testAfterRenderResultIfGetContentIsNotAString($content): void
146143
{
144+
$this->scopeConfigMock->method('isSetFlag')
145+
->with(self::STUB_XML_PATH_DEV_MOVE_JS_TO_BOTTOM, ScopeInterface::SCOPE_STORE)
146+
->willReturn(true);
147+
147148
$this->httpMock->expects($this->once())
148149
->method('getContent')
149150
->willReturn($content);
150151

151152
$this->httpMock->expects($this->never())
152153
->method('setContent');
153154

154-
$this->plugin->beforeSendResponse($this->httpMock);
155+
$this->plugin->afterRenderResult($this->layoutMock, $this->layoutMock, $this->httpMock);
155156
}
156157
}

app/code/Magento/Theme/etc/frontend/di.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
<plugin name="result-messages" type="Magento\Theme\Controller\Result\MessagePlugin"/>
2828
</type>
2929
<type name="Magento\Framework\App\Response\Http">
30-
<plugin name="result-js-footer" type="Magento\Theme\Controller\Result\JsFooterPlugin"/>
3130
<plugin name="asyncCssLoad" type="Magento\Theme\Controller\Result\AsyncCssPlugin"/>
3231
</type>
32+
<type name="Magento\Framework\View\Result\Layout">
33+
<plugin name="result-js-footer" type="Magento\Theme\Controller\Result\JsFooterPlugin"/>
34+
</type>
3335
<type name="Magento\Theme\Block\Html\Header\CriticalCss">
3436
<arguments>
3537
<argument name="filePath" xsi:type="string">css/critical.css</argument>

0 commit comments

Comments
 (0)