TYPO3 CMS  TYPO3_8-7
ResourceCompressorTest.php
Go to the documentation of this file.
1 <?php
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 
18 
23 {
28  protected $backupEnvironment = true;
29 
33  protected $subject;
34 
38  protected function setUp()
39  {
40  parent::setUp();
41  $this->subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
42  }
43 
47  public function cssFixStatementsDataProvider()
48  {
49  return [
50  'nothing to do - no charset/import/namespace' => [
51  'body { background: #ffffff; }',
52  'body { background: #ffffff; }'
53  ],
54  'import in front' => [
55  '@import url(http://www.example.com/css); body { background: #ffffff; }',
56  'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }'
57  ],
58  'import in back, without quotes' => [
59  'body { background: #ffffff; } @import url(http://www.example.com/css);',
60  'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }'
61  ],
62  'import in back, with double-quotes' => [
63  'body { background: #ffffff; } @import url("http://www.example.com/css");',
64  'LF/* moved by compressor */LF@import url("http://www.example.com/css");LF/* moved by compressor */LFbody { background: #ffffff; }'
65  ],
66  'import in back, with single-quotes' => [
67  'body { background: #ffffff; } @import url(\'http://www.example.com/css\');',
68  'LF/* moved by compressor */LF@import url(\'http://www.example.com/css\');LF/* moved by compressor */LFbody { background: #ffffff; }'
69  ],
70  'import in middle and back, without quotes' => [
71  'body { background: #ffffff; } @import url(http://www.example.com/A); div { background: #000; } @import url(http://www.example.com/B);',
72  '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; }'
73  ],
74  'charset declaration is unique' => [
75  'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @charset "UTF-8";',
76  '@charset "UTF-8";LF/* moved by compressor */LFbody { background: #ffffff; } div { background: #000; };'
77  ],
78  'order of charset, namespace and import is correct' => [
79  'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @import "file2.css"; @namespace url(http://www.w3.org/1999/xhtml);',
80  '@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; };'
81  ],
82  ];
83  }
84 
91  public function cssFixStatementsMovesStatementsToTopIfNeeded($input, $expected)
92  {
93  $result = $this->subject->_call('cssFixStatements', $input);
94  $resultWithReadableLinefeed = str_replace(LF, 'LF', $result);
95  $this->assertEquals($expected, $resultWithReadableLinefeed);
96  }
97 
102  {
103  $fileName = 'fooFile.css';
104  $compressedFileName = $fileName . '.gzip';
105  $testFileFixture = [
106  $fileName => [
107  'file' => $fileName,
108  'compress' => true,
109  ]
110  ];
111  $this->subject->expects($this->once())
112  ->method('compressCssFile')
113  ->with($fileName)
114  ->will($this->returnValue($compressedFileName));
115 
116  $result = $this->subject->compressCssFiles($testFileFixture);
117 
118  $this->assertArrayHasKey($compressedFileName, $result);
119  $this->assertArrayHasKey('compress', $result[$compressedFileName]);
120  $this->assertFalse($result[$compressedFileName]['compress']);
121  }
122 
127  {
128  $fileName = 'fooFile.js';
129  $compressedFileName = $fileName . '.gzip';
130  $testFileFixture = [
131  $fileName => [
132  'file' => $fileName,
133  'compress' => true,
134  ]
135  ];
136  $this->subject->expects($this->once())
137  ->method('compressJsFile')
138  ->with($fileName)
139  ->will($this->returnValue($compressedFileName));
140 
141  $result = $this->subject->compressJsFiles($testFileFixture);
142 
143  $this->assertArrayHasKey($compressedFileName, $result);
144  $this->assertArrayHasKey('compress', $result[$compressedFileName]);
145  $this->assertFalse($result[$compressedFileName]['compress']);
146  }
147 
152  {
153  $fileName = 'fooFile.css';
154  $concatenatedFileName = 'merged_' . $fileName;
155  $testFileFixture = [
156  $fileName => [
157  'file' => $fileName,
158  'excludeFromConcatenation' => false,
159  'media' => 'all',
160  ]
161  ];
162  $this->subject->expects($this->once())
163  ->method('createMergedCssFile')
164  ->will($this->returnValue($concatenatedFileName));
165 
166  $result = $this->subject->concatenateCssFiles($testFileFixture);
167 
168  $this->assertArrayHasKey($concatenatedFileName, $result);
169  $this->assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
170  $this->assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
171  }
172 
177  {
178  $allFileName = 'allFile.css';
179  $screenFileName1 = 'screenFile.css';
180  $screenFileName2 = 'screenFile2.css';
181  $testFileFixture = [
182  $allFileName => [
183  'file' => $allFileName,
184  'excludeFromConcatenation' => false,
185  'media' => 'all',
186  ],
187  // use two screen files to check if they are merged into one, even with a different media type
188  $screenFileName1 => [
189  'file' => $screenFileName1,
190  'excludeFromConcatenation' => false,
191  'media' => 'screen',
192  ],
193  $screenFileName2 => [
194  'file' => $screenFileName2,
195  'excludeFromConcatenation' => false,
196  'media' => 'screen',
197  ],
198  ];
199  $this->subject->expects($this->exactly(2))
200  ->method('createMergedCssFile')
201  ->will($this->onConsecutiveCalls(
202  $this->returnValue('merged_' . $allFileName),
203  $this->returnValue('merged_' . $screenFileName1)
204  ));
205 
206  $result = $this->subject->concatenateCssFiles($testFileFixture);
207 
208  $this->assertEquals([
209  'merged_' . $allFileName,
210  'merged_' . $screenFileName1
211  ], array_keys($result));
212  $this->assertEquals('all', $result['merged_' . $allFileName]['media']);
213  $this->assertEquals('screen', $result['merged_' . $screenFileName1]['media']);
214  }
215 
220  {
221  $screen1FileName = 'screen1File.css';
222  $screen2FileName = 'screen2File.css';
223  $screen3FileName = 'screen3File.css';
224  $testFileFixture = [
225  $screen1FileName => [
226  'file' => $screen1FileName,
227  'excludeFromConcatenation' => false,
228  'media' => 'screen',
229  ],
230  $screen2FileName => [
231  'file' => $screen2FileName,
232  'excludeFromConcatenation' => false,
233  'media' => 'screen',
234  ],
235  $screen3FileName => [
236  'file' => $screen3FileName,
237  'excludeFromConcatenation' => false,
238  'forceOnTop' => true,
239  'media' => 'screen',
240  ],
241  ];
242  // Replace mocked method getFilenameFromMainDir by passthrough callback
243  $this->subject->expects($this->any())->method('getFilenameFromMainDir')->willReturnArgument(0);
244  $this->subject->expects($this->once())
245  ->method('createMergedCssFile')
246  ->with($this->equalTo([$screen3FileName, $screen1FileName, $screen2FileName]));
247 
248  $this->subject->concatenateCssFiles($testFileFixture);
249  }
250 
255  {
256  $screen1FileName = 'screen1File.css';
257  $screen2FileName = 'screen2File.css';
258  $screen3FileName = 'screen3File.css';
259  $testFileFixture = [
260  $screen1FileName => [
261  'file' => $screen1FileName,
262  'excludeFromConcatenation' => false,
263  'media' => 'screen',
264  ],
265  $screen2FileName => [
266  'file' => $screen2FileName,
267  'excludeFromConcatenation' => true,
268  'media' => 'screen',
269  ],
270  $screen3FileName => [
271  'file' => $screen3FileName,
272  'excludeFromConcatenation' => false,
273  'media' => 'screen',
274  ],
275  ];
276  $this->subject->expects($this->any())->method('getFilenameFromMainDir')->willReturnArgument(0);
277  $this->subject->expects($this->once())
278  ->method('createMergedCssFile')
279  ->with($this->equalTo([$screen1FileName, $screen3FileName]))
280  ->will($this->returnValue('merged_screen'));
281 
282  $result = $this->subject->concatenateCssFiles($testFileFixture);
283  $this->assertEquals([
284  $screen2FileName,
285  'merged_screen'
286  ], array_keys($result));
287  $this->assertEquals('screen', $result[$screen2FileName]['media']);
288  $this->assertEquals('screen', $result['merged_screen']['media']);
289  }
290 
295  {
296  $fileName = 'fooFile.js';
297  $concatenatedFileName = 'merged_' . $fileName;
298  $testFileFixture = [
299  $fileName => [
300  'file' => $fileName,
301  'excludeFromConcatenation' => false,
302  'section' => 'top',
303  ]
304  ];
305  $this->subject->expects($this->once())
306  ->method('createMergedJsFile')
307  ->will($this->returnValue($concatenatedFileName));
308 
309  $result = $this->subject->concatenateJsFiles($testFileFixture);
310 
311  $this->assertArrayHasKey($concatenatedFileName, $result);
312  $this->assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
313  $this->assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
314  }
315 
319  public function calcStatementsDataProvider()
320  {
321  return [
322  'simple calc' => [
323  'calc(100% - 3px)',
324  'calc(100% - 3px)',
325  ],
326  'complex calc with parentheses at the beginning' => [
327  'calc((100%/20) - 2*3px)',
328  'calc((100%/20) - 2*3px)',
329  ],
330  'complex calc with parentheses at the end' => [
331  'calc(100%/20 - 2*3px - (200px + 3%))',
332  'calc(100%/20 - 2*3px - (200px + 3%))',
333  ],
334  'complex calc with many parentheses' => [
335  'calc((100%/20) - (2 * (3px - (200px + 3%))))',
336  'calc((100%/20) - (2 * (3px - (200px + 3%))))',
337  ],
338  ];
339  }
340 
347  public function calcFunctionMustRetainWhitespaces($input, $expected)
348  {
349  $result = $this->subject->_call('compressCssString', $input);
350  $this->assertSame($expected, trim($result));
351  }
352 
357  {
358  $path = __DIR__ . '/ResourceCompressorTest/Fixtures/';
359  return [
360  // File. Tests:
361  // - Stripped comments and white-space.
362  // - Retain white-space in selectors. (http://drupal.org/node/472820)
363  // - Retain pseudo-selectors. (http://drupal.org/node/460448)
364  0 => [
365  $path . 'css_input_without_import.css',
366  $path . 'css_input_without_import.css.optimized.css'
367  ],
368  // File. Tests:
369  // - Retain comment hacks.
370  2 => [
371  $path . 'comment_hacks.css',
372  $path . 'comment_hacks.css.optimized.css'
373  ], /*
374  // File. Tests:
375  // - Any @charset declaration at the beginning of a file should be
376  // removed without breaking subsequent CSS.*/
377  6 => [
378  $path . 'charset_sameline.css',
379  $path . 'charset.css.optimized.css'
380  ],
381  7 => [
382  $path . 'charset_newline.css',
383  $path . 'charset.css.optimized.css'
384  ],
385  ];
386  }
387 
396  public function compressCssFileContent($cssFile, $expected)
397  {
398  $cssContent = file_get_contents($cssFile);
399  $compressedCss = $this->subject->_call('compressCssString', $cssContent);
400  // we have to fix relative paths, if we aren't working on a file in our target directory
401  $relativeFilename = str_replace(PATH_site, '', $cssFile);
402  if (strpos($relativeFilename, $this->subject->_get('targetDirectory')) === false) {
403  $compressedCss = $this->subject->_call('cssFixRelativeUrlPaths', $compressedCss, dirname($relativeFilename) . '/');
404  }
405  $this->assertEquals(file_get_contents($expected), $compressedCss, 'Group of file CSS assets optimized correctly.');
406  }
407 
412  {
413  return [
414  // Get filename using EXT:
415  [
416  'EXT:core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css',
417  'sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css'
418  ],
419  // Get filename using relative path
420  [
421  'typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css',
422  'sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css'
423  ],
424  [
425  'sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css',
426  'sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css'
427  ],
428  [
429  'typo3temp/assets/compressed/.htaccess',
430  '../typo3temp/assets/compressed/.htaccess'
431  ],
432  ];
433  }
434 
441  public function getVariousFilenamesFromMainDirInBackendContext(string $filename, string $expected)
442  {
443  $rootPath = \dirname($_SERVER['SCRIPT_NAME']);
444  $this->subject = $this->getAccessibleMock(ResourceCompressor::class, ['dummy']);
445  $this->subject->setRootPath($rootPath . '/');
446 
447  $relativeToRootPath = $this->subject->_call('getFilenameFromMainDir', $filename);
448  $this->assertSame($expected, $relativeToRootPath, 'Path to the file relative to the path converted correctly.');
449  }
450 
455  {
456  return [
457  // Get filename using relative path
458  [
459  'typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css',
460  'typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorTest/Fixtures/charset.css'
461  ],
462  [
463  'typo3temp/assets/compressed/.htaccess',
464  'typo3temp/assets/compressed/.htaccess'
465  ],
466  ];
467  }
468 
475  public function getVariousFilenamesFromMainDirInFrontendContext(string $filename, string $expected)
476  {
477  $_SERVER['ORIG_SCRIPT_NAME'] = '/index.php';
478  $this->subject = $this->getAccessibleMock(ResourceCompressor::class, ['dummy']);
479  $this->subject->setRootPath(PATH_site);
480 
481  $relativeToRootPath = $this->subject->_call('getFilenameFromMainDir', $filename);
482  $this->assertSame($expected, $relativeToRootPath, 'Path to the file relative to the path converted correctly.');
483  }
484 }
getVariousFilenamesFromMainDirInBackendContext(string $filename, string $expected)
getVariousFilenamesFromMainDirInFrontendContext(string $filename, string $expected)