[BUGFIX] Better positionName extraction in executePositionedStringInsertion
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Resource / ResourceCompressorTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Resource;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Resource\ResourceCompressor;
18
19 /**
20 * Testcase for the ResourceCompressor class
21 */
22 class ResourceCompressorTest extends BaseTestCase
23 {
24 /**
25 * @var ResourceCompressor|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
26 */
27 protected $subject;
28
29 /**
30 * Set up the test
31 */
32 protected function setUp()
33 {
34 parent::setUp();
35 $this->subject = $this->getAccessibleMock(ResourceCompressor::class, array('compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory'));
36 }
37
38 /**
39 * @return array
40 */
41 public function cssFixStatementsDataProvider()
42 {
43 return array(
44 'nothing to do - no charset/import/namespace' => array(
45 'body { background: #ffffff; }',
46 'body { background: #ffffff; }'
47 ),
48 'import in front' => array(
49 '@import url(http://www.example.com/css); body { background: #ffffff; }',
50 'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }'
51 ),
52 'import in back, without quotes' => array(
53 'body { background: #ffffff; } @import url(http://www.example.com/css);',
54 'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }'
55 ),
56 'import in back, with double-quotes' => array(
57 'body { background: #ffffff; } @import url("http://www.example.com/css");',
58 'LF/* moved by compressor */LF@import url("http://www.example.com/css");LF/* moved by compressor */LFbody { background: #ffffff; }'
59 ),
60 'import in back, with single-quotes' => array(
61 'body { background: #ffffff; } @import url(\'http://www.example.com/css\');',
62 'LF/* moved by compressor */LF@import url(\'http://www.example.com/css\');LF/* moved by compressor */LFbody { background: #ffffff; }'
63 ),
64 'import in middle and back, without quotes' => array(
65 'body { background: #ffffff; } @import url(http://www.example.com/A); div { background: #000; } @import url(http://www.example.com/B);',
66 'LF/* moved by compressor */LF@import url(http://www.example.com/A);@import url(http://www.example.com/B);LF/* moved by compressor */LFbody { background: #ffffff; } div { background: #000; }'
67 ),
68 'charset declaration is unique' => array(
69 'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @charset "UTF-8";',
70 '@charset "UTF-8";LF/* moved by compressor */LFbody { background: #ffffff; } div { background: #000; };'
71 ),
72 'order of charset, namespace and import is correct' => array(
73 'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @import "file2.css"; @namespace url(http://www.w3.org/1999/xhtml);',
74 '@charset "UTF-8";LF/* moved by compressor */LF@namespace url(http://www.w3.org/1999/xhtml);LF/* moved by compressor */LF@import "file2.css";LF/* moved by compressor */LFbody { background: #ffffff; } div { background: #000; };'
75 ),
76 );
77 }
78
79 /**
80 * @test
81 * @dataProvider cssFixStatementsDataProvider
82 * @param string $input
83 * @param string $expected
84 */
85 public function cssFixStatementsMovesStatementsToTopIfNeeded($input, $expected)
86 {
87 $result = $this->subject->_call('cssFixStatements', $input);
88 $resultWithReadableLinefeed = str_replace(LF, 'LF', $result);
89 $this->assertEquals($expected, $resultWithReadableLinefeed);
90 }
91
92 /**
93 * @test
94 */
95 public function compressedCssFileIsFlaggedToNotCompressAgain()
96 {
97 $fileName = 'fooFile.css';
98 $compressedFileName = $fileName . '.gzip';
99 $testFileFixture = array(
100 $fileName => array(
101 'file' => $fileName,
102 'compress' => true,
103 )
104 );
105 $this->subject->expects($this->once())
106 ->method('compressCssFile')
107 ->with($fileName)
108 ->will($this->returnValue($compressedFileName));
109
110 $result = $this->subject->compressCssFiles($testFileFixture);
111
112 $this->assertArrayHasKey($compressedFileName, $result);
113 $this->assertArrayHasKey('compress', $result[$compressedFileName]);
114 $this->assertFalse($result[$compressedFileName]['compress']);
115 }
116
117 /**
118 * @test
119 */
120 public function compressedJsFileIsFlaggedToNotCompressAgain()
121 {
122 $fileName = 'fooFile.js';
123 $compressedFileName = $fileName . '.gzip';
124 $testFileFixture = array(
125 $fileName => array(
126 'file' => $fileName,
127 'compress' => true,
128 )
129 );
130 $this->subject->expects($this->once())
131 ->method('compressJsFile')
132 ->with($fileName)
133 ->will($this->returnValue($compressedFileName));
134
135 $result = $this->subject->compressJsFiles($testFileFixture);
136
137 $this->assertArrayHasKey($compressedFileName, $result);
138 $this->assertArrayHasKey('compress', $result[$compressedFileName]);
139 $this->assertFalse($result[$compressedFileName]['compress']);
140 }
141
142 /**
143 * @test
144 */
145 public function concatenatedCssFileIsFlaggedToNotConcatenateAgain()
146 {
147 $fileName = 'fooFile.css';
148 $concatenatedFileName = 'merged_' . $fileName;
149 $testFileFixture = array(
150 $fileName => array(
151 'file' => $fileName,
152 'excludeFromConcatenation' => false,
153 'media' => 'all',
154 )
155 );
156 $this->subject->expects($this->once())
157 ->method('createMergedCssFile')
158 ->will($this->returnValue($concatenatedFileName));
159
160 $result = $this->subject->concatenateCssFiles($testFileFixture);
161
162 $this->assertArrayHasKey($concatenatedFileName, $result);
163 $this->assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
164 $this->assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
165 }
166
167 /**
168 * @test
169 */
170 public function concatenatedCssFilesAreSeparatedByMediaType()
171 {
172 $allFileName = 'allFile.css';
173 $screenFileName1 = 'screenFile.css';
174 $screenFileName2 = 'screenFile2.css';
175 $testFileFixture = array(
176 $allFileName => array(
177 'file' => $allFileName,
178 'excludeFromConcatenation' => false,
179 'media' => 'all',
180 ),
181 // use two screen files to check if they are merged into one, even with a different media type
182 $screenFileName1 => array(
183 'file' => $screenFileName1,
184 'excludeFromConcatenation' => false,
185 'media' => 'screen',
186 ),
187 $screenFileName2 => array(
188 'file' => $screenFileName2,
189 'excludeFromConcatenation' => false,
190 'media' => 'screen',
191 ),
192 );
193 $this->subject->expects($this->exactly(2))
194 ->method('createMergedCssFile')
195 ->will($this->onConsecutiveCalls(
196 $this->returnValue('merged_' . $allFileName),
197 $this->returnValue('merged_' . $screenFileName1)
198 ));
199
200 $result = $this->subject->concatenateCssFiles($testFileFixture);
201
202 $this->assertEquals(array(
203 'merged_' . $allFileName,
204 'merged_' . $screenFileName1
205 ), array_keys($result));
206 $this->assertEquals('all', $result['merged_' . $allFileName]['media']);
207 $this->assertEquals('screen', $result['merged_' . $screenFileName1]['media']);
208 }
209
210 /**
211 * @test
212 */
213 public function concatenatedCssFilesObeyForceOnTopOption()
214 {
215 $screen1FileName = 'screen1File.css';
216 $screen2FileName = 'screen2File.css';
217 $screen3FileName = 'screen3File.css';
218 $testFileFixture = array(
219 $screen1FileName => array(
220 'file' => $screen1FileName,
221 'excludeFromConcatenation' => false,
222 'media' => 'screen',
223 ),
224 $screen2FileName => array(
225 'file' => $screen2FileName,
226 'excludeFromConcatenation' => false,
227 'media' => 'screen',
228 ),
229 $screen3FileName => array(
230 'file' => $screen3FileName,
231 'excludeFromConcatenation' => false,
232 'forceOnTop' => true,
233 'media' => 'screen',
234 ),
235 );
236 // Replace mocked method getFilenameFromMainDir by passthrough callback
237 $this->subject->expects($this->any())->method('getFilenameFromMainDir')->willReturnArgument(0);
238 $this->subject->expects($this->once())
239 ->method('createMergedCssFile')
240 ->with($this->equalTo(array($screen3FileName, $screen1FileName, $screen2FileName)));
241
242 $this->subject->concatenateCssFiles($testFileFixture);
243 }
244
245 /**
246 * @test
247 */
248 public function concatenatedCssFilesObeyExcludeFromConcatenation()
249 {
250 $screen1FileName = 'screen1File.css';
251 $screen2FileName = 'screen2File.css';
252 $screen3FileName = 'screen3File.css';
253 $testFileFixture = array(
254 $screen1FileName => array(
255 'file' => $screen1FileName,
256 'excludeFromConcatenation' => false,
257 'media' => 'screen',
258 ),
259 $screen2FileName => array(
260 'file' => $screen2FileName,
261 'excludeFromConcatenation' => true,
262 'media' => 'screen',
263 ),
264 $screen3FileName => array(
265 'file' => $screen3FileName,
266 'excludeFromConcatenation' => false,
267 'media' => 'screen',
268 ),
269 );
270 $this->subject->expects($this->any())->method('getFilenameFromMainDir')->willReturnArgument(0);
271 $this->subject->expects($this->once())
272 ->method('createMergedCssFile')
273 ->with($this->equalTo(array($screen1FileName, $screen3FileName)))
274 ->will($this->returnValue('merged_screen'));
275
276 $result = $this->subject->concatenateCssFiles($testFileFixture);
277 $this->assertEquals(array(
278 $screen2FileName,
279 'merged_screen'
280 ), array_keys($result));
281 $this->assertEquals('screen', $result[$screen2FileName]['media']);
282 $this->assertEquals('screen', $result['merged_screen']['media']);
283 }
284
285 /**
286 * @test
287 */
288 public function concatenatedJsFileIsFlaggedToNotConcatenateAgain()
289 {
290 $fileName = 'fooFile.js';
291 $concatenatedFileName = 'merged_' . $fileName;
292 $testFileFixture = array(
293 $fileName => array(
294 'file' => $fileName,
295 'excludeFromConcatenation' => false,
296 'section' => 'top',
297 )
298 );
299 $this->subject->expects($this->once())
300 ->method('createMergedJsFile')
301 ->will($this->returnValue($concatenatedFileName));
302
303 $result = $this->subject->concatenateJsFiles($testFileFixture);
304
305 $this->assertArrayHasKey($concatenatedFileName, $result);
306 $this->assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
307 $this->assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
308 }
309
310 /**
311 * @return array
312 */
313 public function calcStatementsDataProvider()
314 {
315 return array(
316 'simple calc' => array(
317 'calc(100% - 3px)',
318 'calc(100% - 3px)',
319 ),
320 'complex calc with parentheses at the beginning' => array(
321 'calc((100%/20) - 2*3px)',
322 'calc((100%/20) - 2*3px)',
323 ),
324 'complex calc with parentheses at the end' => array(
325 'calc(100%/20 - 2*3px - (200px + 3%))',
326 'calc(100%/20 - 2*3px - (200px + 3%))',
327 ),
328 'complex calc with many parentheses' => array(
329 'calc((100%/20) - (2 * (3px - (200px + 3%))))',
330 'calc((100%/20) - (2 * (3px - (200px + 3%))))',
331 ),
332 );
333 }
334
335 /**
336 * @test
337 * @dataProvider calcStatementsDataProvider
338 * @param string $input
339 * @param string $expected
340 */
341 public function calcFunctionMustRetainWhitespaces($input, $expected)
342 {
343 $result = $this->subject->_call('compressCssString', $input);
344 $this->assertSame($expected, trim($result));
345 }
346
347 /**
348 * @return array
349 */
350 public function compressCssFileContentDataProvider()
351 {
352 $path = dirname(__FILE__) . '/ResourceCompressorTest/Fixtures/';
353 return array(
354 // File. Tests:
355 // - Stripped comments and white-space.
356 // - Retain white-space in selectors. (http://drupal.org/node/472820)
357 // - Retain pseudo-selectors. (http://drupal.org/node/460448)
358 0 => array(
359 $path . 'css_input_without_import.css',
360 $path . 'css_input_without_import.css.optimized.css'
361 ),
362 // File. Tests:
363 // - Retain comment hacks.
364 2 => array(
365 $path . 'comment_hacks.css',
366 $path . 'comment_hacks.css.optimized.css'
367 ),/*
368 // File. Tests:
369 // - Any @charset declaration at the beginning of a file should be
370 // removed without breaking subsequent CSS.*/
371 6 => array(
372 $path . 'charset_sameline.css',
373 $path . 'charset.css.optimized.css'
374 ),
375 7 => array(
376 $path . 'charset_newline.css',
377 $path . 'charset.css.optimized.css'
378 ),
379 );
380 }
381
382 /**
383 * Tests optimizing a CSS asset group.
384 *
385 * @test
386 * @dataProvider compressCssFileContentDataProvider
387 * @param string $cssFile
388 * @param string $expected
389 */
390 public function compressCssFileContent($cssFile, $expected)
391 {
392 $cssContent = file_get_contents($cssFile);
393 $compressedCss = $this->subject->_call('compressCssString', $cssContent);
394 // we have to fix relative paths, if we aren't working on a file in our target directory
395 $relativeFilename = str_replace(PATH_site, '', $cssFile);
396 if (strpos($relativeFilename, $this->subject->_get('targetDirectory')) === false) {
397 $compressedCss = $this->subject->_call('cssFixRelativeUrlPaths', $compressedCss, dirname($relativeFilename) . '/');
398 }
399 $this->assertEquals(file_get_contents($expected), $compressedCss, 'Group of file CSS assets optimized correctly.');
400 }
401 }