8c77839e0c1cbb84a0452205694b240402061aea
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Imaging / IconFactoryTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Imaging;
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 Prophecy\Argument;
18 use TYPO3\CMS\Core\Imaging\Icon;
19 use TYPO3\CMS\Core\Imaging\IconFactory;
20 use TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider;
21 use TYPO3\CMS\Core\Resource\File;
22
23 /**
24 * TestCase for \TYPO3\CMS\Core\Imaging\IconFactory
25 */
26 class IconFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
27 {
28 /**
29 * Subject is not notice free, disable E_NOTICES
30 */
31 protected static $suppressNotices = true;
32
33 /**
34 * @var \TYPO3\CMS\Core\Imaging\IconFactory
35 */
36 protected $subject = null;
37
38 /**
39 * @var string
40 */
41 protected $notRegisteredIconIdentifier = 'my-super-unregistered-identifier';
42
43 /**
44 * @var string
45 */
46 protected $registeredIconIdentifier = 'actions-close';
47
48 /**
49 * @var string
50 */
51 protected $registeredSpinningIconIdentifier = 'spinning-icon';
52
53 /**
54 * @var \TYPO3\CMS\Core\Imaging\IconRegistry
55 */
56 protected $iconRegistryMock;
57
58 /**
59 * @var array Simulate a tt_content record
60 */
61 protected $mockRecord = [
62 'header' => 'dummy content header',
63 'uid' => '1',
64 'pid' => '1',
65 'image' => '',
66 'hidden' => '0',
67 'starttime' => '0',
68 'endtime' => '0',
69 'fe_group' => '',
70 'CType' => 'text',
71 't3ver_id' => '0',
72 't3ver_state' => '0',
73 't3ver_wsid' => '0',
74 'sys_language_uid' => '0',
75 'l18n_parent' => '0',
76 'subheader' => '',
77 'bodytext' => '',
78 ];
79
80 /**
81 * Set up
82 */
83 protected function setUp()
84 {
85 $this->iconRegistryMock = $this->prophesize(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
86 $this->subject = new IconFactory($this->iconRegistryMock->reveal());
87
88 $this->iconRegistryMock->isRegistered('tcarecords--default')->willReturn(false);
89 $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(true);
90 $this->iconRegistryMock->isDeprecated(Argument::any())->willReturn(false);
91 $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
92 $this->iconRegistryMock->getIconIdentifierForMimeType('application/pdf')->willReturn('mimetypes-pdf');
93 $this->iconRegistryMock->getIconIdentifierForMimeType('image/*')->willReturn('mimetypes-media-image');
94 $this->iconRegistryMock->getIconIdentifierForMimeType(Argument::any())->willReturn(null);
95 $this->iconRegistryMock->getIconIdentifierForFileExtension(Argument::any())->willReturn('mimetypes-other-other');
96 $this->iconRegistryMock->getIconIdentifierForFileExtension('foo')->willReturn('mimetypes-other-other');
97 $this->iconRegistryMock->getIconIdentifierForFileExtension('pdf')->willReturn('mimetypes-pdf');
98 $this->iconRegistryMock->getIconIdentifierForFileExtension('png')->willReturn('mimetypes-media-image');
99 $this->iconRegistryMock->getIconConfigurationByIdentifier(Argument::any())->willReturn([
100 'provider' => FontawesomeIconProvider::class,
101 'options' => [
102 'name' => 'times',
103 'additionalClasses' => 'fa-fw'
104 ]
105 ]);
106 }
107
108 /**
109 * DataProvider for icon sizes
110 *
111 * @return array
112 */
113 public function differentSizesDataProvider()
114 {
115 return [
116 ['size ' . Icon::SIZE_SMALL => ['input' => Icon::SIZE_SMALL, 'expected' => Icon::SIZE_SMALL]],
117 ['size ' . Icon::SIZE_DEFAULT => ['input' => Icon::SIZE_DEFAULT, 'expected' => Icon::SIZE_DEFAULT]],
118 ['size ' . Icon::SIZE_LARGE => ['input' => Icon::SIZE_LARGE, 'expected' => Icon::SIZE_LARGE]]
119 ];
120 }
121
122 /**
123 * @test
124 */
125 public function getIconReturnsIconWithCorrectMarkupWrapperIfRegisteredIconIdentifierIsUsed()
126 {
127 $this->assertContains(
128 '<span class="icon-markup">',
129 $this->subject->getIcon($this->registeredIconIdentifier)->render()
130 );
131 }
132
133 /**
134 * @test
135 */
136 public function getIconByIdentifierReturnsIconWithCorrectMarkupIfRegisteredIconIdentifierIsUsed()
137 {
138 $this->assertContains(
139 '<span class="t3js-icon icon icon-size-default icon-state-default icon-actions-close" data-identifier="actions-close">',
140 $this->subject->getIcon($this->registeredIconIdentifier)->render()
141 );
142 }
143
144 /**
145 * @test
146 * @dataProvider differentSizesDataProvider
147 */
148 public function getIconByIdentifierAndSizeReturnsIconWithCorrectMarkupIfRegisteredIconIdentifierIsUsed($size)
149 {
150 $this->assertContains(
151 '<span class="t3js-icon icon icon-size-' . $size['expected'] . ' icon-state-default icon-actions-close" data-identifier="actions-close">',
152 $this->subject->getIcon($this->registeredIconIdentifier, $size['input'])->render()
153 );
154 }
155
156 /**
157 * @test
158 * @dataProvider differentSizesDataProvider
159 */
160 public function getIconByIdentifierAndSizeAndWithOverlayReturnsIconWithCorrectOverlayMarkupIfRegisteredIconIdentifierIsUsed($size)
161 {
162 $this->assertContains(
163 '<span class="icon-overlay icon-overlay-readonly">',
164 $this->subject->getIcon($this->registeredIconIdentifier, $size['input'], 'overlay-readonly')->render()
165 );
166 }
167
168 /**
169 * @test
170 */
171 public function getIconReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed()
172 {
173 $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(false);
174 $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
175 $this->iconRegistryMock->getIconConfigurationByIdentifier('default-not-found')->willReturn([
176 'provider' => FontawesomeIconProvider::class,
177 'options' => [
178 'name' => 'times-circle',
179 'additionalClasses' => 'fa-fw'
180 ]
181 ]);
182 $this->assertContains(
183 '<span class="t3js-icon icon icon-size-default icon-state-default icon-default-not-found" data-identifier="default-not-found">',
184 $this->subject->getIcon($this->notRegisteredIconIdentifier)->render()
185 );
186 }
187
188 /**
189 * @test
190 * @dataProvider differentSizesDataProvider
191 */
192 public function getIconByIdentifierAndSizeReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed($size)
193 {
194 $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(false);
195 $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
196 $this->iconRegistryMock->getIconConfigurationByIdentifier('default-not-found')->willReturn([
197 'provider' => FontawesomeIconProvider::class,
198 'options' => [
199 'name' => 'times-circle',
200 'additionalClasses' => 'fa-fw'
201 ]
202 ]);
203 $this->assertContains(
204 '<span class="t3js-icon icon icon-size-' . $size['expected'] . ' icon-state-default icon-default-not-found" data-identifier="default-not-found">',
205 $this->subject->getIcon($this->notRegisteredIconIdentifier, $size['input'])->render()
206 );
207 }
208
209 /**
210 * @test
211 */
212 public function getIconReturnsCorrectMarkupIfIconIsRegisteredAsSpinningIcon()
213 {
214 $this->iconRegistryMock->getIconConfigurationByIdentifier($this->registeredSpinningIconIdentifier)->willReturn([
215 'provider' => FontawesomeIconProvider::class,
216 'options' => [
217 'name' => 'times-circle',
218 'additionalClasses' => 'fa-fw',
219 'spinning' => true
220 ]
221 ]);
222 $this->assertContains(
223 '<span class="t3js-icon icon icon-size-default icon-state-default icon-' . $this->registeredSpinningIconIdentifier . ' icon-spin" data-identifier="spinning-icon">',
224 $this->subject->getIcon($this->registeredSpinningIconIdentifier)->render()
225 );
226 }
227
228 /**
229 * @test
230 * @dataProvider differentSizesDataProvider
231 * @param string $size
232 */
233 public function getIconByIdentifierAndSizeAndOverlayReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed($size)
234 {
235 $this->assertContains(
236 '<span class="icon-overlay icon-overlay-readonly">',
237 $this->subject->getIcon($this->notRegisteredIconIdentifier, $size['input'], 'overlay-readonly')->render()
238 );
239 }
240
241 /**
242 * @test
243 */
244 public function getIconThrowsExceptionIfInvalidSizeIsGiven()
245 {
246 $this->expectException(\InvalidArgumentException::class);
247 $this->subject->getIcon($this->registeredIconIdentifier, 'foo')->render();
248 }
249
250 //
251 // Tests for getIconForFileExtension
252 //
253
254 /**
255 * Tests the return of an icon for a file without extension
256 *
257 * @test
258 */
259 public function getIconForFileWithNoFileTypeReturnsDefaultFileIcon()
260 {
261 $this->assertContains(
262 '<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-other-other" data-identifier="mimetypes-other-other">',
263 $this->subject->getIconForFileExtension('')->render()
264 );
265 }
266
267 /**
268 * Tests the return of an icon for an unknown file type
269 *
270 * @test
271 */
272 public function getIconForFileWithUnknownFileTypeReturnsDefaultFileIcon()
273 {
274 $this->assertContains(
275 '<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-other-other" data-identifier="mimetypes-other-other">',
276 $this->subject->getIconForFileExtension('foo')->render()
277 );
278 }
279
280 /**
281 * Tests the return of an icon for a file with extension pdf
282 *
283 * @test
284 */
285 public function getIconForFileWithFileTypePdfReturnsPdfIcon()
286 {
287 $this->assertContains(
288 '<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-pdf" data-identifier="mimetypes-pdf">',
289 $this->subject->getIconForFileExtension('pdf')->render()
290 );
291 }
292
293 /**
294 * Tests the return of an icon for a file with extension png
295 *
296 * @test
297 */
298 public function getIconForFileWithFileTypePngReturnsPngIcon()
299 {
300 $this->assertContains(
301 '<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-media-image" data-identifier="mimetypes-media-image">',
302 $this->subject->getIconForFileExtension('png')->render()
303 );
304 }
305
306 /**
307 * @test
308 */
309 public function getIconForResourceReturnsCorrectMarkupForFileResources()
310 {
311 $resourceProphecy = $this->prophesize(File::class);
312 $resourceProphecy->isMissing()->willReturn(false);
313 $resourceProphecy->getExtension()->willReturn('pdf');
314 $resourceProphecy->getMimeType()->willReturn('');
315
316 $this->assertContains(
317 '<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-pdf" data-identifier="mimetypes-pdf">',
318 $this->subject->getIconForResource($resourceProphecy->reveal())->render()
319 );
320 }
321
322 //////////////////////////////////////////////
323 // Tests concerning getIconForResource
324 //////////////////////////////////////////////
325 /**
326 * Tests the returns of no file
327 *
328 * @test
329 */
330 public function getIconForResourceWithFileWithoutExtensionTypeReturnsOtherIcon()
331 {
332 $fileObject = $this->getTestSubjectFileObject('');
333 $result = $this->subject->getIconForResource($fileObject)->render();
334 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-other-other" data-identifier="mimetypes-other-other">', $result);
335 }
336
337 /**
338 * Tests the returns of unknown file
339 *
340 * @test
341 */
342 public function getIconForResourceWithUnknownFileTypeReturnsOtherIcon()
343 {
344 $fileObject = $this->getTestSubjectFileObject('foo');
345 $result = $this->subject->getIconForResource($fileObject)->render();
346 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-other-other" data-identifier="mimetypes-other-other">', $result);
347 }
348
349 /**
350 * Tests the returns of file pdf
351 *
352 * @test
353 */
354 public function getIconForResourceWithPdfReturnsPdfIcon()
355 {
356 $fileObject = $this->getTestSubjectFileObject('pdf');
357 $result = $this->subject->getIconForResource($fileObject)->render();
358 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-pdf" data-identifier="mimetypes-pdf">', $result);
359 }
360
361 /**
362 * Tests the returns of file pdf with known mime-type
363 *
364 * @test
365 */
366 public function getIconForResourceWithMimeTypeApplicationPdfReturnsPdfIcon()
367 {
368 $fileObject = $this->getTestSubjectFileObject('pdf', 'application/pdf');
369 $result = $this->subject->getIconForResource($fileObject)->render();
370 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-pdf" data-identifier="mimetypes-pdf">', $result);
371 }
372
373 /**
374 * Tests the returns of file with custom image mime-type
375 *
376 * @test
377 */
378 public function getIconForResourceWithCustomImageMimeTypeReturnsImageIcon()
379 {
380 $fileObject = $this->getTestSubjectFileObject('custom', 'image/my-custom-extension');
381 $result = $this->subject->getIconForResource($fileObject)->render();
382 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-media-image" data-identifier="mimetypes-media-image">', $result);
383 }
384
385 /**
386 * Tests the returns of file png
387 *
388 * @test
389 */
390 public function getIconForResourceWithPngFileReturnsIcon()
391 {
392 $fileObject = $this->getTestSubjectFileObject('png', 'image/png');
393 $result = $this->subject->getIconForResource($fileObject)->render();
394 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-media-image" data-identifier="mimetypes-media-image">', $result);
395 }
396
397 /**
398 * Tests the returns of normal folder
399 *
400 * @test
401 */
402 public function getIconForResourceWithFolderReturnsFolderIcon()
403 {
404 $folderObject = $this->getTestSubjectFolderObject('/test');
405 $result = $this->subject->getIconForResource($folderObject)->render();
406 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-apps-filetree-folder-default" data-identifier="apps-filetree-folder-default">', $result);
407 }
408
409 /**
410 * Tests the returns of open folder
411 *
412 * @test
413 */
414 public function getIconForResourceWithOpenFolderReturnsOpenFolderIcon()
415 {
416 $folderObject = $this->getTestSubjectFolderObject('/test');
417 $result = $this->subject->getIconForResource($folderObject, Icon::SIZE_DEFAULT, null, ['folder-open' => true])->render();
418 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-apps-filetree-folder-opened" data-identifier="apps-filetree-folder-opened">', $result);
419 }
420
421 /**
422 * Tests the returns of root folder
423 *
424 * @test
425 */
426 public function getIconForResourceWithRootFolderReturnsRootFolderIcon()
427 {
428 $folderObject = $this->getTestSubjectFolderObject('/');
429 $result = $this->subject->getIconForResource($folderObject)->render();
430 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-apps-filetree-root" data-identifier="apps-filetree-root">', $result);
431 }
432
433 /**
434 * Tests the returns of mount root
435 *
436 * @test
437 */
438 public function getIconForResourceWithMountRootReturnsMountFolderIcon()
439 {
440 $folderObject = $this->getTestSubjectFolderObject('/mount');
441 $result = $this->subject->getIconForResource($folderObject, Icon::SIZE_DEFAULT, null, ['mount-root' => true])->render();
442 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-apps-filetree-mount" data-identifier="apps-filetree-mount">', $result);
443 }
444
445 //
446 // Test for getIconForRecord
447 //
448
449 /**
450 * Tests the returns of NULL table + empty array
451 *
452 * @test
453 */
454 public function getIconForRecordWithNullTableReturnsMissingIcon()
455 {
456 $this->assertContains(
457 '<span class="t3js-icon icon icon-size-default icon-state-default icon-default-not-found" data-identifier="default-not-found">',
458 $this->subject->getIconForRecord('', [])->render()
459 );
460 }
461
462 /**
463 * Tests the returns of tt_content + empty record
464 *
465 * @test
466 */
467 public function getIconForRecordWithEmptyRecordReturnsNormalIcon()
468 {
469 $GLOBALS['TCA'] = [
470 'tt_content' => [
471 'ctrl' => [
472 'typeicon_column' => 'CType',
473 'typeicon_classes' => [
474 'default' => 'mimetypes-x-content-text',
475 ],
476 ],
477 ],
478 ];
479 $result = $this->subject->getIconForRecord('tt_content', [])->render();
480 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-x-content-text" data-identifier="mimetypes-x-content-text">', $result);
481 }
482
483 /**
484 * Tests the returns of tt_content + mock record
485 *
486 * @test
487 */
488 public function getIconForRecordWithMockRecordReturnsNormalIcon()
489 {
490 $GLOBALS['TCA'] = [
491 'tt_content' => [
492 'ctrl' => [
493 'typeicon_column' => 'CType',
494 'typeicon_classes' => [
495 'text' => 'mimetypes-x-content-text',
496 ],
497 ],
498 ],
499 ];
500 $result = $this->subject->getIconForRecord('tt_content', $this->mockRecord)->render();
501 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-x-content-text" data-identifier="mimetypes-x-content-text">', $result);
502 }
503
504 /**
505 * Tests the returns of tt_content + mock record of type 'list' (aka plugin)
506 *
507 * @test
508 */
509 public function getIconForRecordWithMockRecordOfTypePluginReturnsPluginIcon()
510 {
511 $GLOBALS['TCA'] = [
512 'tt_content' => [
513 'ctrl' => [
514 'typeicon_column' => 'CType',
515 'typeicon_classes' => [
516 'list' => 'mimetypes-x-content-plugin',
517 ],
518 ],
519 ],
520 ];
521 $mockRecord = $this->mockRecord;
522 $mockRecord['CType'] = 'list';
523 $result = $this->subject->getIconForRecord('tt_content', $mockRecord)->render();
524 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-x-content-plugin" data-identifier="mimetypes-x-content-plugin">', $result);
525 }
526
527 /**
528 * Tests the returns of tt_content + mock record with hidden flag
529 *
530 * @test
531 */
532 public function getIconForRecordWithMockRecordWithHiddenFlagReturnsNormalIconAndOverlay()
533 {
534 $GLOBALS['TCA'] = [
535 'tt_content' => [
536 'ctrl' => [
537 'enablecolumns' => [
538 'disabled' => 'hidden',
539 ],
540 'typeicon_column' => 'CType',
541 'typeicon_classes' => [
542 'text' => 'mimetypes-x-content-text',
543 ],
544 ],
545 ],
546 ];
547 $mockRecord = $this->mockRecord;
548 $mockRecord['hidden'] = '1';
549 $result = $this->subject->getIconForRecord('tt_content', $mockRecord)->render();
550 $this->assertContains('<span class="t3js-icon icon icon-size-default icon-state-default icon-mimetypes-x-content-text" data-identifier="mimetypes-x-content-text">', $result);
551 $this->assertContains('<span class="icon-overlay icon-overlay-hidden">', $result);
552 }
553
554 /**
555 * Create file object to use as test subject
556 *
557 * @param string $extension
558 * @param string $mimeType
559 * @return \TYPO3\CMS\Core\Resource\File
560 */
561 protected function getTestSubjectFileObject($extension, $mimeType = '')
562 {
563 $mockedStorage = $this->createMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class);
564 $mockedFile = $this->getMockBuilder(\TYPO3\CMS\Core\Resource\File::class)
565 ->setConstructorArgs([[], $mockedStorage])
566 ->getMock();
567 $mockedFile->expects($this->atMost(1))->method('getExtension')->will($this->returnValue($extension));
568 $mockedFile->expects($this->atLeastOnce())->method('getMimeType')->will($this->returnValue($mimeType));
569 return $mockedFile;
570 }
571
572 /**
573 * Create folder object to use as test subject
574 *
575 * @param string $identifier
576 * @return \TYPO3\CMS\Core\Resource\Folder
577 */
578 protected function getTestSubjectFolderObject($identifier)
579 {
580 $mockedStorage = $this->createMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class);
581 $mockedStorage->expects($this->any())->method('getRootLevelFolder')->will($this->returnValue(
582 new \TYPO3\CMS\Core\Resource\Folder($mockedStorage, '/', '/')
583 ));
584 $mockedStorage->expects($this->any())->method('checkFolderActionPermission')->will($this->returnValue(true));
585 $mockedStorage->expects($this->any())->method('isBrowsable')->will($this->returnValue(true));
586 return new \TYPO3\CMS\Core\Resource\Folder($mockedStorage, $identifier, $identifier);
587 }
588 }