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