Passed
Push — master ( 91737d...b67073 )
by Sam
08:30
created

RequirementsTest   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 1164
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 662
dl 0
loc 1164
rs 9.218
c 0
b 0
f 0
wmc 39

29 Methods

Rating   Name   Duplication   Size   Complexity  
A testAppendAndBlockWithModuleResourceLoader() 0 19 1
A testIncludedJsIsNotCommentedOut() 0 11 1
A testExternalUrls() 0 25 1
A testConditionalTemplateRequire() 0 63 1
A testJsWriteToBody() 0 18 1
B testCombinedJavascript() 0 98 1
A setupCombinedRequirements() 0 19 1
A testRequirementsBackend() 0 59 1
A testBlockedCombinedJavascript() 0 52 1
A testCommentedOutScriptTagIsIgnored() 0 21 1
A testCustomType() 0 20 1
A testArgsInUrls() 0 22 1
A setUp() 0 10 1
A tearDown() 0 5 1
A setupCombinedNonrequiredRequirements() 0 10 1
A testCombinedCss() 0 60 1
A setupCombinedRequirementsJavascriptAsyncDefer() 0 19 1
B testCombinedJavascriptAsyncDefer() 0 223 1
A setupRequirements() 0 9 1
A testProvidedFiles() 0 57 1
A testSuffix() 0 31 1
A assertFileNotIncluded() 0 24 4
A getBackendFiles() 0 12 5
A testAddI18nJavascript() 0 13 1
A testAddI18nJavascriptWithDefaultLocale() 0 19 1
A testForceJsToBottom() 0 64 1
A assertFileIncluded() 0 25 4
A testAddI18nJavascriptWithMissingLocale() 0 16 1
A testAddI18nJavascriptWithMemberLocale() 0 17 1
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>';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $html_template.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
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>"
893
            . "<body><header>My header</header><p>Body<script></script></p></body></html>";
894
        $JsInBody = "<html><head></head>"
895
            . "<body><header>My header</header><p>Body$expectedScripts<script></script></p></body></html>";
896
        $JsAtEnd  = "<html><head></head>"
897
            . "<body><header>My header</header><p>Body<script></script></p>$expectedScripts</body></html>";
898
899
900
        // Test if the script is before the head tag, not before the body.
901
        // Expected: $JsInHead
902
        $backend->setWriteJavascriptToBody(false);
903
        $backend->setForceJSToBottom(false);
904
        $html = $backend->includeInHTML($template);
905
        $this->assertNotEquals($JsInBody, $html);
906
        $this->assertNotEquals($JsAtEnd, $html);
907
        $this->assertEquals($JsInHead, $html);
908
909
        // Test if the script is before the first <script> tag, not before the body.
910
        // Expected: $JsInBody
911
        $backend->setWriteJavascriptToBody(true);
912
        $backend->setForceJSToBottom(false);
913
        $html = $backend->includeInHTML($template);
914
        $this->assertNotEquals($JsAtEnd, $html);
915
        $this->assertEquals($JsInBody, $html);
916
917
        // Test if the script is placed just before the closing bodytag, with write-to-body false.
918
        // Expected: $JsAtEnd
919
        $backend->setWriteJavascriptToBody(false);
920
        $backend->setForceJSToBottom(true);
921
        $html = $backend->includeInHTML($template);
922
        $this->assertNotEquals($JsInHead, $html);
923
        $this->assertNotEquals($JsInBody, $html);
924
        $this->assertEquals($JsAtEnd, $html);
925
926
        // Test if the script is placed just before the closing bodytag, with write-to-body true.
927
        // Expected: $JsAtEnd
928
        $backend->setWriteJavascriptToBody(true);
929
        $backend->setForceJSToBottom(true);
930
        $html = $backend->includeInHTML($template);
931
        $this->assertNotEquals($JsInHead, $html);
932
        $this->assertNotEquals($JsInBody, $html);
933
        $this->assertEquals($JsAtEnd, $html);
934
    }
935
936
    public function testSuffix()
937
    {
938
        /// Disable nonce
939
        $urlGenerator = new SimpleResourceURLGenerator();
940
        Injector::inst()->registerService($urlGenerator, ResourceURLGenerator::class);
941
942
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
943
944
        /** @var Requirements_Backend $backend */
945
        $backend = Injector::inst()->create(Requirements_Backend::class);
946
        $this->setupRequirements($backend);
947
948
        $backend->javascript('javascript/RequirementsTest_a.js');
949
        $backend->javascript('javascript/RequirementsTest_b.js?foo=bar&bla=blubb');
950
        $backend->css('css/RequirementsTest_a.css');
951
        $backend->css('css/RequirementsTest_b.css?foo=bar&bla=blubb');
952
953
        $urlGenerator->setNonceStyle('mtime');
954
        $html = $backend->includeInHTML($template);
955
        $this->assertRegExp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
956
        $this->assertRegExp('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
957
        $this->assertRegExp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
958
        $this->assertRegExp('/RequirementsTest_b\.css\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
959
960
        $urlGenerator->setNonceStyle(null);
961
        $html = $backend->includeInHTML($template);
962
        $this->assertNotContains('RequirementsTest_a.js=', $html);
963
        $this->assertNotRegExp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
964
        $this->assertNotRegExp('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
965
        $this->assertNotRegExp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
966
        $this->assertNotRegExp('/RequirementsTest_b\.css\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
967
    }
968
969
    /**
970
     * Tests that provided files work
971
     */
972
    public function testProvidedFiles()
973
    {
974
        /** @var Requirements_Backend $backend */
975
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
976
977
        // Test that provided files block subsequent files
978
        $backend = Injector::inst()->create(Requirements_Backend::class);
979
        $this->setupRequirements($backend);
980
        $backend->javascript('javascript/RequirementsTest_a.js');
981
        $backend->javascript(
982
            'javascript/RequirementsTest_b.js',
983
            [
984
            'provides' => [
985
                    'javascript/RequirementsTest_a.js',
986
                    'javascript/RequirementsTest_c.js',
987
                ],
988
            ]
989
        );
990
        $backend->javascript('javascript/RequirementsTest_c.js');
991
        // Note that _a.js isn't considered provided because it was included
992
        // before it was marked as provided
993
        $this->assertEquals(
994
            [
995
                'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
996
            ],
997
            $backend->getProvidedScripts()
998
        );
999
        $html = $backend->includeInHTML($template);
1000
        $this->assertRegExp('/src=".*\/RequirementsTest_a\.js/', $html);
1001
        $this->assertRegExp('/src=".*\/RequirementsTest_b\.js/', $html);
1002
        $this->assertNotRegExp('/src=".*\/RequirementsTest_c\.js/', $html);
1003
1004
        // Test that provided files block subsequent combined files
1005
        $backend = Injector::inst()->create(Requirements_Backend::class);
1006
        $this->setupRequirements($backend);
1007
        $backend->combineFiles('combined_a.js', ['javascript/RequirementsTest_a.js']);
1008
        $backend->javascript(
1009
            'javascript/RequirementsTest_b.js',
1010
            [
1011
            'provides' => [
1012
                'javascript/RequirementsTest_a.js',
1013
                'javascript/RequirementsTest_c.js'
1014
            ]
1015
            ]
1016
        );
1017
        $backend->combineFiles('combined_c.js', ['javascript/RequirementsTest_c.js']);
1018
        $this->assertEquals(
1019
            [
1020
                'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
1021
            ],
1022
            $backend->getProvidedScripts()
1023
        );
1024
        $html = $backend->includeInHTML($template);
1025
        $this->assertRegExp('/src=".*\/combined_a/', $html);
1026
        $this->assertRegExp('/src=".*\/RequirementsTest_b\.js/', $html);
1027
        $this->assertNotRegExp('/src=".*\/combined_c/', $html);
1028
        $this->assertNotRegExp('/src=".*\/RequirementsTest_c\.js/', $html);
1029
    }
1030
1031
    /**
1032
     * Verify that the given backend includes the given files
1033
     *
1034
     * @param Requirements_Backend $backend
1035
     * @param string               $type    js or css
1036
     * @param array|string         $files   Files or list of files to check
1037
     */
1038
    public function assertFileIncluded($backend, $type, $files)
1039
    {
1040
        $includedFiles = $this->getBackendFiles($backend, $type);
1041
1042
        if (is_array($files)) {
1043
            $failedMatches = array();
1044
            foreach ($files as $file) {
1045
                if (!array_key_exists($file, $includedFiles)) {
1046
                    $failedMatches[] = $file;
1047
                }
1048
            }
1049
            $this->assertCount(
1050
                0,
1051
                $failedMatches,
1052
                "Failed asserting the $type files '"
1053
                . implode("', '", $failedMatches)
1054
                . "' have exact matches in the required elements:\n'"
1055
                . implode("'\n'", array_keys($includedFiles)) . "'"
1056
            );
1057
        } else {
1058
            $this->assertArrayHasKey(
1059
                $files,
1060
                $includedFiles,
1061
                "Failed asserting the $type file '$files' has an exact match in the required elements:\n'"
1062
                . implode("'\n'", array_keys($includedFiles)) . "'"
1063
            );
1064
        }
1065
    }
1066
1067
    public function assertFileNotIncluded($backend, $type, $files)
1068
    {
1069
        $includedFiles = $this->getBackendFiles($backend, $type);
1070
        if (is_array($files)) {
1071
            $failedMatches = array();
1072
            foreach ($files as $file) {
1073
                if (array_key_exists($file, $includedFiles)) {
1074
                    $failedMatches[] = $file;
1075
                }
1076
            }
1077
            $this->assertCount(
1078
                0,
1079
                $failedMatches,
1080
                "Failed asserting the $type files '"
1081
                . implode("', '", $failedMatches)
1082
                . "' do not have exact matches in the required elements:\n'"
1083
                . implode("'\n'", array_keys($includedFiles)) . "'"
1084
            );
1085
        } else {
1086
            $this->assertArrayNotHasKey(
1087
                $files,
1088
                $includedFiles,
1089
                "Failed asserting the $type file '$files' does not have an exact match in the required elements:"
1090
                        . "\n'" . implode("'\n'", array_keys($includedFiles)) . "'"
1091
            );
1092
        }
1093
    }
1094
1095
1096
    /**
1097
     * Get files of the given type from the backend
1098
     *
1099
     * @param  Requirements_Backend $backend
1100
     * @param  string               $type    js or css
1101
     * @return array
1102
     */
1103
    protected function getBackendFiles($backend, $type)
1104
    {
1105
        $type = strtolower($type);
1106
        switch (strtolower($type)) {
1107
            case 'css':
1108
                return $backend->getCSS();
1109
            case 'js':
1110
            case 'javascript':
1111
            case 'script':
1112
                return $backend->getJavascript();
1113
        }
1114
        return array();
1115
    }
1116
1117
    public function testAddI18nJavascript()
1118
    {
1119
        /** @var Requirements_Backend $backend */
1120
        $backend = Injector::inst()->create(Requirements_Backend::class);
1121
        $this->setupRequirements($backend);
1122
        $backend->add_i18n_javascript('i18n');
1123
1124
        $actual = $backend->getJavascript();
1125
1126
        // English and English US should always be loaded no matter what
1127
        $this->assertArrayHasKey('i18n/en.js', $actual);
1128
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1129
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1130
    }
1131
1132
    public function testAddI18nJavascriptWithDefaultLocale()
1133
    {
1134
        i18n::config()->set('default_locale', 'fr_CA');
1135
1136
        /** @var Requirements_Backend $backend */
1137
        $backend = Injector::inst()->create(Requirements_Backend::class);
1138
        $this->setupRequirements($backend);
1139
        $backend->add_i18n_javascript('i18n');
1140
1141
        $actual = $backend->getJavascript();
1142
1143
1144
        $this->assertArrayHasKey('i18n/en.js', $actual);
1145
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1146
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1147
        // Default locale should be loaded
1148
        $this->assertArrayHasKey('i18n/fr.js', $actual);
1149
        $this->assertArrayHasKey('i18n/fr_CA.js', $actual);
1150
        $this->assertArrayHasKey('i18n/fr-ca.js', $actual);
1151
    }
1152
1153
    public function testAddI18nJavascriptWithMemberLocale()
1154
    {
1155
        i18n::set_locale('en_GB');
1156
1157
        /** @var Requirements_Backend $backend */
1158
        $backend = Injector::inst()->create(Requirements_Backend::class);
1159
        $this->setupRequirements($backend);
1160
        $backend->add_i18n_javascript('i18n');
1161
1162
        $actual = $backend->getJavascript();
1163
1164
        // The current member's Locale as defined by i18n::get_locale should be loaded
1165
        $this->assertArrayHasKey('i18n/en.js', $actual);
1166
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1167
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1168
        $this->assertArrayHasKey('i18n/en-gb.js', $actual);
1169
        $this->assertArrayHasKey('i18n/en_GB.js', $actual);
1170
    }
1171
1172
    public function testAddI18nJavascriptWithMissingLocale()
1173
    {
1174
        i18n::set_locale('fr_BE');
1175
1176
        /** @var Requirements_Backend $backend */
1177
        $backend = Injector::inst()->create(Requirements_Backend::class);
1178
        $this->setupRequirements($backend);
1179
        $backend->add_i18n_javascript('i18n');
1180
1181
        $actual = $backend->getJavascript();
1182
1183
        // We don't have a file for French Belgium. Regular french should be loaded anyway.
1184
        $this->assertArrayHasKey('i18n/en.js', $actual);
1185
        $this->assertArrayHasKey('i18n/en_US.js', $actual);
1186
        $this->assertArrayHasKey('i18n/en-us.js', $actual);
1187
        $this->assertArrayHasKey('i18n/fr.js', $actual);
1188
    }
1189
}
1190