Passed
Push — 4 ( f1d8a0...d1c927 )
by Robbie
09:11
created

testAddI18nJavascriptWithMissingLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\View\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\i18n\i18n;
10
use SilverStripe\View\Requirements;
11
use SilverStripe\View\ArrayData;
12
use Silverstripe\Assets\Dev\TestAssetStore;
13
use SilverStripe\View\Requirements_Backend;
14
use SilverStripe\Core\Manifest\ResourceURLGenerator;
15
use SilverStripe\Control\SimpleResourceURLGenerator;
16
use SilverStripe\View\SSViewer;
17
use SilverStripe\View\ThemeResourceLoader;
18
19
/**
20
 * @todo Test that order of combine_files() is correct
21
 * @todo Figure out how to clear the modified state of Requirements class - might affect other tests.
22
 * @skipUpgrade
23
 */
24
class RequirementsTest extends SapphireTest
25
{
26
27
    /**
28
     * @var ThemeResourceLoader
29
     */
30
    protected $oldThemeResourceLoader = null;
31
32
    static $html_template = '<html><head></head><body></body></html>';
33
34
    protected function setUp()
35
    {
36
        parent::setUp();
37
        Director::config()->set('alternate_base_folder', __DIR__ . '/SSViewerTest');
38
        Director::config()->set('alternate_base_url', 'http://www.mysite.com/basedir/');
39
        Director::config()->set('alternate_public_dir', 'public'); // Enforce public dir
40
        // Add public as a theme in itself
41
        SSViewer::set_themes([SSViewer::PUBLIC_THEME, SSViewer::DEFAULT_THEME]);
42
        TestAssetStore::activate('RequirementsTest'); // Set backend root to /RequirementsTest
43
        $this->oldThemeResourceLoader = ThemeResourceLoader::inst();
44
    }
45
46
    protected function tearDown()
47
    {
48
        ThemeResourceLoader::set_instance($this->oldThemeResourceLoader);
49
        TestAssetStore::reset();
50
        parent::tearDown();
51
    }
52
53
    public function testExternalUrls()
54
    {
55
        /** @var Requirements_Backend $backend */
56
        $backend = Injector::inst()->create(Requirements_Backend::class);
57
        $backend->setCombinedFilesEnabled(true);
58
59
        $backend->javascript('http://www.mydomain.com/test.js');
60
        $backend->javascript('https://www.mysecuredomain.com/test.js');
61
        $backend->javascript('//scheme-relative.example.com/test.js');
62
        $backend->javascript('http://www.mydomain.com:3000/test.js');
63
        $backend->css('http://www.mydomain.com/test.css');
64
        $backend->css('https://www.mysecuredomain.com/test.css');
65
        $backend->css('//scheme-relative.example.com/test.css');
66
        $backend->css('http://www.mydomain.com:3000/test.css');
67
68
        $html = $backend->includeInHTML(self::$html_template);
69
70
        $this->assertContains('http://www.mydomain.com/test.js', $html, 'Load external javascript URL');
71
        $this->assertContains('https://www.mysecuredomain.com/test.js', $html, 'Load external secure javascript URL');
72
        $this->assertContains('//scheme-relative.example.com/test.js', $html, 'Load external scheme-relative JS');
73
        $this->assertContains('http://www.mydomain.com:3000/test.js', $html, 'Load external with port');
74
        $this->assertContains('http://www.mydomain.com/test.css', $html, 'Load external CSS URL');
75
        $this->assertContains('https://www.mysecuredomain.com/test.css', $html, 'Load external secure CSS URL');
76
        $this->assertContains('//scheme-relative.example.com/test.css', $html, 'Load scheme-relative CSS URL');
77
        $this->assertContains('http://www.mydomain.com:3000/test.css', $html, 'Load external with port');
78
    }
79
80
    /**
81
     * Setup new backend
82
     *
83
     * @param Requirements_Backend $backend
84
     */
85
    protected function setupRequirements($backend)
86
    {
87
        // Flush requirements
88
        $backend->clear();
89
        $backend->clearCombinedFiles();
90
        $backend->setCombinedFilesFolder('_combinedfiles');
91
        $backend->setMinifyCombinedFiles(false);
92
        $backend->setCombinedFilesEnabled(true);
93
        Requirements::flush();
94
    }
95
96
    /**
97
     * Setup combined and non-combined js with the backend
98
     *
99
     * @param Requirements_Backend $backend
100
     */
101
    protected function setupCombinedRequirements($backend)
102
    {
103
        $this->setupRequirements($backend);
104
105
        // require files normally (e.g. called from a FormField instance)
106
        $backend->javascript('javascript/RequirementsTest_a.js');
107
        $backend->javascript('javascript/RequirementsTest_b.js');
108
        $backend->javascript('javascript/RequirementsTest_c.js');
109
110
        // Public resources may or may not be specified with `public/` prefix
111
        $backend->javascript('javascript/RequirementsTest_d.js');
112
        $backend->javascript('public/javascript/RequirementsTest_e.js');
113
114
        // require two of those files as combined includes
115
        $backend->combineFiles(
116
            'RequirementsTest_bc.js',
117
            array(
118
                'javascript/RequirementsTest_b.js',
119
                'javascript/RequirementsTest_c.js'
120
            )
121
        );
122
    }
123
124
    /**
125
     * Setup combined files with the backend
126
     *
127
     * @param Requirements_Backend $backend
128
     */
129
    protected function setupCombinedNonrequiredRequirements($backend)
130
    {
131
        $this->setupRequirements($backend);
132
133
        // require files as combined includes
134
        $backend->combineFiles(
135
            'RequirementsTest_bc.js',
136
            array(
137
                'javascript/RequirementsTest_b.js',
138
                'javascript/RequirementsTest_c.js'
139
            )
140
        );
141
    }
142
143
    /**
144
     * @param Requirements_Backend $backend
145
     * @param bool                 $async
146
     * @param bool                 $defer
147
     */
148
    protected function setupCombinedRequirementsJavascriptAsyncDefer($backend, $async, $defer)
149
    {
150
        $this->setupRequirements($backend);
151
152
        // require files normally (e.g. called from a FormField instance)
153
        $backend->javascript('javascript/RequirementsTest_a.js');
154
        $backend->javascript('javascript/RequirementsTest_b.js');
155
        $backend->javascript('javascript/RequirementsTest_c.js');
156
157
        // require two of those files as combined includes
158
        $backend->combineFiles(
159
            'RequirementsTest_bc.js',
160
            [
161
                'javascript/RequirementsTest_b.js',
162
                'javascript/RequirementsTest_c.js'
163
            ],
164
            [
165
                'async' => $async,
166
                'defer' => $defer,
167
            ]
168
        );
169
    }
170
171
    public function testCustomType()
172
    {
173
        /** @var Requirements_Backend $backend */
174
        $backend = Injector::inst()->create(Requirements_Backend::class);
175
        $this->setupRequirements($backend);
176
177
        // require files normally (e.g. called from a FormField instance)
178
        $backend->javascript(
179
            'javascript/RequirementsTest_a.js',
180
            [ 'type' => 'application/json' ]
181
        );
182
        $backend->javascript('javascript/RequirementsTest_b.js');
183
        $result = $backend->includeInHTML(self::$html_template);
184
        $this->assertRegExp(
185
            '#<script type="application/json" src=".*/javascript/RequirementsTest_a.js#',
186
            $result
187
        );
188
        $this->assertRegExp(
189
            '#<script type="application/javascript" src=".*/javascript/RequirementsTest_b.js#',
190
            $result
191
        );
192
    }
193
194
    public function testCombinedJavascript()
195
    {
196
        /** @var Requirements_Backend $backend */
197
        $backend = Injector::inst()->create(Requirements_Backend::class);
198
        $backend->setCombinedFilesEnabled(true);
199
        $this->setupCombinedRequirements($backend);
200
201
        $combinedFileName = '/_combinedfiles/RequirementsTest_bc-2a55d56.js';
202
        $combinedFilePath = TestAssetStore::base_path() . $combinedFileName;
203
204
        $html = $backend->includeInHTML(self::$html_template);
205
206
        /* COMBINED JAVASCRIPT FILE IS INCLUDED IN HTML HEADER */
207
        $this->assertRegExp(
208
            '/src=".*' . preg_quote($combinedFileName, '/') . '/',
209
            $html,
210
            'combined javascript file is included in html header'
211
        );
212
213
        /* COMBINED JAVASCRIPT FILE EXISTS */
214
        $this->assertTrue(
215
            file_exists($combinedFilePath),
216
            'combined javascript file exists'
217
        );
218
219
        /* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
220
        $this->assertContains(
221
            "alert('b')",
222
            file_get_contents($combinedFilePath),
223
            'combined javascript has correct content'
224
        );
225
        $this->assertContains(
226
            "alert('c')",
227
            file_get_contents($combinedFilePath),
228
            'combined javascript has correct content'
229
        );
230
231
        /* COMBINED FILES ARE NOT INCLUDED TWICE */
232
        $this->assertNotRegExp(
233
            '/src=".*\/RequirementsTest_b\.js/',
234
            $html,
235
            'combined files are not included twice'
236
        );
237
        $this->assertNotRegExp(
238
            '/src=".*\/RequirementsTest_c\.js/',
239
            $html,
240
            'combined files are not included twice'
241
        );
242
243
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
244
        $this->assertRegExp(
245
            '/src=".*\/RequirementsTest_a\.js/',
246
            $html,
247
            'normal requirements are still included'
248
        );
249
250
        // Then do it again, this time not requiring the files beforehand
251
        unlink($combinedFilePath);
252
        /** @var Requirements_Backend $backend */
253
        $backend = Injector::inst()->create(Requirements_Backend::class);
254
        $this->setupCombinedNonrequiredRequirements($backend);
255
        $html = $backend->includeInHTML(self::$html_template);
256
257
        /* COMBINED JAVASCRIPT FILE IS INCLUDED IN HTML HEADER */
258
        $this->assertRegExp(
259
            '/src=".*' . preg_quote($combinedFileName, '/') . '/',
260
            $html,
261
            'combined javascript file is included in html header'
262
        );
263
264
        /* COMBINED JAVASCRIPT FILE EXISTS */
265
        $this->assertTrue(
266
            file_exists($combinedFilePath),
267
            'combined javascript file exists'
268
        );
269
270
        /* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
271
        $this->assertContains(
272
            "alert('b')",
273
            file_get_contents($combinedFilePath),
274
            'combined javascript has correct content'
275
        );
276
        $this->assertContains(
277
            "alert('c')",
278
            file_get_contents($combinedFilePath),
279
            'combined javascript has correct content'
280
        );
281
282
        /* COMBINED FILES ARE NOT INCLUDED TWICE */
283
        $this->assertNotRegExp(
284
            '/src=".*\/RequirementsTest_b\.js/',
285
            $html,
286
            'combined files are not included twice'
287
        );
288
        $this->assertNotRegExp(
289
            '/src=".*\/RequirementsTest_c\.js/',
290
            $html,
291
            'combined files are not included twice'
292
        );
293
    }
294
295
    public function testCombinedJavascriptAsyncDefer()
296
    {
297
        /** @var Requirements_Backend $backend */
298
        $backend = Injector::inst()->create(Requirements_Backend::class);
299
300
        $this->setupCombinedRequirementsJavascriptAsyncDefer($backend, true, false);
301
302
        $combinedFileName = '/_combinedfiles/RequirementsTest_bc-2a55d56.js';
303
        $combinedFilePath = TestAssetStore::base_path() . $combinedFileName;
304
305
        $html = $backend->includeInHTML(false, self::$html_template);
306
307
        /* ASYNC IS INCLUDED IN SCRIPT TAG */
308
        $this->assertRegExp(
309
            '/src=".*' . preg_quote($combinedFileName, '/') . '" async/',
310
            $html,
311
            'async is included in script tag'
312
        );
313
314
        /* DEFER IS NOT INCLUDED IN SCRIPT TAG */
315
        $this->assertNotContains('defer', $html, 'defer is not included');
316
317
        /* COMBINED JAVASCRIPT FILE EXISTS */
318
        clearstatcache(); // needed to get accurate file_exists() results
319
        $this->assertFileExists(
320
            $combinedFilePath,
321
            'combined javascript file exists'
322
        );
323
324
        /* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
325
        $this->assertContains(
326
            "alert('b')",
327
            file_get_contents($combinedFilePath),
328
            'combined javascript has correct content'
329
        );
330
        $this->assertContains(
331
            "alert('c')",
332
            file_get_contents($combinedFilePath),
333
            'combined javascript has correct content'
334
        );
335
336
        /* COMBINED FILES ARE NOT INCLUDED TWICE */
337
        $this->assertNotRegExp(
338
            '/src=".*\/RequirementsTest_b\.js/',
339
            $html,
340
            'combined files are not included twice'
341
        );
342
        $this->assertNotRegExp(
343
            '/src=".*\/RequirementsTest_c\.js/',
344
            $html,
345
            'combined files are not included twice'
346
        );
347
348
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
349
        $this->assertRegExp(
350
            '/src=".*\/RequirementsTest_a\.js/',
351
            $html,
352
            'normal requirements are still included'
353
        );
354
355
        /* NORMAL REQUIREMENTS DON'T HAVE ASYNC/DEFER */
356
        $this->assertNotRegExp(
357
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
358
            $html,
359
            'normal requirements don\'t have async'
360
        );
361
        $this->assertNotRegExp(
362
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
363
            $html,
364
            'normal requirements don\'t have defer'
365
        );
366
        $this->assertNotRegExp(
367
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async defer/',
368
            $html,
369
            'normal requirements don\'t have async/defer'
370
        );
371
372
        // setup again for testing defer
373
        unlink($combinedFilePath);
374
        /** @var Requirements_Backend $backend */
375
        $backend = Injector::inst()->create(Requirements_Backend::class);
376
377
        $this->setupCombinedRequirementsJavascriptAsyncDefer($backend, false, true);
378
379
        $html = $backend->includeInHTML(self::$html_template);
380
381
        /* DEFER IS INCLUDED IN SCRIPT TAG */
382
        $this->assertRegExp(
383
            '/src=".*' . preg_quote($combinedFileName, '/') . '" defer/',
384
            $html,
385
            'defer is included in script tag'
386
        );
387
388
        /* ASYNC IS NOT INCLUDED IN SCRIPT TAG */
389
        $this->assertNotContains('async', $html, 'async is not included');
390
391
        /* COMBINED JAVASCRIPT FILE EXISTS */
392
        clearstatcache(); // needed to get accurate file_exists() results
393
        $this->assertFileExists(
394
            $combinedFilePath,
395
            'combined javascript file exists'
396
        );
397
398
        /* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
399
        $this->assertContains(
400
            "alert('b')",
401
            file_get_contents($combinedFilePath),
402
            'combined javascript has correct content'
403
        );
404
        $this->assertContains(
405
            "alert('c')",
406
            file_get_contents($combinedFilePath),
407
            'combined javascript has correct content'
408
        );
409
410
        /* COMBINED FILES ARE NOT INCLUDED TWICE */
411
        $this->assertNotRegExp(
412
            '/src=".*\/RequirementsTest_b\.js/',
413
            $html,
414
            'combined files are not included twice'
415
        );
416
        $this->assertNotRegExp(
417
            '/src=".*\/RequirementsTest_c\.js/',
418
            $html,
419
            'combined files are not included twice'
420
        );
421
422
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
423
        $this->assertRegExp(
424
            '/src=".*\/RequirementsTest_a\.js/',
425
            $html,
426
            'normal requirements are still included'
427
        );
428
429
        /* NORMAL REQUIREMENTS DON'T HAVE ASYNC/DEFER */
430
        $this->assertNotRegExp(
431
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
432
            $html,
433
            'normal requirements don\'t have async'
434
        );
435
        $this->assertNotRegExp(
436
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
437
            $html,
438
            'normal requirements don\'t have defer'
439
        );
440
        $this->assertNotRegExp(
441
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async defer/',
442
            $html,
443
            'normal requirements don\'t have async/defer'
444
        );
445
446
        // setup again for testing async and defer
447
        unlink($combinedFilePath);
448
        /** @var Requirements_Backend $backend */
449
        $backend = Injector::inst()->create(Requirements_Backend::class);
450
451
        $this->setupCombinedRequirementsJavascriptAsyncDefer($backend, true, true);
452
453
        $html = $backend->includeInHTML(self::$html_template);
454
455
        /* ASYNC/DEFER IS INCLUDED IN SCRIPT TAG */
456
        $this->assertRegExp(
457
            '/src=".*' . preg_quote($combinedFileName, '/') . '" async="async" defer="defer"/',
458
            $html,
459
            'async and defer are included in script tag'
460
        );
461
462
        /* COMBINED JAVASCRIPT FILE EXISTS */
463
        clearstatcache(); // needed to get accurate file_exists() results
464
        $this->assertFileExists(
465
            $combinedFilePath,
466
            'combined javascript file exists'
467
        );
468
469
        /* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
470
        $this->assertContains(
471
            "alert('b')",
472
            file_get_contents($combinedFilePath),
473
            'combined javascript has correct content'
474
        );
475
        $this->assertContains(
476
            "alert('c')",
477
            file_get_contents($combinedFilePath),
478
            'combined javascript has correct content'
479
        );
480
481
        /* COMBINED FILES ARE NOT INCLUDED TWICE */
482
        $this->assertNotRegExp(
483
            '/src=".*\/RequirementsTest_b\.js/',
484
            $html,
485
            'combined files are not included twice'
486
        );
487
        $this->assertNotRegExp(
488
            '/src=".*\/RequirementsTest_c\.js/',
489
            $html,
490
            'combined files are not included twice'
491
        );
492
493
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
494
        $this->assertRegExp(
495
            '/src=".*\/RequirementsTest_a\.js/',
496
            $html,
497
            'normal requirements are still included'
498
        );
499
500
        /* NORMAL REQUIREMENTS DON'T HAVE ASYNC/DEFER */
501
        $this->assertNotRegExp(
502
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
503
            $html,
504
            'normal requirements don\'t have async'
505
        );
506
        $this->assertNotRegExp(
507
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
508
            $html,
509
            'normal requirements don\'t have defer'
510
        );
511
        $this->assertNotRegExp(
512
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async defer/',
513
            $html,
514
            'normal requirements don\'t have async/defer'
515
        );
516
517
        unlink($combinedFilePath);
518
    }
519
520
    public function testCombinedCss()
521
    {
522
        /** @var Requirements_Backend $backend */
523
        $backend = Injector::inst()->create(Requirements_Backend::class);
524
        $this->setupRequirements($backend);
525
526
        $backend->combineFiles(
527
            'print.css',
528
            [
529
                'css/RequirementsTest_print_a.css',
530
                'css/RequirementsTest_print_b.css',
531
                'css/RequirementsTest_print_d.css',
532
                'public/css/RequirementsTest_print_e.css',
533
            ],
534
            [
535
                'media' => 'print'
536
            ]
537
        );
538
539
        $html = $backend->includeInHTML(self::$html_template);
540
541
        $this->assertRegExp(
542
            '/href=".*\/print\-69ce614\.css/',
543
            $html,
544
            'Print stylesheets have been combined.'
545
        );
546
        $this->assertRegExp(
547
            '/media="print/',
548
            $html,
549
            'Combined print stylesheet retains the media parameter'
550
        );
551
552
        // Test that combining a file multiple times doesn't trigger an error
553
        /** @var Requirements_Backend $backend */
554
        $backend = Injector::inst()->create(Requirements_Backend::class);
555
        $this->setupRequirements($backend);
556
        $backend->combineFiles(
557
            'style.css',
558
            [
559
                'css/RequirementsTest_b.css',
560
                'css/RequirementsTest_c.css',
561
                'css/RequirementsTest_d.css',
562
                'public/css/RequirementsTest_e.css',
563
            ]
564
        );
565
        $backend->combineFiles(
566
            'style.css',
567
            array(
568
                'css/RequirementsTest_b.css',
569
                'css/RequirementsTest_c.css',
570
                'css/RequirementsTest_d.css',
571
                'public/css/RequirementsTest_e.css',
572
            )
573
        );
574
575
        $html = $backend->includeInHTML(self::$html_template);
576
        $this->assertRegExp(
577
            '/href=".*\/style\-8011538\.css/',
578
            $html,
579
            'Stylesheets have been combined.'
580
        );
581
    }
582
583
    public function testBlockedCombinedJavascript()
584
    {
585
        /** @var Requirements_Backend $backend */
586
        $backend = Injector::inst()->create(Requirements_Backend::class);
587
        $this->setupCombinedRequirements($backend);
588
        $combinedFileName = '/_combinedfiles/RequirementsTest_bc-2a55d56.js';
589
        $combinedFilePath = TestAssetStore::base_path() . $combinedFileName;
590
591
        /* BLOCKED COMBINED FILES ARE NOT INCLUDED */
592
        $backend->block('RequirementsTest_bc.js');
593
594
        clearstatcache(); // needed to get accurate file_exists() results
595
        $html = $backend->includeInHTML(self::$html_template);
596
        $this->assertFileNotExists($combinedFilePath);
597
        $this->assertNotRegExp(
598
            '/src=".*\/RequirementsTest_bc\.js/',
599
            $html,
600
            'blocked combined files are not included'
601
        );
602
        $backend->unblock('RequirementsTest_bc.js');
603
604
        /* BLOCKED UNCOMBINED FILES ARE NOT INCLUDED */
605
        $this->setupCombinedRequirements($backend);
606
        $backend->block('javascript/RequirementsTest_b.js');
607
        $combinedFileName2 = '/_combinedfiles/RequirementsTest_bc-3748f67.js'; // SHA1 without file b included
608
        $combinedFilePath2 = TestAssetStore::base_path() . $combinedFileName2;
609
        clearstatcache(); // needed to get accurate file_exists() results
610
        $backend->includeInHTML(self::$html_template);
611
        $this->assertFileExists($combinedFilePath2);
612
        $this->assertNotContains(
613
            "alert('b')",
614
            file_get_contents($combinedFilePath2),
615
            'blocked uncombined files are not included'
616
        );
617
        $backend->unblock('javascript/RequirementsTest_b.js');
618
619
        /* A SINGLE FILE CAN'T BE INCLUDED IN TWO COMBINED FILES */
620
        $this->setupCombinedRequirements($backend);
621
        clearstatcache(); // needed to get accurate file_exists() results
622
623
        // Exception generated from including invalid file
624
        $this->expectException(InvalidArgumentException::class);
625
        $this->expectExceptionMessage(sprintf(
626
            "Requirements_Backend::combine_files(): Already included file(s) %s in combined file '%s'",
627
            'javascript/RequirementsTest_c.js',
628
            'RequirementsTest_bc.js'
629
        ));
630
        $backend->combineFiles(
631
            'RequirementsTest_ac.js',
632
            [
633
                'javascript/RequirementsTest_a.js',
634
                'javascript/RequirementsTest_c.js'
635
            ]
636
        );
637
    }
638
639
    public function testArgsInUrls()
640
    {
641
        /** @var Requirements_Backend $backend */
642
        $backend = Injector::inst()->create(Requirements_Backend::class);
643
        $this->setupRequirements($backend);
644
645
        $backend->javascript('javascript/RequirementsTest_a.js?test=1&test=2&test=3');
646
        $backend->css('css/RequirementsTest_a.css?test=1&test=2&test=3');
647
        $html = $backend->includeInHTML(self::$html_template);
648
649
        /* Javascript has correct path */
650
        $this->assertRegExp(
651
            '/src=".*\/RequirementsTest_a\.js\?test=1&amp;test=2&amp;test=3&amp;m=\d\d+/',
652
            $html,
653
            'javascript has correct path'
654
        );
655
656
        /* CSS has correct path */
657
        $this->assertRegExp(
658
            '/href=".*\/RequirementsTest_a\.css\?test=1&amp;test=2&amp;test=3&amp;m=\d\d+/',
659
            $html,
660
            'css has correct path'
661
        );
662
    }
663
664
    public function testRequirementsBackend()
665
    {
666
        /** @var Requirements_Backend $backend */
667
        $backend = Injector::inst()->create(Requirements_Backend::class);
668
        $this->setupRequirements($backend);
669
        $backend->javascript('a.js');
670
671
        $this->assertCount(
672
            1,
673
            $backend->getJavascript(),
674
            "There should be only 1 file included in required javascript."
675
        );
676
        $this->assertArrayHasKey(
677
            'a.js',
678
            $backend->getJavascript(),
679
            "a.js should be included in required javascript."
680
        );
681
682
        $backend->javascript('b.js');
683
        $this->assertCount(
684
            2,
685
            $backend->getJavascript(),
686
            "There should be 2 files included in required javascript."
687
        );
688
689
        $backend->block('a.js');
690
        $this->assertCount(
691
            1,
692
            $backend->getJavascript(),
693
            "There should be only 1 file included in required javascript."
694
        );
695
        $this->assertArrayNotHasKey(
696
            'a.js',
697
            $backend->getJavascript(),
698
            "a.js should not be included in required javascript after it has been blocked."
699
        );
700
        $this->assertArrayHasKey(
701
            'b.js',
702
            $backend->getJavascript(),
703
            "b.js should be included in required javascript."
704
        );
705
706
        $backend->css('a.css');
707
        $this->assertCount(
708
            1,
709
            $backend->getCSS(),
710
            "There should be only 1 file included in required css."
711
        );
712
        $this->assertArrayHasKey(
713
            'a.css',
714
            $backend->getCSS(),
715
            "a.css should be in required css."
716
        );
717
718
        $backend->block('a.css');
719
        $this->assertCount(
720
            0,
721
            $backend->getCSS(),
722
            "There should be nothing in required css after file has been blocked."
723
        );
724
    }
725
726
    public function testAppendAndBlockWithModuleResourceLoader()
727
    {
728
        /** @var Requirements_Backend $backend */
729
        $backend = Injector::inst()->create(Requirements_Backend::class);
730
        $this->setupRequirements($backend);
731
732
        // Note: assumes that client/styles/debug.css is "exposed"
733
        $backend->css('silverstripe/framework:client/styles/debug.css');
734
        $this->assertCount(
735
            1,
736
            $backend->getCSS(),
737
            'Module resource can be loaded via resources reference'
738
        );
739
740
        $backend->block('silverstripe/framework:client/styles/debug.css');
741
        $this->assertCount(
742
            0,
743
            $backend->getCSS(),
744
            'Module resource can be blocked via resources reference'
745
        );
746
    }
747
748
    public function testConditionalTemplateRequire()
749
    {
750
        // Set /SSViewerTest and /SSViewerTest/public as themes
751
        SSViewer::set_themes([
752
            '/',
753
            SSViewer::PUBLIC_THEME
754
        ]);
755
        ThemeResourceLoader::set_instance(new ThemeResourceLoader(__DIR__ . '/SSViewerTest'));
756
757
        /** @var Requirements_Backend $backend */
758
        $backend = Injector::inst()->create(Requirements_Backend::class);
759
        $this->setupRequirements($backend);
760
        $holder = Requirements::backend();
761
        Requirements::set_backend($backend);
762
        $data = new ArrayData([
763
            'FailTest' => true,
764
        ]);
765
766
        $data->renderWith('RequirementsTest_Conditionals');
767
        $this->assertFileIncluded($backend, 'css', 'css/RequirementsTest_a.css');
768
        $this->assertFileIncluded(
769
            $backend,
770
            'js',
771
            [
772
                'javascript/RequirementsTest_b.js',
773
                'javascript/RequirementsTest_c.js'
774
            ]
775
        );
776
        $this->assertFileNotIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
777
        $this->assertFileNotIncluded(
778
            $backend,
779
            'css',
780
            [
781
                'css/RequirementsTest_b.css',
782
                'css/RequirementsTest_c.css'
783
            ]
784
        );
785
        $backend->clear();
786
        $data = new ArrayData(
787
            array(
788
            'FailTest' => false,
789
            )
790
        );
791
        $data->renderWith('RequirementsTest_Conditionals');
792
        $this->assertFileNotIncluded($backend, 'css', 'css/RequirementsTest_a.css');
793
        $this->assertFileNotIncluded(
794
            $backend,
795
            'js',
796
            [
797
                'javascript/RequirementsTest_b.js',
798
                'javascript/RequirementsTest_c.js',
799
            ]
800
        );
801
        $this->assertFileIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
802
        $this->assertFileIncluded(
803
            $backend,
804
            'css',
805
            [
806
                'css/RequirementsTest_b.css',
807
                'css/RequirementsTest_c.css',
808
            ]
809
        );
810
        Requirements::set_backend($holder);
811
    }
812
813
    public function testJsWriteToBody()
814
    {
815
        /** @var Requirements_Backend $backend */
816
        $backend = Injector::inst()->create(Requirements_Backend::class);
817
        $this->setupRequirements($backend);
818
        $backend->javascript('http://www.mydomain.com/test.js');
819
820
        // Test matching with HTML5 <header> tags as well
821
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
822
823
        $backend->setWriteJavascriptToBody(false);
824
        $html = $backend->includeInHTML($template);
825
        $this->assertContains('<head><script', $html);
826
827
        $backend->setWriteJavascriptToBody(true);
828
        $html = $backend->includeInHTML($template);
829
        $this->assertNotContains('<head><script', $html);
830
        $this->assertContains("</script>\n</body>", $html);
831
    }
832
833
    public function testIncludedJsIsNotCommentedOut()
834
    {
835
        $template = '<html><head></head><body><!--<script>alert("commented out");</script>--></body></html>';
836
        /** @var Requirements_Backend $backend */
837
        $backend = Injector::inst()->create(Requirements_Backend::class);
838
        $this->setupRequirements($backend);
839
        $backend->javascript('javascript/RequirementsTest_a.js');
840
        $html = $backend->includeInHTML($template);
841
        //wiping out commented-out html
842
        $html = preg_replace('/<!--(.*)-->/Uis', '', $html);
843
        $this->assertContains("RequirementsTest_a.js", $html);
844
    }
845
846
    public function testCommentedOutScriptTagIsIgnored()
847
    {
848
        /// Disable nonce
849
        $urlGenerator = new SimpleResourceURLGenerator();
850
        Injector::inst()->registerService($urlGenerator, ResourceURLGenerator::class);
851
852
        $template = '<html><head></head><body><!--<script>alert("commented out");</script>-->'
853
            . '<h1>more content</h1></body></html>';
854
        /** @var Requirements_Backend $backend */
855
        $backend = Injector::inst()->create(Requirements_Backend::class);
856
        $this->setupRequirements($backend);
857
858
        $src = 'javascript/RequirementsTest_a.js';
859
        $backend->javascript($src);
860
        $html = $backend->includeInHTML($template);
861
        $urlSrc = $urlGenerator->urlForResource($src);
862
        $this->assertEquals(
863
            '<html><head></head><body><!--<script>alert("commented out");</script>-->'
864
            . '<h1>more content</h1><script type="application/javascript" src="' . $urlSrc
865
            . "\"></script>\n</body></html>",
866
            $html
867
        );
868
    }
869
870
    public function testForceJsToBottom()
871
    {
872
        /** @var Requirements_Backend $backend */
873
        $backend = Injector::inst()->create(Requirements_Backend::class);
874
        $this->setupRequirements($backend);
875
        $backend->javascript('http://www.mydomain.com/test.js');
876
        $backend->customScript(
877
            <<<'EOS'
878
var globalvar = {
879
	pattern: '\\$custom\\1'
880
};
881
EOS
882
        );
883
884
        // Test matching with HTML5 <header> tags as well
885
        $template = '<html><head></head><body><header>My header</header><p>Body<script></script></p></body></html>';
886
887
        // The expected outputs
888
        $expectedScripts = "<script type=\"application/javascript\" src=\"http://www.mydomain.com/test.js\"></script>\n"
889
            . "<script type=\"application/javascript\">//<![CDATA[\n"
890
            . "var globalvar = {\n\tpattern: '\\\\\$custom\\\\1'\n};\n"
891
            . "//]]></script>\n";
892
        $JsInHead = "<html><head>$expectedScripts</head><body><header>My header</header><p>Body<script></script></p></body></html>";
893
        $JsInBody = "<html><head></head><body><header>My header</header><p>Body$expectedScripts<script></script></p></body></html>";
894
        $JsAtEnd  = "<html><head></head><body><header>My header</header><p>Body<script></script></p>$expectedScripts</body></html>";
895
896
897
        // Test if the script is before the head tag, not before the body.
898
        // Expected: $JsInHead
899
        $backend->setWriteJavascriptToBody(false);
900
        $backend->setForceJSToBottom(false);
901
        $html = $backend->includeInHTML($template);
902
        $this->assertNotEquals($JsInBody, $html);
903
        $this->assertNotEquals($JsAtEnd, $html);
904
        $this->assertEquals($JsInHead, $html);
905
906
        // Test if the script is before the first <script> tag, not before the body.
907
        // Expected: $JsInBody
908
        $backend->setWriteJavascriptToBody(true);
909
        $backend->setForceJSToBottom(false);
910
        $html = $backend->includeInHTML($template);
911
        $this->assertNotEquals($JsAtEnd, $html);
912
        $this->assertEquals($JsInBody, $html);
913
914
        // Test if the script is placed just before the closing bodytag, with write-to-body false.
915
        // Expected: $JsAtEnd
916
        $backend->setWriteJavascriptToBody(false);
917
        $backend->setForceJSToBottom(true);
918
        $html = $backend->includeInHTML($template);
919
        $this->assertNotEquals($JsInHead, $html);
920
        $this->assertNotEquals($JsInBody, $html);
921
        $this->assertEquals($JsAtEnd, $html);
922
923
        // Test if the script is placed just before the closing bodytag, with write-to-body true.
924
        // Expected: $JsAtEnd
925
        $backend->setWriteJavascriptToBody(true);
926
        $backend->setForceJSToBottom(true);
927
        $html = $backend->includeInHTML($template);
928
        $this->assertNotEquals($JsInHead, $html);
929
        $this->assertNotEquals($JsInBody, $html);
930
        $this->assertEquals($JsAtEnd, $html);
931
    }
932
933
    public function testSuffix()
934
    {
935
        /// Disable nonce
936
        $urlGenerator = new SimpleResourceURLGenerator();
937
        Injector::inst()->registerService($urlGenerator, ResourceURLGenerator::class);
938
939
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
940
941
        /** @var Requirements_Backend $backend */
942
        $backend = Injector::inst()->create(Requirements_Backend::class);
943
        $this->setupRequirements($backend);
944
945
        $backend->javascript('javascript/RequirementsTest_a.js');
946
        $backend->javascript('javascript/RequirementsTest_b.js?foo=bar&bla=blubb');
947
        $backend->css('css/RequirementsTest_a.css');
948
        $backend->css('css/RequirementsTest_b.css?foo=bar&bla=blubb');
949
950
        $urlGenerator->setNonceStyle('mtime');
951
        $html = $backend->includeInHTML($template);
952
        $this->assertRegExp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
953
        $this->assertRegExp('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
954
        $this->assertRegExp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
955
        $this->assertRegExp('/RequirementsTest_b\.css\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
956
957
        $urlGenerator->setNonceStyle(null);
958
        $html = $backend->includeInHTML($template);
959
        $this->assertNotContains('RequirementsTest_a.js=', $html);
960
        $this->assertNotRegExp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
961
        $this->assertNotRegExp('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
962
        $this->assertNotRegExp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
963
        $this->assertNotRegExp('/RequirementsTest_b\.css\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
964
    }
965
966
    /**
967
     * Tests that provided files work
968
     */
969
    public function testProvidedFiles()
970
    {
971
        /** @var Requirements_Backend $backend */
972
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
973
974
        // Test that provided files block subsequent files
975
        $backend = Injector::inst()->create(Requirements_Backend::class);
976
        $this->setupRequirements($backend);
977
        $backend->javascript('javascript/RequirementsTest_a.js');
978
        $backend->javascript(
979
            'javascript/RequirementsTest_b.js',
980
            [
981
            'provides' => [
982
                    'javascript/RequirementsTest_a.js',
983
                    'javascript/RequirementsTest_c.js',
984
                ],
985
            ]
986
        );
987
        $backend->javascript('javascript/RequirementsTest_c.js');
988
        // Note that _a.js isn't considered provided because it was included
989
        // before it was marked as provided
990
        $this->assertEquals(
991
            [
992
                'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
993
            ],
994
            $backend->getProvidedScripts()
995
        );
996
        $html = $backend->includeInHTML($template);
997
        $this->assertRegExp('/src=".*\/RequirementsTest_a\.js/', $html);
998
        $this->assertRegExp('/src=".*\/RequirementsTest_b\.js/', $html);
999
        $this->assertNotRegExp('/src=".*\/RequirementsTest_c\.js/', $html);
1000
1001
        // Test that provided files block subsequent combined files
1002
        $backend = Injector::inst()->create(Requirements_Backend::class);
1003
        $this->setupRequirements($backend);
1004
        $backend->combineFiles('combined_a.js', ['javascript/RequirementsTest_a.js']);
1005
        $backend->javascript(
1006
            'javascript/RequirementsTest_b.js',
1007
            [
1008
            'provides' => [
1009
                'javascript/RequirementsTest_a.js',
1010
                'javascript/RequirementsTest_c.js'
1011
            ]
1012
            ]
1013
        );
1014
        $backend->combineFiles('combined_c.js', ['javascript/RequirementsTest_c.js']);
1015
        $this->assertEquals(
1016
            [
1017
                'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
1018
            ],
1019
            $backend->getProvidedScripts()
1020
        );
1021
        $html = $backend->includeInHTML($template);
1022
        $this->assertRegExp('/src=".*\/combined_a/', $html);
1023
        $this->assertRegExp('/src=".*\/RequirementsTest_b\.js/', $html);
1024
        $this->assertNotRegExp('/src=".*\/combined_c/', $html);
1025
        $this->assertNotRegExp('/src=".*\/RequirementsTest_c\.js/', $html);
1026
    }
1027
1028
    /**
1029
     * Verify that the given backend includes the given files
1030
     *
1031
     * @param Requirements_Backend $backend
1032
     * @param string               $type    js or css
1033
     * @param array|string         $files   Files or list of files to check
1034
     */
1035
    public function assertFileIncluded($backend, $type, $files)
1036
    {
1037
        $includedFiles = $this->getBackendFiles($backend, $type);
1038
1039
        if (is_array($files)) {
1040
            $failedMatches = array();
1041
            foreach ($files as $file) {
1042
                if (!array_key_exists($file, $includedFiles)) {
1043
                    $failedMatches[] = $file;
1044
                }
1045
            }
1046
            $this->assertCount(
1047
                0,
1048
                $failedMatches,
1049
                "Failed asserting the $type files '"
1050
                . implode("', '", $failedMatches)
1051
                . "' have exact matches in the required elements:\n'"
1052
                . implode("'\n'", array_keys($includedFiles)) . "'"
1053
            );
1054
        } else {
1055
            $this->assertArrayHasKey(
1056
                $files,
1057
                $includedFiles,
1058
                "Failed asserting the $type file '$files' has an exact match in the required elements:\n'"
1059
                . implode("'\n'", array_keys($includedFiles)) . "'"
1060
            );
1061
        }
1062
    }
1063
1064
    public function assertFileNotIncluded($backend, $type, $files)
1065
    {
1066
        $includedFiles = $this->getBackendFiles($backend, $type);
1067
        if (is_array($files)) {
1068
            $failedMatches = array();
1069
            foreach ($files as $file) {
1070
                if (array_key_exists($file, $includedFiles)) {
1071
                    $failedMatches[] = $file;
1072
                }
1073
            }
1074
            $this->assertCount(
1075
                0,
1076
                $failedMatches,
1077
                "Failed asserting the $type files '"
1078
                . implode("', '", $failedMatches)
1079
                . "' do not have exact matches in the required elements:\n'"
1080
                . implode("'\n'", array_keys($includedFiles)) . "'"
1081
            );
1082
        } else {
1083
            $this->assertArrayNotHasKey(
1084
                $files,
1085
                $includedFiles,
1086
                "Failed asserting the $type file '$files' does not have an exact match in the required elements:"
1087
                        . "\n'" . implode("'\n'", array_keys($includedFiles)) . "'"
1088
            );
1089
        }
1090
    }
1091
1092
1093
    /**
1094
     * Get files of the given type from the backend
1095
     *
1096
     * @param  Requirements_Backend $backend
1097
     * @param  string               $type    js or css
1098
     * @return array
1099
     */
1100
    protected function getBackendFiles($backend, $type)
1101
    {
1102
        $type = strtolower($type);
1103
        switch (strtolower($type)) {
1104
            case 'css':
1105
                return $backend->getCSS();
1106
            case 'js':
1107
            case 'javascript':
1108
            case 'script':
1109
                return $backend->getJavascript();
1110
        }
1111
        return array();
1112
    }
1113
1114
    public function testAddI18nJavascript()
1115
    {
1116
        /** @var Requirements_Backend $backend */
1117
        $backend = Injector::inst()->create(Requirements_Backend::class);
1118
        $this->setupRequirements($backend);
1119
        $backend->add_i18n_javascript('i18n');
1120
1121
        $actual = $backend->getJavascript();
1122
1123
        // English and English US should always be loaded no matter what
1124
        $this->assertArrayHasKey('i18n/en.js', $actual);
1125
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1126
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1127
    }
1128
1129
    public function testAddI18nJavascriptWithDefaultLocale()
1130
    {
1131
        i18n::config()->set('default_locale', 'fr_CA');
1132
1133
        /** @var Requirements_Backend $backend */
1134
        $backend = Injector::inst()->create(Requirements_Backend::class);
1135
        $this->setupRequirements($backend);
1136
        $backend->add_i18n_javascript('i18n');
1137
1138
        $actual = $backend->getJavascript();
1139
1140
1141
        $this->assertArrayHasKey('i18n/en.js', $actual);
1142
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1143
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1144
        // Default locale should be loaded
1145
        $this->assertArrayHasKey('i18n/fr.js', $actual);
1146
        $this->assertArrayHasKey('i18n/fr_CA.js', $actual);
1147
        $this->assertArrayHasKey('i18n/fr-ca.js', $actual);
1148
    }
1149
1150
    public function testAddI18nJavascriptWithMemberLocale()
1151
    {
1152
        i18n::set_locale('en_GB');
1153
1154
        /** @var Requirements_Backend $backend */
1155
        $backend = Injector::inst()->create(Requirements_Backend::class);
1156
        $this->setupRequirements($backend);
1157
        $backend->add_i18n_javascript('i18n');
1158
1159
        $actual = $backend->getJavascript();
1160
1161
        // The current member's Locale as defined by i18n::get_locale should be loaded
1162
        $this->assertArrayHasKey('i18n/en.js', $actual);
1163
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1164
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1165
        $this->assertArrayHasKey('i18n/en-gb.js', $actual);
1166
        $this->assertArrayHasKey('i18n/en_GB.js', $actual);
1167
    }
1168
1169
    public function testAddI18nJavascriptWithMissingLocale()
1170
    {
1171
        i18n::set_locale('fr_BE');
1172
1173
        /** @var Requirements_Backend $backend */
1174
        $backend = Injector::inst()->create(Requirements_Backend::class);
1175
        $this->setupRequirements($backend);
1176
        $backend->add_i18n_javascript('i18n');
1177
1178
        $actual = $backend->getJavascript();
1179
1180
        // We don't have a file for French Belgium. Regular french should be loaded anyway.
1181
        $this->assertArrayHasKey('i18n/en.js', $actual);
1182
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1183
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1184
        $this->assertArrayHasKey('i18n/fr.js', $actual);
1185
    }
1186
1187
    public function testSriAttributes()
1188
    {
1189
        /** @var Requirements_Backend $backend */
1190
        $backend = Injector::inst()->create(Requirements_Backend::class);
1191
        $this->setupRequirements($backend);
1192
1193
        $backend->javascript('javascript/RequirementsTest_a.js', ['integrity' => 'abc', 'crossorigin' => 'use-credentials']);
1194
        $backend->css('css/RequirementsTest_a.css', null, ['integrity' => 'def', 'crossorigin' => 'anonymous']);
1195
        $html = $backend->includeInHTML(self::$html_template);
1196
1197
        /* Javascript has correct attributes */
1198
        $this->assertRegExp(
1199
            '#<script type="application/javascript" src=".*/javascript/RequirementsTest_a.js.*" integrity="abc" crossorigin="use-credentials"#',
1200
            $html,
1201
            'javascript has correct sri attributes'
1202
        );
1203
1204
        /* CSS has correct attributes */
1205
        $this->assertRegExp(
1206
            '#<link .*href=".*/RequirementsTest_a\.css.*" integrity="def" crossorigin="anonymous"#',
1207
            $html,
1208
            'css has correct sri attributes'
1209
        );
1210
    }
1211
}
1212