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