[TASK] Unit tests: Do not rely on CacheManager instance
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Utility / LocalizationUtilityTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Utility;
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\Localization\LocalizationFactory;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
21 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22
23 /**
24 * Test case
25 */
26 class LocalizationUtilityTest extends UnitTestCase
27 {
28 /**
29 * Subject is not notice free, disable E_NOTICES
30 */
31 protected static $suppressNotices = true;
32
33 /**
34 * Instance of configurationManager, injected to subject
35 *
36 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManager
37 */
38 protected $configurationManagerMock;
39
40 /**
41 * LOCAL_LANG array fixture
42 *
43 * @var array
44 */
45 protected $LOCAL_LANG = [];
46
47 /**
48 * File path of locallang for extension "core"
49 * @var string
50 */
51 protected $languageFilePath = '';
52
53 /**
54 * Prepare class mocking some dependencies
55 */
56 protected function setUp()
57 {
58 $this->languageFilePath = $this->getLanguageFilePath('core');
59 $this->LOCAL_LANG = [
60 $this->languageFilePath => [
61 'default' => [
62 'key1' => [
63 [
64 'source' => 'English label for key1',
65 'target' => 'English label for key1',
66 ]
67 ],
68 'key2' => [
69 [
70 'source' => 'English label for key2',
71 'target' => 'English label for key2',
72 ]
73 ],
74 'key3' => [
75 [
76 'source' => 'English label for key3',
77 'target' => 'English label for key3',
78 ]
79 ],
80 'key4' => [
81 [
82 'source' => 'English label for key4',
83 'target' => 'English label for key4',
84 ]
85 ],
86 'keyWithPlaceholder' => [
87 [
88 'source' => 'English label with number %d',
89 'target' => 'English label with number %d',
90 ]
91 ],
92 ],
93 'dk' => [
94 'key1' => [
95 [
96 'source' => 'English label for key1',
97 'target' => 'Dansk label for key1',
98 ]
99 ],
100 // not translated in dk => no target (llxml)
101 'key2' => [
102 [
103 'source' => 'English label for key2',
104 ]
105 ],
106 'key3' => [
107 [
108 'source' => 'English label for key3',
109 ]
110 ],
111 // not translated in dk => empty target (xliff)
112 'key4' => [
113 [
114 'source' => 'English label for key4',
115 'target' => '',
116 ]
117 ],
118 // not translated in dk => empty target (xliff)
119 'key5' => [
120 [
121 'source' => 'English label for key5',
122 'target' => '',
123 ]
124 ],
125 'keyWithPlaceholder' => [
126 [
127 'source' => 'English label with number %d',
128 ]
129 ],
130 ],
131 // fallback language for labels which are not translated in dk
132 'dk_alt' => [
133 'key1' => [
134 [
135 'source' => 'English label for key1',
136 ]
137 ],
138 'key2' => [
139 [
140 'source' => 'English label for key2',
141 'target' => 'Dansk alternative label for key2',
142 ]
143 ],
144 'key3' => [
145 [
146 'source' => 'English label for key3',
147 ]
148 ],
149 // not translated in dk_alt => empty target (xliff)
150 'key4' => [
151 [
152 'source' => 'English label for key4',
153 'target' => '',
154 ]
155 ],
156 'key5' => [
157 [
158 'source' => 'English label for key5',
159 'target' => 'Dansk alternative label for key5',
160 ]
161 ],
162 'keyWithPlaceholder' => [
163 [
164 'source' => 'English label with number %d',
165 ]
166 ],
167 ],
168
169 ],
170 ];
171
172 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
173
174 $this->configurationManager = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Configuration\ConfigurationManager::class, ['getConfiguration']);
175 $property = $reflectionClass->getProperty('configurationManager');
176 $property->setAccessible(true);
177 $property->setValue($this->configurationManager);
178
179 $localizationFactoryProphecy = $this->prophesize(LocalizationFactory::class);
180 GeneralUtility::setSingletonInstance(LocalizationFactory::class, $localizationFactoryProphecy->reveal());
181 $localizationFactoryProphecy->getParsedData(Argument::cetera())->willReturn([]);
182 }
183
184 /**
185 * Reset static properties
186 */
187 protected function tearDown()
188 {
189 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
190
191 $property = $reflectionClass->getProperty('configurationManager');
192 $property->setAccessible(true);
193 $property->setValue(null);
194
195 $property = $reflectionClass->getProperty('LOCAL_LANG');
196 $property->setAccessible(true);
197 $property->setValue([]);
198
199 GeneralUtility::purgeInstances();
200
201 parent::tearDown();
202 }
203
204 /**
205 * @param string $extensionName
206 * @return string
207 */
208 protected function getLanguageFilePath(string $extensionName): string
209 {
210 return PATH_typo3 . 'sysext/' . $extensionName . '/Resources/Private/Language/locallang.xlf';
211 }
212
213 /**
214 * @test
215 */
216 public function implodeTypoScriptLabelArrayWorks()
217 {
218 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
219 $method = $reflectionClass->getMethod('flattenTypoScriptLabelArray');
220 $method->setAccessible(true);
221
222 $expected = [
223 'key1' => 'value1',
224 'key2' => 'value2',
225 'key3' => 'value3',
226 'key3.subkey1' => 'subvalue1',
227 'key3.subkey2.subsubkey' => 'val'
228 ];
229 $input = [
230 'key1' => 'value1',
231 'key2' => 'value2',
232 'key3' => [
233 '_typoScriptNodeValue' => 'value3',
234 'subkey1' => 'subvalue1',
235 'subkey2' => [
236 'subsubkey' => 'val'
237 ]
238 ]
239 ];
240 $result = $method->invoke(null, $input);
241 $this->assertEquals($expected, $result);
242 }
243
244 /**
245 * @test
246 */
247 public function translateForEmptyStringKeyReturnsNull()
248 {
249 $this->assertNull(LocalizationUtility::translate('', 'extbase'));
250 }
251
252 /**
253 * @test
254 */
255 public function translateForEmptyStringKeyWithArgumentsReturnsNull()
256 {
257 $this->assertNull(LocalizationUtility::translate('', 'extbase', ['argument']));
258 }
259
260 /**
261 * @return array
262 */
263 public function translateDataProvider()
264 {
265 return [
266 'get translated key' =>
267 ['key1', 'dk', 'Dansk label for key1'],
268
269 'fallback to English when translation is missing for key' =>
270 ['key2', 'dk', 'English label for key2'],
271
272 'fallback to English for non existing language' =>
273 ['key2', 'xx', 'English label for key2'],
274
275 'replace placeholder with argument' =>
276 ['keyWithPlaceholder', 'default', 'English label with number 100', [], [100]],
277
278 'get translated key from primary language' =>
279 ['key1', 'dk', 'Dansk label for key1', ['dk_alt']],
280
281 'fallback to alternative language if translation is missing(llxml)' =>
282 ['key2', 'dk', 'Dansk alternative label for key2', ['dk_alt']],
283
284 'fallback to alternative language if translation is missing(xlif)' =>
285 ['key5', 'dk', 'Dansk alternative label for key5', ['dk_alt']],
286
287 'fallback to English for label not translated in dk and dk_alt(llxml)' =>
288 ['key3', 'dk', 'English label for key3', ['dk_alt']],
289
290 'fallback to English for label not translated in dk and dk_alt(xlif)' =>
291 ['key4', 'dk', 'English label for key4', ['dk_alt']],
292 ];
293 }
294
295 /**
296 * @param string $key
297 * @param string $languageKey
298 * @param string $expected
299 * @param array $altLanguageKeys
300 * @param array $arguments
301 * @dataProvider translateDataProvider
302 * @test
303 */
304 public function translateTestWithBackendUserLanguage($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
305 {
306 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
307
308 $property = $reflectionClass->getProperty('LOCAL_LANG');
309 $property->setAccessible(true);
310 $property->setValue($this->LOCAL_LANG);
311
312 $oldBackendUserLanguage = $GLOBALS['BE_USER']->uc['lang'];
313 $GLOBALS['BE_USER']->uc['lang'] = $languageKey;
314 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, null, $altLanguageKeys));
315 $GLOBALS['BE_USER']->uc['lang'] = $oldBackendUserLanguage;
316 }
317
318 /**
319 * @param string $key
320 * @param string $languageKey
321 * @param string $expected
322 * @param array $altLanguageKeys
323 * @param array $arguments
324 * @dataProvider translateDataProvider
325 * @test
326 */
327 public function translateTestWithExplicitLanguageParameters($key, $languageKey, $expected, array $altLanguageKeys = [], array $arguments = null)
328 {
329 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
330
331 $property = $reflectionClass->getProperty('LOCAL_LANG');
332 $property->setAccessible(true);
333 $property->setValue($this->LOCAL_LANG);
334
335 $this->assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, $languageKey, $altLanguageKeys));
336 }
337
338 /**
339 * @return array
340 */
341 public function loadTypoScriptLabelsProvider()
342 {
343 return [
344 'override labels with typoscript' => [
345 'LOCAL_LANG' => [
346 $this->getLanguageFilePath('core') => [
347 'dk' => [
348 'key1' => [
349 [
350 'source' => 'English label for key1',
351 'target' => 'Dansk label for key1 core',
352 ]
353 ],
354 'key2' => [
355 [
356 'source' => 'English label for key2',
357 ]
358 ],
359 'key3.subkey1' => [
360 [
361 'source' => 'English label for key3',
362 ]
363 ],
364 ],
365 ],
366 $this->getLanguageFilePath('backend') => [
367 'dk' => [
368 'key1' => [
369 [
370 'source' => 'English label for key1',
371 'target' => 'Dansk label for key1 backend',
372 ]
373 ],
374 'key2' => [
375 [
376 'source' => 'English label for key2',
377 ]
378 ],
379 'key3.subkey1' => [
380 [
381 'source' => 'English label for key3',
382 ]
383 ],
384 ],
385 ],
386 ],
387 'typoscript LOCAL_LANG' => [
388 '_LOCAL_LANG' => [
389 'dk' => [
390 'key1' => 'key1 value from TS core',
391 'key3' => [
392 'subkey1' => 'key3.subkey1 value from TS core',
393 // this key doesn't exist in xml files
394 'subkey2' => [
395 'subsubkey' => 'key3.subkey2.subsubkey value from TS core'
396 ]
397 ]
398 ]
399 ]
400 ],
401 'language key' => 'dk',
402 'expected' => [
403 'key1' => [
404 [
405 'source' => 'English label for key1',
406 'target' => 'key1 value from TS core',
407 ]
408 ],
409 'key2' => [
410 [
411 'source' => 'English label for key2',
412 ]
413 ],
414 'key3.subkey1' => [
415 [
416 'source' => 'English label for key3',
417 'target' => 'key3.subkey1 value from TS core',
418 ]
419 ],
420 'key3.subkey2.subsubkey' => [
421 [
422 'target' => 'key3.subkey2.subsubkey value from TS core',
423 ]
424 ],
425 ],
426 ]
427 ];
428 }
429
430 /**
431 * Tests whether labels from xml are overwritten by TypoScript labels
432 *
433 * @param array $LOCAL_LANG
434 * @param array $typoScriptLocalLang
435 * @param string $languageKey
436 * @param array $expected
437 * @dataProvider loadTypoScriptLabelsProvider
438 * @test
439 */
440 public function loadTypoScriptLabels(array $LOCAL_LANG, array $typoScriptLocalLang, $languageKey, array $expected)
441 {
442 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
443
444 $property = $reflectionClass->getProperty('LOCAL_LANG');
445 $property->setAccessible(true);
446 $property->setValue($LOCAL_LANG);
447
448 $configurationType = \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
449 $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'core', null)->will($this->returnValue($typoScriptLocalLang));
450
451 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
452 $method->setAccessible(true);
453 $method->invoke(null, 'core', $this->languageFilePath);
454
455 $property = $reflectionClass->getProperty('LOCAL_LANG');
456 $property->setAccessible(true);
457 $result = $property->getValue();
458
459 $this->assertEquals($expected, $result[$this->languageFilePath][$languageKey]);
460 }
461
462 /**
463 * @test
464 */
465 public function clearLabelWithTypoScript()
466 {
467 $reflectionClass = new \ReflectionClass(LocalizationUtility::class);
468
469 $property = $reflectionClass->getProperty('LOCAL_LANG');
470 $property->setAccessible(true);
471 $property->setValue($this->LOCAL_LANG);
472
473 $typoScriptLocalLang = [
474 '_LOCAL_LANG' => [
475 'dk' => [
476 'key1' => '',
477 ]
478 ]
479 ];
480
481 $configurationType = \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK;
482 $this->configurationManager->expects($this->at(0))->method('getConfiguration')->with($configurationType, 'core', null)->will($this->returnValue($typoScriptLocalLang));
483
484 $method = $reflectionClass->getMethod('loadTypoScriptLabels');
485 $method->setAccessible(true);
486 $method->invoke(null, 'core', $this->languageFilePath);
487
488 $result = LocalizationUtility::translate('key1', 'core', null, 'dk');
489 $this->assertNotNull($result);
490 $this->assertEquals('', $result);
491 }
492
493 /**
494 * @test
495 */
496 public function translateThrowsExceptionWithEmptyExtensionNameIfKeyIsNotPrefixedWithLLL()
497 {
498 $this->expectException(\InvalidArgumentException::class);
499 $this->expectExceptionCode(1498144052);
500 LocalizationUtility::translate('foo/bar', '');
501 }
502 }