Passed
Push — 4.1.1 ( 01ed8a )
by Robbie
09:45
created

RequirementsTest::testArgsInUrls()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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