‪TYPO3CMS  ‪main
ResourceCompressorTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
23 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
24 
25 final class ‪ResourceCompressorTest extends UnitTestCase
26 {
27  protected bool ‪$backupEnvironment = true;
28 
29  public static function ‪cssFixStatementsDataProvider(): array
30  {
31  return [
32  'nothing to do - no charset/import/namespace' => [
33  'body { background: #ffffff; }',
34  'body { background: #ffffff; }',
35  ],
36  'import in front' => [
37  '@import url(http://www.example.com/css); body { background: #ffffff; }',
38  'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }',
39  ],
40  'import in back, without quotes' => [
41  'body { background: #ffffff; } @import url(http://www.example.com/css);',
42  'LF/* moved by compressor */LF@import url(http://www.example.com/css);LF/* moved by compressor */LFbody { background: #ffffff; }',
43  ],
44  'import in back, with double-quotes' => [
45  'body { background: #ffffff; } @import url("http://www.example.com/css");',
46  'LF/* moved by compressor */LF@import url("http://www.example.com/css");LF/* moved by compressor */LFbody { background: #ffffff; }',
47  ],
48  'import in back, with single-quotes' => [
49  'body { background: #ffffff; } @import url(\'http://www.example.com/css\');',
50  'LF/* moved by compressor */LF@import url(\'http://www.example.com/css\');LF/* moved by compressor */LFbody { background: #ffffff; }',
51  ],
52  'import in middle and back, without quotes' => [
53  'body { background: #ffffff; } @import url(http://www.example.com/A); div { background: #000; } @import url(http://www.example.com/B);',
54  '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; }',
55  ],
56  'charset declaration is unique' => [
57  'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @charset "UTF-8";',
58  '@charset "UTF-8";LF/* moved by compressor */LFbody { background: #ffffff; } div { background: #000; };',
59  ],
60  'order of charset, namespace and import is correct' => [
61  'body { background: #ffffff; } @charset "UTF-8"; div { background: #000; }; @import "file2.css"; @namespace url(http://www.w3.org/1999/xhtml);',
62  '@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; };',
63  ],
64  ];
65  }
66 
67  #[DataProvider('cssFixStatementsDataProvider')]
68  #[Test]
69  public function ‪cssFixStatementsMovesStatementsToTopIfNeeded(string $input, string $expected): void
70  {
71  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
72  $subject->_call('initialize');
73  $result = $subject->_call('cssFixStatements', $input);
74  $resultWithReadableLinefeed = str_replace(LF, 'LF', $result);
75  self::assertEquals($expected, $resultWithReadableLinefeed);
76  }
77 
78  #[Test]
80  {
81  $fileName = 'fooFile.css';
82  $compressedFileName = $fileName . '.gz';
83  $testFileFixture = [
84  $fileName => [
85  'file' => $fileName,
86  'compress' => true,
87  ],
88  ];
89  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
90  $subject->expects(self::once())
91  ->method('compressCssFile')
92  ->with($fileName)
93  ->willReturn($compressedFileName);
94 
95  $result = $subject->compressCssFiles($testFileFixture);
96 
97  self::assertArrayHasKey($compressedFileName, $result);
98  self::assertArrayHasKey('compress', $result[$compressedFileName]);
99  self::assertFalse($result[$compressedFileName]['compress']);
100  }
101 
102  #[Test]
104  {
105  $fileName = 'fooFile.js';
106  $compressedFileName = $fileName . '.gz';
107  $testFileFixture = [
108  $fileName => [
109  'file' => $fileName,
110  'compress' => true,
111  ],
112  ];
113  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
114  $subject->expects(self::once())
115  ->method('compressJsFile')
116  ->with($fileName)
117  ->willReturn($compressedFileName);
118 
119  $result = $subject->compressJsFiles($testFileFixture);
120 
121  self::assertArrayHasKey($compressedFileName, $result);
122  self::assertArrayHasKey('compress', $result[$compressedFileName]);
123  self::assertFalse($result[$compressedFileName]['compress']);
124  }
125 
126  #[Test]
128  {
129  $fileName = 'fooFile.css';
130  $concatenatedFileName = 'merged_' . $fileName;
131  $testFileFixture = [
132  $fileName => [
133  'file' => $fileName,
134  'excludeFromConcatenation' => false,
135  'media' => 'all',
136  ],
137  ];
138  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
139  $subject->expects(self::once())
140  ->method('createMergedCssFile')
141  ->willReturn($concatenatedFileName);
142 
143  $result = $subject->concatenateCssFiles($testFileFixture);
144 
145  self::assertArrayHasKey($concatenatedFileName, $result);
146  self::assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
147  self::assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
148  }
149 
150  #[Test]
152  {
153  $allFileName = 'allFile.css';
154  $screenFileName1 = 'screenFile.css';
155  $screenFileName2 = 'screenFile2.css';
156  $testFileFixture = [
157  $allFileName => [
158  'file' => $allFileName,
159  'excludeFromConcatenation' => false,
160  'media' => 'all',
161  ],
162  // use two screen files to check if they are merged into one, even with a different media type
163  $screenFileName1 => [
164  'file' => $screenFileName1,
165  'excludeFromConcatenation' => false,
166  'media' => 'screen',
167  ],
168  $screenFileName2 => [
169  'file' => $screenFileName2,
170  'excludeFromConcatenation' => false,
171  'media' => 'screen',
172  ],
173  ];
174  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
175  $subject->expects(self::exactly(2))
176  ->method('createMergedCssFile')
177  ->willReturn('merged_' . $allFileName, 'merged_' . $screenFileName1);
178 
179  $result = $subject->concatenateCssFiles($testFileFixture);
180 
181  self::assertEquals([
182  'merged_' . $allFileName,
183  'merged_' . $screenFileName1,
184  ], array_keys($result));
185  self::assertEquals('all', $result['merged_' . $allFileName]['media']);
186  self::assertEquals('screen', $result['merged_' . $screenFileName1]['media']);
187  }
188 
189  #[Test]
191  {
192  $screen1FileName = 'screen1File.css';
193  $screen2FileName = 'screen2File.css';
194  $screen3FileName = 'screen3File.css';
195  $testFileFixture = [
196  $screen1FileName => [
197  'file' => $screen1FileName,
198  'excludeFromConcatenation' => false,
199  'media' => 'screen',
200  ],
201  $screen2FileName => [
202  'file' => $screen2FileName,
203  'excludeFromConcatenation' => false,
204  'media' => 'screen',
205  ],
206  $screen3FileName => [
207  'file' => $screen3FileName,
208  'excludeFromConcatenation' => false,
209  'forceOnTop' => true,
210  'media' => 'screen',
211  ],
212  ];
213  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
214  // Replace mocked method getFilenameFromMainDir by passthrough callback
215  $subject->method('getFilenameFromMainDir')->willReturnArgument(0);
216  $subject->expects(self::once())
217  ->method('createMergedCssFile')
218  ->with(self::equalTo([$screen3FileName, $screen1FileName, $screen2FileName]));
219 
220  $subject->concatenateCssFiles($testFileFixture);
221  }
222 
223  #[Test]
225  {
226  $screen1FileName = 'screen1File.css';
227  $screen2FileName = 'screen2File.css';
228  $screen3FileName = 'screen3File.css';
229  $testFileFixture = [
230  $screen1FileName => [
231  'file' => $screen1FileName,
232  'excludeFromConcatenation' => false,
233  'media' => 'screen',
234  ],
235  $screen2FileName => [
236  'file' => $screen2FileName,
237  'excludeFromConcatenation' => true,
238  'media' => 'screen',
239  ],
240  $screen3FileName => [
241  'file' => $screen3FileName,
242  'excludeFromConcatenation' => false,
243  'media' => 'screen',
244  ],
245  ];
246  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
247  $subject->method('getFilenameFromMainDir')->willReturnArgument(0);
248  $subject->expects(self::once())
249  ->method('createMergedCssFile')
250  ->with(self::equalTo([$screen1FileName, $screen3FileName]))
251  ->willReturn('merged_screen');
252 
253  $result = $subject->concatenateCssFiles($testFileFixture);
254  self::assertEquals([
255  $screen2FileName,
256  'merged_screen',
257  ], array_keys($result));
258  self::assertEquals('screen', $result[$screen2FileName]['media']);
259  self::assertEquals('screen', $result['merged_screen']['media']);
260  }
261 
262  #[Test]
264  {
265  $fileName = 'fooFile.js';
266  $concatenatedFileName = 'merged_' . $fileName;
267  $testFileFixture = [
268  $fileName => [
269  'file' => $fileName,
270  'excludeFromConcatenation' => false,
271  'section' => 'top',
272  ],
273  ];
274  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory', 'getJavaScriptFileType']);
275  $subject->method('getJavaScriptFileType')->willReturn('');
276  $subject->expects(self::once())
277  ->method('createMergedJsFile')
278  ->willReturn($concatenatedFileName);
279 
280  $result = $subject->concatenateJsFiles($testFileFixture);
281 
282  self::assertArrayHasKey($concatenatedFileName, $result);
283  self::assertArrayHasKey('excludeFromConcatenation', $result[$concatenatedFileName]);
284  self::assertTrue($result[$concatenatedFileName]['excludeFromConcatenation']);
285  }
286 
287  public static function ‪concatenateJsFileAsyncDataProvider(): array
288  {
289  return [
290  'all files have no async' => [
291  [
292  [
293  'file' => 'file1.js',
294  'excludeFromConcatenation' => false,
295  'section' => 'top',
296  ],
297  [
298  'file' => 'file2.js',
299  'excludeFromConcatenation' => false,
300  'section' => 'top',
301  ],
302  ],
303  false,
304  ],
305  'all files have async false' => [
306  [
307  [
308  'file' => 'file1.js',
309  'excludeFromConcatenation' => false,
310  'section' => 'top',
311  'async' => false,
312  ],
313  [
314  'file' => 'file2.js',
315  'excludeFromConcatenation' => false,
316  'section' => 'top',
317  'async' => false,
318  ],
319  ],
320  false,
321  ],
322  'all files have async true' => [
323  [
324  [
325  'file' => 'file1.js',
326  'excludeFromConcatenation' => false,
327  'section' => 'top',
328  'async' => true,
329  ],
330  [
331  'file' => 'file2.js',
332  'excludeFromConcatenation' => false,
333  'section' => 'top',
334  'async' => true,
335  ],
336  ],
337  true,
338  ],
339  'one file async true and one file async false' => [
340  [
341  [
342  'file' => 'file1.js',
343  'excludeFromConcatenation' => false,
344  'section' => 'top',
345  'async' => true,
346  ],
347  [
348  'file' => 'file2.js',
349  'excludeFromConcatenation' => false,
350  'section' => 'top',
351  'async' => false,
352  ],
353  ],
354  false,
355  ],
356  'one file async true and one file async false but is excluded form concatenation' => [
357  [
358  [
359  'file' => 'file1.js',
360  'excludeFromConcatenation' => false,
361  'section' => 'top',
362  'async' => true,
363  ],
364  [
365  'file' => 'file2.js',
366  'excludeFromConcatenation' => true,
367  'section' => 'top',
368  'async' => false,
369  ],
370  ],
371  true,
372  ],
373  'one file async false and one file async true but is excluded form concatenation' => [
374  [
375  [
376  'file' => 'file1.js',
377  'excludeFromConcatenation' => false,
378  'section' => 'top',
379  'async' => false,
380  ],
381  [
382  'file' => 'file2.js',
383  'excludeFromConcatenation' => true,
384  'section' => 'top',
385  'async' => true,
386  ],
387  ],
388  false,
389  ],
390  ];
391  }
392 
393  #[DataProvider('concatenateJsFileAsyncDataProvider')]
394  #[Test]
395  public function ‪concatenateJsFileAddsAsyncPropertyIfAllFilesAreAsync(array $input, bool $expected): void
396  {
397  $concatenatedFileName = 'merged_foo.js';
398  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory', 'getJavaScriptFileType']);
399  $subject->method('getJavaScriptFileType')->willReturn('');
400  $subject->expects(self::once())
401  ->method('createMergedJsFile')
402  ->willReturn($concatenatedFileName);
403 
404  $result = $subject->concatenateJsFiles($input);
405 
406  self::assertSame($expected, $result[$concatenatedFileName]['async']);
407  }
408 
409  public static function ‪calcStatementsDataProvider(): array
410  {
411  return [
412  'simple calc' => [
413  'calc(100% - 3px)',
414  'calc(100% - 3px)',
415  ],
416  'complex calc with parentheses at the beginning' => [
417  'calc((100%/20) - 2*3px)',
418  'calc((100%/20) - 2*3px)',
419  ],
420  'complex calc with parentheses at the end' => [
421  'calc(100%/20 - 2*3px - (200px + 3%))',
422  'calc(100%/20 - 2*3px - (200px + 3%))',
423  ],
424  'complex calc with many parentheses' => [
425  'calc((100%/20) - (2 * (3px - (200px + 3%))))',
426  'calc((100%/20) - (2 * (3px - (200px + 3%))))',
427  ],
428  ];
429  }
430 
431  #[DataProvider('calcStatementsDataProvider')]
432  #[Test]
433  public function ‪calcFunctionMustRetainWhitespaces(string $input, string $expected): void
434  {
435  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
436  $subject->_call('initialize');
437  $result = $subject->_call('compressCssString', $input);
438  self::assertSame($expected, trim($result));
439  }
440 
441  #[Test]
443  {
444  $fileName = 'fooFile.js';
445  $concatenatedFileName = 'merged_' . $fileName;
446  $testFileFixture = [
447  $fileName => [
448  'file' => $fileName,
449  'nomodule' => true,
450  'section' => 'top',
451  ],
452  ];
453 
454  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
455  $result = $subject->concatenateJsFiles($testFileFixture);
456 
457  self::assertArrayNotHasKey($concatenatedFileName, $result);
458  self::assertTrue($result[$fileName]['nomodule']);
459  }
460 
461  #[Test]
462  public function ‪deferJavascriptIsNotConcatenated(): void
463  {
464  $fileName = 'fooFile.js';
465  $concatenatedFileName = 'merged_' . $fileName;
466  $testFileFixture = [
467  $fileName => [
468  'file' => $fileName,
469  'defer' => true,
470  'section' => 'top',
471  ],
472  ];
473 
474  $subject = $this->getAccessibleMock(ResourceCompressor::class, ['compressCssFile', 'compressJsFile', 'createMergedCssFile', 'createMergedJsFile', 'getFilenameFromMainDir', 'checkBaseDirectory']);
475  $result = $subject->concatenateJsFiles($testFileFixture);
476 
477  self::assertArrayNotHasKey($concatenatedFileName, $result);
478  self::assertTrue($result[$fileName]['defer']);
479  }
480 }
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\calcFunctionMustRetainWhitespaces
‪calcFunctionMustRetainWhitespaces(string $input, string $expected)
Definition: ResourceCompressorTest.php:433
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\$backupEnvironment
‪bool $backupEnvironment
Definition: ResourceCompressorTest.php:27
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenatedCssFilesObeyExcludeFromConcatenation
‪concatenatedCssFilesObeyExcludeFromConcatenation()
Definition: ResourceCompressorTest.php:224
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenatedCssFilesAreSeparatedByMediaType
‪concatenatedCssFilesAreSeparatedByMediaType()
Definition: ResourceCompressorTest.php:151
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\nomoduleJavascriptIsNotConcatenated
‪nomoduleJavascriptIsNotConcatenated()
Definition: ResourceCompressorTest.php:442
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest
Definition: ResourceCompressorTest.php:26
‪TYPO3\CMS\Core\Tests\Unit\Resource
Definition: AbstractFileTest.php:18
‪TYPO3\CMS\Core\Resource\ResourceCompressor
Definition: ResourceCompressor.php:31
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenateJsFileAddsAsyncPropertyIfAllFilesAreAsync
‪concatenateJsFileAddsAsyncPropertyIfAllFilesAreAsync(array $input, bool $expected)
Definition: ResourceCompressorTest.php:395
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenateJsFileIsFlaggedToNotConcatenateAgain
‪concatenateJsFileIsFlaggedToNotConcatenateAgain()
Definition: ResourceCompressorTest.php:263
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\deferJavascriptIsNotConcatenated
‪deferJavascriptIsNotConcatenated()
Definition: ResourceCompressorTest.php:462
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\compressedCssFileIsFlaggedToNotCompressAgain
‪compressedCssFileIsFlaggedToNotCompressAgain()
Definition: ResourceCompressorTest.php:79
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenateJsFileAsyncDataProvider
‪static concatenateJsFileAsyncDataProvider()
Definition: ResourceCompressorTest.php:287
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\calcStatementsDataProvider
‪static calcStatementsDataProvider()
Definition: ResourceCompressorTest.php:409
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\compressedJsFileIsFlaggedToNotCompressAgain
‪compressedJsFileIsFlaggedToNotCompressAgain()
Definition: ResourceCompressorTest.php:103
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\cssFixStatementsDataProvider
‪static cssFixStatementsDataProvider()
Definition: ResourceCompressorTest.php:29
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenatedCssFileIsFlaggedToNotConcatenateAgain
‪concatenatedCssFileIsFlaggedToNotConcatenateAgain()
Definition: ResourceCompressorTest.php:127
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\cssFixStatementsMovesStatementsToTopIfNeeded
‪cssFixStatementsMovesStatementsToTopIfNeeded(string $input, string $expected)
Definition: ResourceCompressorTest.php:69
‪TYPO3\CMS\Core\Tests\Unit\Resource\ResourceCompressorTest\concatenatedCssFilesObeyForceOnTopOption
‪concatenatedCssFilesObeyForceOnTopOption()
Definition: ResourceCompressorTest.php:190