Passed
Pull Request — 4 (#10222)
by Steve
07:01
created

RequirementsTest::testBlockedCombinedJavascript()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 52
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 36
nc 1
nop 0
dl 0
loc 52
rs 9.344
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\View\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\i18n\i18n;
10
use SilverStripe\View\Requirements;
11
use SilverStripe\View\ArrayData;
12
use Silverstripe\Assets\Dev\TestAssetStore;
13
use SilverStripe\View\Requirements_Backend;
14
use SilverStripe\Core\Manifest\ResourceURLGenerator;
15
use SilverStripe\Control\SimpleResourceURLGenerator;
16
use SilverStripe\View\SSViewer;
17
use SilverStripe\View\ThemeResourceLoader;
18
19
/**
20
 * @todo Test that order of combine_files() is correct
21
 * @todo Figure out how to clear the modified state of Requirements class - might affect other tests.
22
 * @skipUpgrade
23
 */
24
class RequirementsTest extends SapphireTest
25
{
26
27
    /**
28
     * @var ThemeResourceLoader
29
     */
30
    protected $oldThemeResourceLoader = null;
31
32
    static $html_template = '<html><head></head><body></body></html>';
33
34
    protected function setUp(): void
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(): void
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->assertStringContainsString('http://www.mydomain.com/test.js', $html, 'Load external javascript URL');
71
        $this->assertStringContainsString('https://www.mysecuredomain.com/test.js', $html, 'Load external secure javascript URL');
72
        $this->assertStringContainsString('//scheme-relative.example.com/test.js', $html, 'Load external scheme-relative JS');
73
        $this->assertStringContainsString('http://www.mydomain.com:3000/test.js', $html, 'Load external with port');
74
        $this->assertStringContainsString('http://www.mydomain.com/test.css', $html, 'Load external CSS URL');
75
        $this->assertStringContainsString('https://www.mysecuredomain.com/test.css', $html, 'Load external secure CSS URL');
76
        $this->assertStringContainsString('//scheme-relative.example.com/test.css', $html, 'Load scheme-relative CSS URL');
77
        $this->assertStringContainsString('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
            [
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
            [
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->assertMatchesRegularExpression(
185
            '#<script type="application/json" src=".*/javascript/RequirementsTest_a.js#',
186
            $result
187
        );
188
        $this->assertMatchesRegularExpression(
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->assertMatchesRegularExpression(
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->assertStringContainsString(
221
            "alert('b')",
222
            file_get_contents($combinedFilePath),
223
            'combined javascript has correct content'
224
        );
225
        $this->assertStringContainsString(
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->assertDoesNotMatchRegularExpression(
233
            '/src=".*\/RequirementsTest_b\.js/',
234
            $html,
235
            'combined files are not included twice'
236
        );
237
        $this->assertDoesNotMatchRegularExpression(
238
            '/src=".*\/RequirementsTest_c\.js/',
239
            $html,
240
            'combined files are not included twice'
241
        );
242
243
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
244
        $this->assertMatchesRegularExpression(
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((string) $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->assertMatchesRegularExpression(
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->assertStringContainsString(
272
            "alert('b')",
273
            file_get_contents($combinedFilePath),
274
            'combined javascript has correct content'
275
        );
276
        $this->assertStringContainsString(
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->assertDoesNotMatchRegularExpression(
284
            '/src=".*\/RequirementsTest_b\.js/',
285
            $html,
286
            'combined files are not included twice'
287
        );
288
        $this->assertDoesNotMatchRegularExpression(
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->assertMatchesRegularExpression(
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->assertStringNotContainsString('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->assertStringContainsString(
326
            "alert('b')",
327
            file_get_contents($combinedFilePath),
328
            'combined javascript has correct content'
329
        );
330
        $this->assertStringContainsString(
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->assertDoesNotMatchRegularExpression(
338
            '/src=".*\/RequirementsTest_b\.js/',
339
            $html,
340
            'combined files are not included twice'
341
        );
342
        $this->assertDoesNotMatchRegularExpression(
343
            '/src=".*\/RequirementsTest_c\.js/',
344
            $html,
345
            'combined files are not included twice'
346
        );
347
348
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
349
        $this->assertMatchesRegularExpression(
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->assertDoesNotMatchRegularExpression(
357
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
358
            $html,
359
            'normal requirements don\'t have async'
360
        );
361
        $this->assertDoesNotMatchRegularExpression(
362
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
363
            $html,
364
            'normal requirements don\'t have defer'
365
        );
366
        $this->assertDoesNotMatchRegularExpression(
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((string) $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->assertMatchesRegularExpression(
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->assertStringNotContainsString('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->assertStringContainsString(
400
            "alert('b')",
401
            file_get_contents($combinedFilePath),
402
            'combined javascript has correct content'
403
        );
404
        $this->assertStringContainsString(
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->assertDoesNotMatchRegularExpression(
412
            '/src=".*\/RequirementsTest_b\.js/',
413
            $html,
414
            'combined files are not included twice'
415
        );
416
        $this->assertDoesNotMatchRegularExpression(
417
            '/src=".*\/RequirementsTest_c\.js/',
418
            $html,
419
            'combined files are not included twice'
420
        );
421
422
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
423
        $this->assertMatchesRegularExpression(
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->assertDoesNotMatchRegularExpression(
431
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
432
            $html,
433
            'normal requirements don\'t have async'
434
        );
435
        $this->assertDoesNotMatchRegularExpression(
436
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
437
            $html,
438
            'normal requirements don\'t have defer'
439
        );
440
        $this->assertDoesNotMatchRegularExpression(
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((string) $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->assertMatchesRegularExpression(
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->assertStringContainsString(
471
            "alert('b')",
472
            file_get_contents($combinedFilePath),
473
            'combined javascript has correct content'
474
        );
475
        $this->assertStringContainsString(
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->assertDoesNotMatchRegularExpression(
483
            '/src=".*\/RequirementsTest_b\.js/',
484
            $html,
485
            'combined files are not included twice'
486
        );
487
        $this->assertDoesNotMatchRegularExpression(
488
            '/src=".*\/RequirementsTest_c\.js/',
489
            $html,
490
            'combined files are not included twice'
491
        );
492
493
        /* NORMAL REQUIREMENTS ARE STILL INCLUDED */
494
        $this->assertMatchesRegularExpression(
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->assertDoesNotMatchRegularExpression(
502
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async/',
503
            $html,
504
            'normal requirements don\'t have async'
505
        );
506
        $this->assertDoesNotMatchRegularExpression(
507
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" defer/',
508
            $html,
509
            'normal requirements don\'t have defer'
510
        );
511
        $this->assertDoesNotMatchRegularExpression(
512
            '/src=".*\/RequirementsTest_a\.js\?m=\d+" async defer/',
513
            $html,
514
            'normal requirements don\'t have async/defer'
515
        );
516
517
        unlink((string) $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->assertMatchesRegularExpression(
542
            '/href=".*\/print\-69ce614\.css/',
543
            $html,
544
            'Print stylesheets have been combined.'
545
        );
546
        $this->assertMatchesRegularExpression(
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
            [
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->assertMatchesRegularExpression(
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->assertFileDoesNotExist($combinedFilePath);
597
        $this->assertDoesNotMatchRegularExpression(
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->assertStringNotContainsString(
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
        $generator = Injector::inst()->get(ResourceURLGenerator::class);
646
        $generator->setNonceStyle('mtime');
647
648
        $backend->javascript('javascript/RequirementsTest_a.js?test=1&test=2&test=3');
649
        $backend->css('css/RequirementsTest_a.css?test=1&test=2&test=3');
650
        $html = $backend->includeInHTML(self::$html_template);
651
652
        /* Javascript has correct path */
653
        $this->assertMatchesRegularExpression(
654
            '/src=".*\/RequirementsTest_a\.js\?test=1&amp;test=2&amp;test=3&amp;m=\d\d+/',
655
            $html,
656
            'javascript has correct path'
657
        );
658
659
        /* CSS has correct path */
660
        $this->assertMatchesRegularExpression(
661
            '/href=".*\/RequirementsTest_a\.css\?test=1&amp;test=2&amp;test=3&amp;m=\d\d+/',
662
            $html,
663
            'css has correct path'
664
        );
665
    }
666
667
    public function testRequirementsBackend()
668
    {
669
        /** @var Requirements_Backend $backend */
670
        $backend = Injector::inst()->create(Requirements_Backend::class);
671
        $this->setupRequirements($backend);
672
        $backend->javascript('a.js');
673
674
        $this->assertCount(
675
            1,
676
            $backend->getJavascript(),
677
            "There should be only 1 file included in required javascript."
678
        );
679
        $this->assertArrayHasKey(
680
            'a.js',
681
            $backend->getJavascript(),
682
            "a.js should be included in required javascript."
683
        );
684
685
        $backend->javascript('b.js');
686
        $this->assertCount(
687
            2,
688
            $backend->getJavascript(),
689
            "There should be 2 files included in required javascript."
690
        );
691
692
        $backend->block('a.js');
693
        $this->assertCount(
694
            1,
695
            $backend->getJavascript(),
696
            "There should be only 1 file included in required javascript."
697
        );
698
        $this->assertArrayNotHasKey(
699
            'a.js',
700
            $backend->getJavascript(),
701
            "a.js should not be included in required javascript after it has been blocked."
702
        );
703
        $this->assertArrayHasKey(
704
            'b.js',
705
            $backend->getJavascript(),
706
            "b.js should be included in required javascript."
707
        );
708
709
        $backend->css('a.css');
710
        $this->assertCount(
711
            1,
712
            $backend->getCSS(),
713
            "There should be only 1 file included in required css."
714
        );
715
        $this->assertArrayHasKey(
716
            'a.css',
717
            $backend->getCSS(),
718
            "a.css should be in required css."
719
        );
720
721
        $backend->block('a.css');
722
        $this->assertCount(
723
            0,
724
            $backend->getCSS(),
725
            "There should be nothing in required css after file has been blocked."
726
        );
727
    }
728
729
    public function testAppendAndBlockWithModuleResourceLoader()
730
    {
731
        /** @var Requirements_Backend $backend */
732
        $backend = Injector::inst()->create(Requirements_Backend::class);
733
        $this->setupRequirements($backend);
734
735
        // Note: assumes that client/styles/debug.css is "exposed"
736
        $backend->css('silverstripe/framework:client/styles/debug.css');
737
        $this->assertCount(
738
            1,
739
            $backend->getCSS(),
740
            'Module resource can be loaded via resources reference'
741
        );
742
743
        $backend->block('silverstripe/framework:client/styles/debug.css');
744
        $this->assertCount(
745
            0,
746
            $backend->getCSS(),
747
            'Module resource can be blocked via resources reference'
748
        );
749
    }
750
751
    public function testConditionalTemplateRequire()
752
    {
753
        // Set /SSViewerTest and /SSViewerTest/public as themes
754
        SSViewer::set_themes([
755
            '/',
756
            SSViewer::PUBLIC_THEME
757
        ]);
758
        ThemeResourceLoader::set_instance(new ThemeResourceLoader(__DIR__ . '/SSViewerTest'));
759
760
        /** @var Requirements_Backend $backend */
761
        $backend = Injector::inst()->create(Requirements_Backend::class);
762
        $this->setupRequirements($backend);
763
        $holder = Requirements::backend();
764
        Requirements::set_backend($backend);
765
        $data = new ArrayData([
766
            'FailTest' => true,
767
        ]);
768
769
        $data->renderWith('RequirementsTest_Conditionals');
770
        $this->assertFileIncluded($backend, 'css', 'css/RequirementsTest_a.css');
771
        $this->assertFileIncluded(
772
            $backend,
773
            'js',
774
            [
775
                'javascript/RequirementsTest_b.js',
776
                'javascript/RequirementsTest_c.js'
777
            ]
778
        );
779
        $this->assertFileNotIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
780
        $this->assertFileNotIncluded(
781
            $backend,
782
            'css',
783
            [
784
                'css/RequirementsTest_b.css',
785
                'css/RequirementsTest_c.css'
786
            ]
787
        );
788
        $backend->clear();
789
        $data = new ArrayData(
790
            [
791
            'FailTest' => false,
792
            ]
793
        );
794
        $data->renderWith('RequirementsTest_Conditionals');
795
        $this->assertFileNotIncluded($backend, 'css', 'css/RequirementsTest_a.css');
796
        $this->assertFileNotIncluded(
797
            $backend,
798
            'js',
799
            [
800
                'javascript/RequirementsTest_b.js',
801
                'javascript/RequirementsTest_c.js',
802
            ]
803
        );
804
        $this->assertFileIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
805
        $this->assertFileIncluded(
806
            $backend,
807
            'css',
808
            [
809
                'css/RequirementsTest_b.css',
810
                'css/RequirementsTest_c.css',
811
            ]
812
        );
813
        Requirements::set_backend($holder);
814
    }
815
816
    public function testJsWriteToBody()
817
    {
818
        /** @var Requirements_Backend $backend */
819
        $backend = Injector::inst()->create(Requirements_Backend::class);
820
        $this->setupRequirements($backend);
821
        $backend->javascript('http://www.mydomain.com/test.js');
822
823
        // Test matching with HTML5 <header> tags as well
824
        $template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
825
826
        $backend->setWriteJavascriptToBody(false);
827
        $html = $backend->includeInHTML($template);
828
        $this->assertStringContainsString('<head><script', $html);
829
830
        $backend->setWriteJavascriptToBody(true);
831
        $html = $backend->includeInHTML($template);
832
        $this->assertStringNotContainsString('<head><script', $html);
833
        $this->assertStringContainsString("</script>\n</body>", $html);
834
    }
835
836
    public function testIncludedJsIsNotCommentedOut()
837
    {
838
        $template = '<html><head></head><body><!--<script>alert("commented out");</script>--></body></html>';
839
        /** @var Requirements_Backend $backend */
840
        $backend = Injector::inst()->create(Requirements_Backend::class);
841
        $this->setupRequirements($backend);
842
        $backend->javascript('javascript/RequirementsTest_a.js');
843
        $html = $backend->includeInHTML($template);
844
        //wiping out commented-out html
845
        $html = preg_replace('/<!--(.*)-->/Uis', '', $html ?: '');
846
        $this->assertStringContainsString("RequirementsTest_a.js", $html);
847
    }
848
849
    public function testCommentedOutScriptTagIsIgnored()
850
    {
851
        /// Disable nonce
852
        $urlGenerator = new SimpleResourceURLGenerator();
853
        Injector::inst()->registerService($urlGenerator, ResourceURLGenerator::class);
854
855
        $template = '<html><head></head><body><!--<script>alert("commented out");</script>-->'
856
            . '<h1>more content</h1></body></html>';
857
        /** @var Requirements_Backend $backend */
858
        $backend = Injector::inst()->create(Requirements_Backend::class);
859
        $this->setupRequirements($backend);
860
861
        $src = 'javascript/RequirementsTest_a.js';
862
        $backend->javascript($src);
863
        $html = $backend->includeInHTML($template);
864
        $urlSrc = $urlGenerator->urlForResource($src);
865
        $this->assertEquals(
866
            '<html><head></head><body><!--<script>alert("commented out");</script>-->'
867
            . '<h1>more content</h1><script type="application/javascript" src="' . $urlSrc
868
            . "\"></script>\n</body></html>",
869
            $html
870
        );
871
    }
872
873
    public function testForceJsToBottom()
874
    {
875
        /** @var Requirements_Backend $backend */
876
        $backend = Injector::inst()->create(Requirements_Backend::class);
877
        $this->setupRequirements($backend);
878
        $backend->javascript('http://www.mydomain.com/test.js');
879
        $backend->customScript(
880
            <<<'EOS'
881
var globalvar = {
882
	pattern: '\\$custom\\1'
883
};
884
EOS
885
        );
886
887
        // Test matching with HTML5 <header> tags as well
888
        $template = '<html><head></head><body><header>My header</header><p>Body<script></script></p></body></html>';
889
890
        // The expected outputs
891
        $expectedScripts = "<script type=\"application/javascript\" src=\"http://www.mydomain.com/test.js\"></script>\n"
892
            . "<script type=\"application/javascript\">//<![CDATA[\n"
893
            . "var globalvar = {\n\tpattern: '\\\\\$custom\\\\1'\n};\n"
894
            . "//]]></script>\n";
895
        $JsInHead = "<html><head>$expectedScripts</head><body><header>My header</header><p>Body<script></script></p></body></html>";
896
        $JsInBody = "<html><head></head><body><header>My header</header><p>Body$expectedScripts<script></script></p></body></html>";
897
        $JsAtEnd  = "<html><head></head><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->assertMatchesRegularExpression('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
956
        $this->assertMatchesRegularExpression('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
957
        $this->assertMatchesRegularExpression('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
958
        $this->assertMatchesRegularExpression('/RequirementsTest_b\.css\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
959
960
        $urlGenerator->setNonceStyle(null);
961
        $html = $backend->includeInHTML($template);
962
        $this->assertStringNotContainsString('RequirementsTest_a.js=', $html);
963
        $this->assertDoesNotMatchRegularExpression('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
964
        $this->assertDoesNotMatchRegularExpression('/RequirementsTest_b\.js\?foo=bar&amp;bla=blubb&amp;m=[\d]*"/', $html);
965
        $this->assertDoesNotMatchRegularExpression('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
966
        $this->assertDoesNotMatchRegularExpression('/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->assertMatchesRegularExpression('/src=".*\/RequirementsTest_a\.js/', $html);
1001
        $this->assertMatchesRegularExpression('/src=".*\/RequirementsTest_b\.js/', $html);
1002
        $this->assertDoesNotMatchRegularExpression('/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->assertMatchesRegularExpression('/src=".*\/combined_a/', $html);
1026
        $this->assertMatchesRegularExpression('/src=".*\/RequirementsTest_b\.js/', $html);
1027
        $this->assertDoesNotMatchRegularExpression('/src=".*\/combined_c/', $html);
1028
        $this->assertDoesNotMatchRegularExpression('/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 = [];
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 = [];
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((string) $type);
1106
        switch (strtolower((string) $type)) {
1107
            case 'css':
1108
                return $backend->getCSS();
1109
            case 'js':
1110
            case 'javascript':
1111
            case 'script':
1112
                return $backend->getJavascript();
1113
        }
1114
        return [];
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
    public function testSriAttributes()
1191
    {
1192
        /** @var Requirements_Backend $backend */
1193
        $backend = Injector::inst()->create(Requirements_Backend::class);
1194
        $this->setupRequirements($backend);
1195
1196
        $backend->javascript('javascript/RequirementsTest_a.js', ['integrity' => 'abc', 'crossorigin' => 'use-credentials']);
1197
        $backend->css('css/RequirementsTest_a.css', null, ['integrity' => 'def', 'crossorigin' => 'anonymous']);
1198
        $html = $backend->includeInHTML(self::$html_template);
1199
1200
        /* Javascript has correct attributes */
1201
        $this->assertMatchesRegularExpression(
1202
            '#<script type="application/javascript" src=".*/javascript/RequirementsTest_a.js.*" integrity="abc" crossorigin="use-credentials"#',
1203
            $html,
1204
            'javascript has correct sri attributes'
1205
        );
1206
1207
        /* CSS has correct attributes */
1208
        $this->assertMatchesRegularExpression(
1209
            '#<link .*href=".*/RequirementsTest_a\.css.*" integrity="def" crossorigin="anonymous"#',
1210
            $html,
1211
            'css has correct sri attributes'
1212
        );
1213
    }
1214
}
1215