Passed
Pull Request — 4 (#10134)
by Sergey
13:21 queued 06:04
created

RequirementsTest   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 1373
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 771
dl 0
loc 1373
rs 8.869
c 0
b 0
f 0
wmc 42

32 Methods

Rating   Name   Duplication   Size   Complexity  
A testProvidedFiles() 0 57 1
A testAppendAndBlockWithModuleResourceLoader() 0 19 1
A testResolveCSSReferencesDisabled() 0 45 1
A testSuffix() 0 31 1
B testResolveCSSReferences() 0 134 1
A testIncludedJsIsNotCommentedOut() 0 11 1
A testExternalUrls() 0 25 1
A testConditionalTemplateRequire() 0 63 1
A testJsWriteToBody() 0 18 1
A assertFileNotIncluded() 0 24 4
A getBackendFiles() 0 12 5
B testCombinedJavascript() 0 98 1
A setupCombinedRequirements() 0 19 1
A testRequirementsBackend() 0 59 1
A testBlockedCombinedJavascript() 0 52 1
A testCommentedOutScriptTagIsIgnored() 0 21 1
A testCustomType() 0 20 1
A testArgsInUrls() 0 25 1
A testAddI18nJavascript() 0 13 1
A setUp() 0 10 1
A tearDown() 0 5 1
A setupCombinedNonrequiredRequirements() 0 10 1
A testCombinedCss() 0 60 1
A setupCombinedRequirementsJavascriptAsyncDefer() 0 19 1
B testCombinedJavascriptAsyncDefer() 0 223 1
A testSriAttributes() 0 22 1
A testAddI18nJavascriptWithDefaultLocale() 0 19 1
A testForceJsToBottom() 0 61 1
A assertFileIncluded() 0 25 4
A testAddI18nJavascriptWithMissingLocale() 0 16 1
A setupRequirements() 0 9 1
A testAddI18nJavascriptWithMemberLocale() 0 17 1

How to fix   Complexity   

Complex Class

Complex classes like RequirementsTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RequirementsTest, and based on these observations, apply Extract Interface, too.

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