Completed
Push — master ( 90072e...c81959 )
by Daniel
11:23
created

i18nTextCollectorTest::testUncollectableCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\i18n\Tests;
4
5
use PHPUnit_Framework_Error_Notice;
6
use SilverStripe\Assets\Filesystem;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\i18n\i18n;
9
use SilverStripe\i18n\TextCollection\i18nTextCollector;
10
use SilverStripe\i18n\Messages\YamlWriter;
11
use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector;
12
use SilverStripe\View\SSViewer;
13
14
class i18nTextCollectorTest extends SapphireTest
15
{
16
    use i18nTestManifest;
17
18
    /**
19
     * @var string
20
     */
21
    protected $alternateBaseSavePath = null;
22
23
    public function setUp()
24
    {
25
        parent::setUp();
26
        $this->setupManifest();
27
28
        $this->alternateBaseSavePath = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'i18nTextCollectorTest_webroot';
29
        Filesystem::makeFolder($this->alternateBaseSavePath);
30
    }
31
32
    public function tearDown()
33
    {
34
        if (is_dir($this->alternateBaseSavePath)) {
35
            Filesystem::removeFolder($this->alternateBaseSavePath);
36
        }
37
38
        $this->tearDownManifest();
39
        parent::tearDown();
40
    }
41
42
    public function testConcatenationInEntityValues()
43
    {
44
        $c = i18nTextCollector::create();
45
46
        $php = <<<PHP
47
_t(
48
'Test.CONCATENATED',
49
'Line 1 and ' .
50
'Line \'2\' and ' .
51
'Line "3"',
52
53
'Comment'
54
);
55
56
_t(
57
'Test.CONCATENATED2',
58
"Line \"4\" and " .
59
"Line 5");
60
PHP;
61
        $this->assertEquals(
62
            array(
63
                'Test.CONCATENATED' => [
64
                    'default' => "Line 1 and Line '2' and Line \"3\"",
65
                    'comment' => 'Comment'
66
                ],
67
                'Test.CONCATENATED2' => "Line \"4\" and Line 5"
68
            ),
69
            $c->collectFromCode($php, 'mymodule')
70
        );
71
    }
72
73
    public function testCollectFromNewTemplateSyntaxUsingParserSubclass()
74
    {
75
        $c = i18nTextCollector::create();
76
        $c->setWarnOnEmptyDefault(false);
77
78
        $html = <<<SS
79
        <% _t('Test.SINGLEQUOTE','Single Quote'); %>
80
<%t i18nTestModule.NEWMETHODSIG "New _t method signature test" %>
81
<%t i18nTestModule.INJECTIONS_0 "Hello {name} {greeting}, and {goodbye}" name="Mark" greeting="welcome" goodbye="bye" %>
82
<%t i18nTestModule.INJECTIONS_1 "Hello {name} {greeting}, and {goodbye}" name="Paul" greeting="welcome" goodbye="cya" %>
83
<%t i18nTestModule.INJECTIONS_2 "Hello {name} {greeting}" is "context (ignored)" name="Steffen" greeting="Wilkommen" %>
84
<%t i18nTestModule.INJECTIONS_3 name="Cat" greeting='meow' goodbye="meow" %>
85
<%t i18nTestModule.INJECTIONS_4 name=\$absoluteBaseURL greeting=\$get_locale goodbye="global calls" %>
86
<%t i18nTestModule.INJECTIONS_9 "An item|{count} items" is "Test Pluralisation" count=4 %>
87
SS;
88
        $c->collectFromTemplate($html, 'mymodule', 'Test');
89
90
        $this->assertEquals(
91
            [
92
                'Test.SINGLEQUOTE' => 'Single Quote',
93
                'i18nTestModule.NEWMETHODSIG' => "New _t method signature test",
94
                'i18nTestModule.INJECTIONS_0' => "Hello {name} {greeting}, and {goodbye}",
95
                'i18nTestModule.INJECTIONS_1' => "Hello {name} {greeting}, and {goodbye}",
96
                'i18nTestModule.INJECTIONS_2' => [
97
                    'default' => "Hello {name} {greeting}",
98
                    'comment' => 'context (ignored)',
99
                ],
100
                'i18nTestModule.INJECTIONS_9' => [
101
                    'one' => 'An item',
102
                    'other' => '{count} items',
103
                    'comment' => 'Test Pluralisation'
104
                ],
105
            ],
106
            $c->collectFromTemplate($html, 'mymodule', 'Test')
107
        );
108
109
        // Test warning is raised on empty default
110
        $c->setWarnOnEmptyDefault(true);
111
        $this->setExpectedException(
112
            PHPUnit_Framework_Error_Notice::class,
113
            'Missing localisation default for key i18nTestModule.INJECTIONS_3'
114
        );
115
        $c->collectFromTemplate($html, 'mymodule', 'Test');
116
    }
117
118
    public function testCollectFromTemplateSimple()
119
    {
120
        $c = i18nTextCollector::create();
121
122
        $html = <<<SS
123
<% _t('Test.SINGLEQUOTE','Single Quote'); %>
124
SS;
125
        $this->assertEquals(
126
            [ 'Test.SINGLEQUOTE' => 'Single Quote' ],
127
            $c->collectFromTemplate($html, 'mymodule', 'Test')
128
        );
129
130
        $html = <<<SS
131
<% _t(  "Test.DOUBLEQUOTE", "Double Quote and Spaces"   ); %>
132
SS;
133
        $this->assertEquals(
134
            [ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ],
135
            $c->collectFromTemplate($html, 'mymodule', 'Test')
136
        );
137
138
        $html = <<<SS
139
<% _t("Test.NOSEMICOLON","No Semicolon") %>
140
SS;
141
        $this->assertEquals(
142
            [ 'Test.NOSEMICOLON' => "No Semicolon" ],
143
            $c->collectFromTemplate($html, 'mymodule', 'Test')
144
        );
145
    }
146
147
    public function testCollectFromTemplateAdvanced()
148
    {
149
        $c = i18nTextCollector::create();
150
        $c->setWarnOnEmptyDefault(false);
151
152
        $html = <<<SS
153
<% _t(
154
	'NEWLINES',
155
	'New Lines'
156
) %>
157
SS;
158
        $this->assertEquals(
159
            [ 'Test.NEWLINES' => "New Lines" ],
160
            $c->collectFromTemplate($html, 'mymodule', 'Test')
161
        );
162
163
        $html = <<<SS
164
<% _t(
165
	'Test.PRIOANDCOMMENT',
166
	' Prio and Value with "Double Quotes"',
167
	'Comment with "Double Quotes"'
168
) %>
169
SS;
170
        $this->assertEquals(
171
            [ 'Test.PRIOANDCOMMENT' => [
172
                'default' => ' Prio and Value with "Double Quotes"',
173
                'comment' => 'Comment with "Double Quotes"',
174
            ]],
175
            $c->collectFromTemplate($html, 'mymodule', 'Test')
176
        );
177
178
        $html = <<<SS
179
<% _t(
180
	'Test.PRIOANDCOMMENT',
181
	" Prio and Value with 'Single Quotes'",
182
183
	"Comment with 'Single Quotes'"
184
) %>
185
SS;
186
        $this->assertEquals(
187
            [ 'Test.PRIOANDCOMMENT' => [
188
                'default' => " Prio and Value with 'Single Quotes'",
189
                'comment' => "Comment with 'Single Quotes'",
190
            ]],
191
            $c->collectFromTemplate($html, 'mymodule', 'Test')
192
        );
193
194
        // Test empty
195
        $html = <<<SS
196
<% _t('Test.PRIOANDCOMMENT') %>
197
SS;
198
        $this->assertEquals(
199
            [],
200
            $c->collectFromTemplate($html, 'mymodule', 'Test')
201
        );
202
203
        // Test warning is raised on empty default
204
        $c->setWarnOnEmptyDefault(true);
205
        $this->setExpectedException(
206
            PHPUnit_Framework_Error_Notice::class,
207
            'Missing localisation default for key Test.PRIOANDCOMMENT'
208
        );
209
        $c->collectFromTemplate($html, 'mymodule', 'Test');
210
    }
211
212
213
    public function testCollectFromCodeSimple()
214
    {
215
        $c = i18nTextCollector::create();
216
217
        $php = <<<PHP
218
_t('Test.SINGLEQUOTE','Single Quote');
219
PHP;
220
        $this->assertEquals(
221
            [ 'Test.SINGLEQUOTE' => 'Single Quote' ],
222
            $c->collectFromCode($php, 'mymodule')
223
        );
224
225
        $php = <<<PHP
226
_t(  "Test.DOUBLEQUOTE", "Double Quote and Spaces"   );
227
PHP;
228
        $this->assertEquals(
229
            [ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ],
230
            $c->collectFromCode($php, 'mymodule')
231
        );
232
    }
233
234
    public function testCollectFromCodeAdvanced()
235
    {
236
        $c = i18nTextCollector::create();
237
238
        $php = <<<PHP
239
_t(
240
	'Test.NEWLINES',
241
	'New Lines'
242
);
243
PHP;
244
        $this->assertEquals(
245
            [ 'Test.NEWLINES' => "New Lines" ],
246
            $c->collectFromCode($php, 'mymodule')
247
        );
248
249
        $php = <<<PHP
250
_t(
251
	'Test.PRIOANDCOMMENT',
252
	' Value with "Double Quotes"',
253
254
	'Comment with "Double Quotes"'
255
);
256
PHP;
257
        $this->assertEquals(
258
            [
259
                'Test.PRIOANDCOMMENT' => [
260
                    'default' => ' Value with "Double Quotes"',
261
                    'comment' => 'Comment with "Double Quotes"',
262
                ]
263
            ],
264
            $c->collectFromCode($php, 'mymodule')
265
        );
266
267
        $php = <<<PHP
268
_t(
269
	'Test.PRIOANDCOMMENT',
270
	" Value with 'Single Quotes'",
271
272
	"Comment with 'Single Quotes'"
273
);
274
PHP;
275
        $this->assertEquals(
276
            [ 'Test.PRIOANDCOMMENT' => [
277
                'default' => " Value with 'Single Quotes'",
278
                'comment' => "Comment with 'Single Quotes'"
279
            ] ],
280
            $c->collectFromCode($php, 'mymodule')
281
        );
282
283
        $php = <<<PHP
284
_t(
285
	'Test.PRIOANDCOMMENT',
286
	'Value with \'Escaped Single Quotes\''
287
);
288
PHP;
289
        $this->assertEquals(
290
            [ 'Test.PRIOANDCOMMENT' => "Value with 'Escaped Single Quotes'" ],
291
            $c->collectFromCode($php, 'mymodule')
292
        );
293
294
        $php = <<<PHP
295
_t(
296
	'Test.PRIOANDCOMMENT',
297
	"Doublequoted Value with 'Unescaped Single Quotes'"
298
	
299
	
300
);
301
PHP;
302
        $this->assertEquals(
303
            [ 'Test.PRIOANDCOMMENT' => "Doublequoted Value with 'Unescaped Single Quotes'"],
304
            $c->collectFromCode($php, 'mymodule')
305
        );
306
    }
307
308
309
    public function testNewlinesInEntityValues()
310
    {
311
        $c = i18nTextCollector::create();
312
313
        $php = <<<PHP
314
_t(
315
'Test.NEWLINESINGLEQUOTE',
316
'Line 1
317
Line 2'
318
);
319
PHP;
320
321
        $eol = PHP_EOL;
322
        $this->assertEquals(
323
            [ 'Test.NEWLINESINGLEQUOTE' => "Line 1{$eol}Line 2" ],
324
            $c->collectFromCode($php, 'mymodule')
325
        );
326
327
        $php = <<<PHP
328
_t(
329
'Test.NEWLINEDOUBLEQUOTE',
330
"Line 1
331
Line 2"
332
);
333
PHP;
334
        $this->assertEquals(
335
            [ 'Test.NEWLINEDOUBLEQUOTE' => "Line 1{$eol}Line 2" ],
336
            $c->collectFromCode($php, 'mymodule')
337
        );
338
    }
339
340
    /**
341
     * Test extracting entities from the new _t method signature
342
     */
343
    public function testCollectFromCodeNewSignature()
344
    {
345
        $c = i18nTextCollector::create();
346
        $c->setWarnOnEmptyDefault(false); // Disable warnings for tests
347
348
        $php = <<<PHP
349
_t('i18nTestModule.NEWMETHODSIG',"New _t method signature test");
350
_t('i18nTestModule.INJECTIONS2', "Hello {name} {greeting}. But it is late, {goodbye}",
351
	array("name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"));
352
_t("i18nTestModule.INJECTIONS3", "Hello {name} {greeting}. But it is late, {goodbye}",
353
		"New context (this should be ignored)",
354
		array("name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"));
355
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
356
_t('i18nTestModule.INJECTIONS6', "Hello {name} {greeting}. But it is late, {goodbye}",
357
	["name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"]);
358
_t("i18nTestModule.INJECTIONS7", "Hello {name} {greeting}. But it is late, {goodbye}",
359
		"New context (this should be ignored)",
360
		["name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"]);
361
_t('i18nTestModule.INJECTIONS8', ["name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"]);
362
_t('i18nTestModule.INJECTIONS9', "An item|{count} items", ['count' => 4], "Test Pluralisation");
363
PHP;
364
365
        $collectedTranslatables = $c->collectFromCode($php, 'mymodule');
366
367
        $expectedArray = [
368
            'i18nTestModule.INJECTIONS2' => "Hello {name} {greeting}. But it is late, {goodbye}",
369
            'i18nTestModule.INJECTIONS3' => [
370
                'default' => "Hello {name} {greeting}. But it is late, {goodbye}",
371
                'comment' => 'New context (this should be ignored)'
372
            ],
373
            'i18nTestModule.INJECTIONS6' => "Hello {name} {greeting}. But it is late, {goodbye}",
374
            'i18nTestModule.INJECTIONS7' => [
375
                'default' => "Hello {name} {greeting}. But it is late, {goodbye}",
376
                'comment' => "New context (this should be ignored)",
377
            ],
378
            'i18nTestModule.INJECTIONS9' => [
379
                'one' => 'An item',
380
                'other' => '{count} items',
381
                'comment' => 'Test Pluralisation',
382
            ],
383
            'i18nTestModule.NEWMETHODSIG' => "New _t method signature test",
384
        ];
385
        $this->assertEquals($expectedArray, $collectedTranslatables);
386
387
        // Test warning is raised on empty default
388
        $this->setExpectedException(
389
            PHPUnit_Framework_Error_Notice::class,
390
            'Missing localisation default for key i18nTestModule.INJECTIONS4'
391
        );
392
        $php = <<<PHP
393
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
394
PHP;
395
        $c->setWarnOnEmptyDefault(true);
396
        $c->collectFromCode($php, 'mymodule');
397
    }
398
399
    public function testUncollectableCode()
400
    {
401
        $c = i18nTextCollector::create();
402
403
        $php = <<<PHP
404
_t(static::class.'.KEY1', 'Default');
405
_t(self::class.'.KEY2', 'Default');
406
_t(__CLASS__.'.KEY3', 'Default');
407
_t('Collectable.KEY4', 'Default');
408
PHP;
409
410
        $collectedTranslatables = $c->collectFromCode($php, 'mymodule');
411
412
        // Only one item is collectable
413
        $expectedArray = [ 'Collectable.KEY4' => 'Default' ];
414
        $this->assertEquals($expectedArray, $collectedTranslatables);
415
    }
416
417
    public function testCollectFromIncludedTemplates()
418
    {
419
        $c = i18nTextCollector::create();
420
        $c->setWarnOnEmptyDefault(false); // Disable warnings for tests
421
422
        $templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss';
423
        $html = file_get_contents($templateFilePath);
424
        $matches = $c->collectFromTemplate($html, 'mymodule', 'RandomNamespace');
425
426
        $this->assertArrayHasKey('RandomNamespace.LAYOUTTEMPLATENONAMESPACE', $matches);
427
        $this->assertEquals(
428
            'Layout Template no namespace',
429
            $matches['RandomNamespace.LAYOUTTEMPLATENONAMESPACE']
430
        );
431
        $this->assertArrayHasKey('RandomNamespace.SPRINTFNONAMESPACE', $matches);
432
        $this->assertEquals(
433
            'My replacement no namespace: %s',
434
            $matches['RandomNamespace.SPRINTFNONAMESPACE']
435
        );
436
        $this->assertArrayHasKey('i18nTestModule.LAYOUTTEMPLATE', $matches);
437
        $this->assertEquals(
438
            'Layout Template',
439
            $matches['i18nTestModule.LAYOUTTEMPLATE']
440
        );
441
        $this->assertArrayHasKey('i18nTestModule.SPRINTFNAMESPACE', $matches);
442
        $this->assertEquals(
443
            'My replacement: %s',
444
            $matches['i18nTestModule.SPRINTFNAMESPACE']
445
        );
446
447
        // Includes should not automatically inject translations into parent templates
448
        $this->assertArrayNotHasKey('i18nTestModule.WITHNAMESPACE', $matches);
449
        $this->assertArrayNotHasKey('i18nTestModuleInclude.ss.NONAMESPACE', $matches);
450
        $this->assertArrayNotHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE', $matches);
451
        $this->assertArrayNotHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE', $matches);
452
    }
453
454
    public function testCollectFromThemesTemplates()
455
    {
456
        $c = i18nTextCollector::create();
457
        SSViewer::set_themes([ 'testtheme1' ]);
458
459
        // Collect from layout
460
        $layoutFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Layout/i18nTestTheme1.ss';
461
        $layoutHTML = file_get_contents($layoutFilePath);
462
        $layoutMatches = $c->collectFromTemplate($layoutHTML, 'themes/testtheme1', 'i18nTestTheme1.ss');
463
464
        // all entities from i18nTestTheme1.ss
465
        $this->assertEquals(
466
            [
467
                'i18nTestTheme1.LAYOUTTEMPLATE' => 'Theme1 Layout Template',
468
                'i18nTestTheme1.SPRINTFNAMESPACE' => 'Theme1 My replacement: %s',
469
                'i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE' => 'Theme1 Layout Template no namespace',
470
                'i18nTestTheme1.ss.SPRINTFNONAMESPACE' => 'Theme1 My replacement no namespace: %s',
471
            ],
472
            $layoutMatches
473
        );
474
475
        // Collect from include
476
        $includeFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Includes/i18nTestTheme1Include.ss';
477
        $includeHTML = file_get_contents($includeFilePath);
478
        $includeMatches = $c->collectFromTemplate($includeHTML, 'themes/testtheme1', 'i18nTestTheme1Include.ss');
479
480
        // all entities from i18nTestTheme1Include.ss
481
        $this->assertEquals(
482
            [
483
                'i18nTestTheme1Include.SPRINTFINCLUDENAMESPACE' => 'Theme1 My include replacement: %s',
484
                'i18nTestTheme1Include.WITHNAMESPACE' => 'Theme1 Include Entity with Namespace',
485
                'i18nTestTheme1Include.ss.NONAMESPACE' => 'Theme1 Include Entity without Namespace',
486
                'i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE' => 'Theme1 My include replacement no namespace: %s'
487
            ],
488
            $includeMatches
489
        );
490
    }
491
492
    public function testCollectMergesWithExisting()
493
    {
494
        $c = i18nTextCollector::create();
495
        $c->setWarnOnEmptyDefault(false);
496
        $c->setWriter(new YamlWriter());
497
        $c->basePath = $this->alternateBasePath;
498
        $c->baseSavePath = $this->alternateBaseSavePath;
499
500
        $entitiesByModule = $c->collect(null, true /* merge */);
501
        $this->assertArrayHasKey(
502
            'i18nTestModule.ENTITY',
503
            $entitiesByModule['i18ntestmodule'],
504
            'Retains existing entities'
505
        );
506
        $this->assertArrayHasKey(
507
            'i18nTestModule.NEWENTITY',
508
            $entitiesByModule['i18ntestmodule'],
509
            'Adds new entities'
510
        );
511
512
        // Test cross-module strings are set correctly
513
        $this->assertArrayHasKey(
514
            'i18nProviderClass.OTHER_MODULE',
515
            $entitiesByModule['i18ntestmodule']
516
        );
517
        $this->assertEquals(
518
            [
519
                'comment' => 'Test string in another module',
520
                'default' => 'i18ntestmodule string defined in i18nothermodule',
521
            ],
522
            $entitiesByModule['i18ntestmodule']['i18nProviderClass.OTHER_MODULE']
523
        );
524
    }
525
526
    public function testCollectFromFilesystemAndWriteMasterTables()
527
    {
528
        i18n::set_locale('en_US');  //set the locale to the US locale expected in the asserts
529
        i18n::config()->update('default_locale', 'en_US');
530
        i18n::config()->update('missing_default_warning', false);
531
532
        $c = i18nTextCollector::create();
533
        $c->setWarnOnEmptyDefault(false);
534
        $c->setWriter(new YamlWriter());
535
        $c->basePath = $this->alternateBasePath;
536
        $c->baseSavePath = $this->alternateBaseSavePath;
537
538
        $c->run();
539
540
        // i18ntestmodule
541
        $moduleLangFile = "{$this->alternateBaseSavePath}/i18ntestmodule/lang/" . $c->getDefaultLocale() . '.yml';
542
        $this->assertTrue(
543
            file_exists($moduleLangFile),
544
            'Master language file can be written to modules /lang folder'
545
        );
546
547
        $moduleLangFileContent = file_get_contents($moduleLangFile);
548
        $this->assertContains(
549
            "    ADDITION: Addition\n",
550
            $moduleLangFileContent
551
        );
552
        $this->assertContains(
553
            "    ENTITY: 'Entity with \"Double Quotes\"'\n",
554
            $moduleLangFileContent
555
        );
556
        $this->assertContains(
557
            "    MAINTEMPLATE: 'Main Template'\n",
558
            $moduleLangFileContent
559
        );
560
        $this->assertContains(
561
            "    OTHERENTITY: 'Other Entity'\n",
562
            $moduleLangFileContent
563
        );
564
        $this->assertContains(
565
            "    WITHNAMESPACE: 'Include Entity with Namespace'\n",
566
            $moduleLangFileContent
567
        );
568
        $this->assertContains(
569
            "    NONAMESPACE: 'Include Entity without Namespace'\n",
570
            $moduleLangFileContent
571
        );
572
573
        // i18nothermodule
574
        $otherModuleLangFile = "{$this->alternateBaseSavePath}/i18nothermodule/lang/" . $c->getDefaultLocale() . '.yml';
575
        $this->assertTrue(
576
            file_exists($otherModuleLangFile),
577
            'Master language file can be written to modules /lang folder'
578
        );
579
        $otherModuleLangFileContent = file_get_contents($otherModuleLangFile);
580
        $this->assertContains(
581
            "    ENTITY: 'Other Module Entity'\n",
582
            $otherModuleLangFileContent
583
        );
584
        $this->assertContains(
585
            "    MAINTEMPLATE: 'Main Template Other Module'\n",
586
            $otherModuleLangFileContent
587
        );
588
589
        // testtheme1
590
        $theme1LangFile = "{$this->alternateBaseSavePath}/themes/testtheme1/lang/" . $c->getDefaultLocale() . '.yml';
591
        $this->assertTrue(
592
            file_exists($theme1LangFile),
593
            'Master theme language file can be written to themes/testtheme1 /lang folder'
594
        );
595
        $theme1LangFileContent = file_get_contents($theme1LangFile);
596
        $this->assertContains(
597
            "    MAINTEMPLATE: 'Theme1 Main Template'\n",
598
            $theme1LangFileContent
599
        );
600
        $this->assertContains(
601
            "    LAYOUTTEMPLATE: 'Theme1 Layout Template'\n",
602
            $theme1LangFileContent
603
        );
604
        $this->assertContains(
605
            "    SPRINTFNAMESPACE: 'Theme1 My replacement: %s'\n",
606
            $theme1LangFileContent
607
        );
608
        $this->assertContains(
609
            "    LAYOUTTEMPLATENONAMESPACE: 'Theme1 Layout Template no namespace'\n",
610
            $theme1LangFileContent
611
        );
612
        $this->assertContains(
613
            "    SPRINTFNONAMESPACE: 'Theme1 My replacement no namespace: %s'\n",
614
            $theme1LangFileContent
615
        );
616
617
        $this->assertContains(
618
            "    SPRINTFINCLUDENAMESPACE: 'Theme1 My include replacement: %s'\n",
619
            $theme1LangFileContent
620
        );
621
        $this->assertContains(
622
            "    WITHNAMESPACE: 'Theme1 Include Entity with Namespace'\n",
623
            $theme1LangFileContent
624
        );
625
        $this->assertContains(
626
            "    NONAMESPACE: 'Theme1 Include Entity without Namespace'\n",
627
            $theme1LangFileContent
628
        );
629
        $this->assertContains(
630
            "    SPRINTFINCLUDENONAMESPACE: 'Theme1 My include replacement no namespace: %s'\n",
631
            $theme1LangFileContent
632
        );
633
634
        // testtheme2
635
        $theme2LangFile = "{$this->alternateBaseSavePath}/themes/testtheme2/lang/" . $c->getDefaultLocale() . '.yml';
636
        $this->assertTrue(
637
            file_exists($theme2LangFile),
638
            'Master theme language file can be written to themes/testtheme2 /lang folder'
639
        );
640
        $theme2LangFileContent = file_get_contents($theme2LangFile);
641
        $this->assertContains(
642
            "    MAINTEMPLATE: 'Theme2 Main Template'\n",
643
            $theme2LangFileContent
644
        );
645
    }
646
647
    public function testCollectFromEntityProvidersInCustomObject()
648
    {
649
        // note: Disable _fakewebroot manifest for this test
650
        $this->popManifests();
651
652
        $c = i18nTextCollector::create();
653
654
        // Collect from MyObject.php
655
        $filePath = __DIR__ . '/i18nTest/MyObject.php';
656
        $matches = $c->collectFromEntityProviders($filePath);
657
        $this->assertEquals(
658
            [
659
                'SilverStripe\Admin\LeftAndMain.OTHER_TITLE' => [
660
                    'default' => 'Other title',
661
                    'module' => 'admin',
662
                ],
663
                'SilverStripe\i18n\Tests\i18nTest\MyObject.PLURALNAME' => 'My Objects',
664
                'SilverStripe\i18n\Tests\i18nTest\MyObject.PLURALS' => [
665
                    'one' => 'A My Object',
666
                    'other' => '{count} My Objects',
667
                ],
668
                'SilverStripe\i18n\Tests\i18nTest\MyObject.SINGULARNAME' => 'My Object',
669
            ],
670
            $matches
671
        );
672
    }
673
674
    public function testCollectFromEntityProvidersInWebRoot()
675
    {
676
        // Collect from i18nProviderClass
677
        $c = i18nTextCollector::create();
678
        $c->setWarnOnEmptyDefault(false);
679
        $c->setWriter(new YamlWriter());
680
        $c->basePath = $this->alternateBasePath;
681
        $c->baseSavePath = $this->alternateBaseSavePath;
682
        $entitiesByModule = $c->collect(null, false);
683
        $this->assertEquals(
684
            [
685
                'comment' => 'Plural forms for the test class',
686
                'one' => 'A class',
687
                'other' => '{count} classes',
688
            ],
689
            $entitiesByModule['i18nothermodule']['i18nProviderClass.PLURALS']
690
        );
691
        $this->assertEquals(
692
            'My Provider Class',
693
            $entitiesByModule['i18nothermodule']['i18nProviderClass.TITLE']
694
        );
695
        $this->assertEquals(
696
            [
697
                'comment' => 'Test string in another module',
698
                'default' => 'i18ntestmodule string defined in i18nothermodule',
699
            ],
700
            $entitiesByModule['i18ntestmodule']['i18nProviderClass.OTHER_MODULE']
701
        );
702
    }
703
704
    /**
705
     * Test that duplicate keys are resolved to the appropriate modules
706
     */
707
    public function testResolveDuplicates()
708
    {
709
        $collector = new Collector();
710
711
        // Dummy data as collected
712
        $data1 = [
713
            'i18ntestmodule' => [
714
                'i18nTestModule.PLURALNAME' => 'Data Objects',
715
                'i18nTestModule.SINGULARNAME' => 'Data Object',
716
            ],
717
            'mymodule' => [
718
                'i18nTestModule.PLURALNAME' => 'Ignored String',
719
                'i18nTestModule.STREETNAME' => 'Shortland Street',
720
            ],
721
        ];
722
        $expected = [
723
            'i18ntestmodule' => [
724
                'i18nTestModule.PLURALNAME' => 'Data Objects',
725
                'i18nTestModule.SINGULARNAME' => 'Data Object',
726
            ],
727
            'mymodule' => [
728
                // Removed PLURALNAME because this key doesn't exist in i18ntestmodule strings
729
                'i18nTestModule.STREETNAME' => 'Shortland Street'
730
            ]
731
        ];
732
733
        $resolved = $collector->resolveDuplicateConflicts_Test($data1);
734
        $this->assertEquals($expected, $resolved);
735
736
        // Test getConflicts
737
        $data2 = [
738
            'module1' => [
739
                'i18ntestmodule.ONE' => 'One',
740
                'i18ntestmodule.TWO' => 'Two',
741
                'i18ntestmodule.THREE' => 'Three',
742
            ],
743
            'module2' => [
744
                'i18ntestmodule.THREE' => 'Three',
745
            ],
746
            'module3' => [
747
                'i18ntestmodule.TWO' => 'Two',
748
                'i18ntestmodule.THREE' => 'Three',
749
            ],
750
        ];
751
        $conflictsA = $collector->getConflicts_Test($data2);
752
        sort($conflictsA);
753
        $this->assertEquals(
754
            array('i18ntestmodule.THREE', 'i18ntestmodule.TWO'),
755
            $conflictsA
756
        );
757
758
        // Removing module3 should remove a conflict
759
        unset($data2['module3']);
760
        $conflictsB = $collector->getConflicts_Test($data2);
761
        $this->assertEquals(
762
            array('i18ntestmodule.THREE'),
763
            $conflictsB
764
        );
765
    }
766
767
    /**
768
     * Test ability for textcollector to detect modules
769
     */
770
    public function testModuleDetection()
771
    {
772
        $collector = new Collector();
773
        $modules = $collector->getModules_Test($this->alternateBasePath);
774
        $this->assertEquals(
775
            array(
776
                'i18nnonstandardmodule',
777
                'i18nothermodule',
778
                'i18ntestmodule',
779
                'themes/testtheme1',
780
                'themes/testtheme2'
781
            ),
782
            $modules
783
        );
784
785
        $this->assertEquals('i18ntestmodule', $collector->findModuleForClass_Test('i18nTestNamespacedClass'));
786
        $this->assertEquals(
787
            'i18ntestmodule',
788
            $collector->findModuleForClass_Test('i18nTest\\i18nTestNamespacedClass')
789
        );
790
        $this->assertEquals('i18ntestmodule', $collector->findModuleForClass_Test('i18nTestSubModule'));
791
    }
792
793
    /**
794
     * Test that text collector can detect module file lists properly
795
     */
796
    public function testModuleFileList()
797
    {
798
        $collector = new Collector();
799
        $collector->basePath = $this->alternateBasePath;
800
        $collector->baseSavePath = $this->alternateBaseSavePath;
801
802
        // Non-standard modules can't be safely filtered, so just index everything
803
        $nonStandardFiles = $collector->getFileListForModule_Test('i18nnonstandardmodule');
804
        $nonStandardRoot = $this->alternateBasePath . '/i18nnonstandardmodule';
805
        $this->assertEquals(3, count($nonStandardFiles));
806
        $this->assertArrayHasKey("{$nonStandardRoot}/_config.php", $nonStandardFiles);
807
        $this->assertArrayHasKey("{$nonStandardRoot}/phpfile.php", $nonStandardFiles);
808
        $this->assertArrayHasKey("{$nonStandardRoot}/template.ss", $nonStandardFiles);
809
810
        // Normal module should have predictable dir structure
811
        $testFiles = $collector->getFileListForModule_Test('i18ntestmodule');
812
        $testRoot = $this->alternateBasePath . '/i18ntestmodule';
813
        $this->assertEquals(7, count($testFiles));
814
        // Code in code folder is detected
815
        $this->assertArrayHasKey("{$testRoot}/code/i18nTestModule.php", $testFiles);
816
        $this->assertArrayHasKey("{$testRoot}/code/subfolder/_config.php", $testFiles);
817
        $this->assertArrayHasKey("{$testRoot}/code/subfolder/i18nTestSubModule.php", $testFiles);
818
        $this->assertArrayHasKey("{$testRoot}/code/subfolder/i18nTestNamespacedClass.php", $testFiles);
819
        // Templates in templates folder is detected
820
        $this->assertArrayHasKey("{$testRoot}/templates/Includes/i18nTestModuleInclude.ss", $testFiles);
821
        $this->assertArrayHasKey("{$testRoot}/templates/Layout/i18nTestModule.ss", $testFiles);
822
        $this->assertArrayHasKey("{$testRoot}/templates/i18nTestModule.ss", $testFiles);
823
824
        // Standard modules with code in odd places should only have code in those directories detected
825
        $otherFiles = $collector->getFileListForModule_Test('i18nothermodule');
826
        $otherRoot = $this->alternateBasePath . '/i18nothermodule';
827
        $this->assertEquals(4, count($otherFiles));
828
        // Only detect well-behaved files
829
        $this->assertArrayHasKey("{$otherRoot}/code/i18nOtherModule.php", $otherFiles);
830
        $this->assertArrayHasKey("{$otherRoot}/code/i18nProviderClass.php", $otherFiles);
831
        $this->assertArrayHasKey("{$otherRoot}/code/i18nTestModuleDecorator.php", $otherFiles);
832
        $this->assertArrayHasKey("{$otherRoot}/templates/i18nOtherModule.ss", $otherFiles);
833
834
        // Themes should detect all ss files only
835
        $theme1Files = $collector->getFileListForModule_Test('themes/testtheme1');
836
        $theme1Root = $this->alternateBasePath . '/themes/testtheme1/templates';
837
        $this->assertEquals(3, count($theme1Files));
838
        // Find only ss files
839
        $this->assertArrayHasKey("{$theme1Root}/Includes/i18nTestTheme1Include.ss", $theme1Files);
840
        $this->assertArrayHasKey("{$theme1Root}/Layout/i18nTestTheme1.ss", $theme1Files);
841
        $this->assertArrayHasKey("{$theme1Root}/i18nTestTheme1Main.ss", $theme1Files);
842
843
        // Only 1 file here
844
        $theme2Files = $collector->getFileListForModule_Test('themes/testtheme2');
845
        $this->assertEquals(1, count($theme2Files));
846
        $this->assertArrayHasKey(
847
            $this->alternateBasePath . '/themes/testtheme2/templates/i18nTestTheme2.ss',
848
            $theme2Files
849
        );
850
    }
851
}
852