Completed
Push — master ( 0b9e95...0208b2 )
by Damian
42s queued 20s
created

SSViewerTest::testUpInLoop()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 86
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 54
nc 1
nop 0
dl 0
loc 86
rs 8.6583
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 Exception;
6
use InvalidArgumentException;
7
use PHPUnit_Framework_MockObject_MockObject;
8
use Silverstripe\Assets\Dev\TestAssetStore;
9
use SilverStripe\Control\ContentNegotiator;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Control\HTTPResponse;
13
use SilverStripe\Core\Convert;
14
use SilverStripe\Core\Injector\Injector;
15
use SilverStripe\Dev\SapphireTest;
16
use SilverStripe\i18n\i18n;
17
use SilverStripe\ORM\ArrayList;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\ORM\FieldType\DBField;
20
use SilverStripe\ORM\PaginatedList;
21
use SilverStripe\Security\Permission;
22
use SilverStripe\Security\Security;
23
use SilverStripe\Security\SecurityToken;
24
use SilverStripe\View\ArrayData;
25
use SilverStripe\View\Requirements;
26
use SilverStripe\View\Requirements_Backend;
27
use SilverStripe\View\Requirements_Minifier;
28
use SilverStripe\View\SSTemplateParser;
29
use SilverStripe\View\SSViewer;
30
use SilverStripe\View\SSViewer_FromString;
31
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
32
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModelController;
33
use SilverStripe\View\ViewableData;
34
35
/**
36
 * @skipUpgrade
37
 */
38
class SSViewerTest extends SapphireTest
39
{
40
41
    /**
42
     * Backup of $_SERVER global
43
     *
44
     * @var array
45
     */
46
    protected $oldServer = array();
47
48
    protected static $extra_dataobjects = array(
49
        SSViewerTest\TestObject::class,
50
    );
51
52
    protected function setUp()
53
    {
54
        parent::setUp();
55
        SSViewer::config()->update('source_file_comments', false);
56
        SSViewer_FromString::config()->update('cache_template', false);
57
        TestAssetStore::activate('SSViewerTest');
58
        $this->oldServer = $_SERVER;
59
    }
60
61
    protected function tearDown()
62
    {
63
        $_SERVER = $this->oldServer;
64
        TestAssetStore::reset();
65
        parent::tearDown();
66
    }
67
68
    /**
69
     * Tests for {@link Config::inst()->get('SSViewer', 'theme')} for different behaviour
70
     * of user defined themes via {@link SiteConfig} and default theme
71
     * when no user themes are defined.
72
     */
73
    public function testCurrentTheme()
74
    {
75
        SSViewer::config()->update('theme', 'mytheme');
76
        $this->assertEquals(
77
            'mytheme',
78
            SSViewer::config()->uninherited('theme'),
79
            'Current theme is the default - user has not defined one'
80
        );
81
    }
82
83
    /**
84
     * Tests for themes helper functions, ensuring they behave as defined in the RFC at
85
     * https://github.com/silverstripe/silverstripe-framework/issues/5604
86
     */
87
    public function testThemesHelpers()
88
    {
89
        // Test set_themes()
90
        SSViewer::set_themes(['mytheme', '$default']);
91
        $this->assertEquals(['mytheme', '$default'], SSViewer::get_themes());
92
93
        // Ensure add_themes() prepends
94
        SSViewer::add_themes(['my_more_important_theme']);
95
        $this->assertEquals(['my_more_important_theme', 'mytheme', '$default'], SSViewer::get_themes());
96
97
        // Ensure add_themes() on theme already in cascade promotes it to the top
98
        SSViewer::add_themes(['mytheme']);
99
        $this->assertEquals(['mytheme', 'my_more_important_theme', '$default'], SSViewer::get_themes());
100
    }
101
102
    /**
103
     * Test that a template without a <head> tag still renders.
104
     */
105
    public function testTemplateWithoutHeadRenders()
106
    {
107
        $data = new ArrayData([ 'Var' => 'var value' ]);
108
        $result = $data->renderWith("SSViewerTestPartialTemplate");
109
        $this->assertEquals('Test partial template: var value', trim(preg_replace("/<!--.*-->/U", '', $result)));
110
    }
111
112
    public function testIncludeScopeInheritance()
113
    {
114
        $data = $this->getScopeInheritanceTestData();
115
        $expected = array(
116
        'Item 1 - First-ODD top:Item 1',
117
        'Item 2 - EVEN top:Item 2',
118
        'Item 3 - ODD top:Item 3',
119
        'Item 4 - EVEN top:Item 4',
120
        'Item 5 - ODD top:Item 5',
121
        'Item 6 - Last-EVEN top:Item 6',
122
        );
123
124
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritance');
125
        $this->assertExpectedStrings($result, $expected);
126
127
        // reset results for the tests that include arguments (the title is passed as an arg)
128
        $expected = array(
129
        'Item 1 _ Item 1 - First-ODD top:Item 1',
130
        'Item 2 _ Item 2 - EVEN top:Item 2',
131
        'Item 3 _ Item 3 - ODD top:Item 3',
132
        'Item 4 _ Item 4 - EVEN top:Item 4',
133
        'Item 5 _ Item 5 - ODD top:Item 5',
134
        'Item 6 _ Item 6 - Last-EVEN top:Item 6',
135
        );
136
137
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
138
        $this->assertExpectedStrings($result, $expected);
139
    }
140
141
    public function testIncludeTruthyness()
142
    {
143
        $data = new ArrayData([
144
            'Title' => 'TruthyTest',
145
            'Items' => new ArrayList([
146
                new ArrayData(['Title' => 'Item 1']),
147
                new ArrayData(['Title' => '']),
148
                new ArrayData(['Title' => true]),
149
                new ArrayData(['Title' => false]),
150
                new ArrayData(['Title' => null]),
151
                new ArrayData(['Title' => 0]),
152
                new ArrayData(['Title' => 7])
153
            ])
154
        ]);
155
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
156
157
        // We should not end up with empty values appearing as empty
158
        $expected = [
159
            'Item 1 _ Item 1 - First-ODD top:Item 1',
160
            'Untitled - EVEN top:',
161
            '1 _ 1 - ODD top:1',
162
            'Untitled - EVEN top:',
163
            'Untitled - ODD top:',
164
            'Untitled - EVEN top:0',
165
            '7 _ 7 - Last-ODD top:7',
166
        ];
167
        $this->assertExpectedStrings($result, $expected);
168
    }
169
170
    private function getScopeInheritanceTestData()
171
    {
172
        return new ArrayData([
173
            'Title' => 'TopTitleValue',
174
            'Items' => new ArrayList([
175
                new ArrayData(['Title' => 'Item 1']),
176
                new ArrayData(['Title' => 'Item 2']),
177
                new ArrayData(['Title' => 'Item 3']),
178
                new ArrayData(['Title' => 'Item 4']),
179
                new ArrayData(['Title' => 'Item 5']),
180
                new ArrayData(['Title' => 'Item 6'])
181
            ])
182
        ]);
183
    }
184
185
    private function assertExpectedStrings($result, $expected)
186
    {
187
        foreach ($expected as $expectedStr) {
188
            $this->assertTrue(
189
                (boolean) preg_match("/{$expectedStr}/", $result),
190
                "Didn't find '{$expectedStr}' in:\n{$result}"
191
            );
192
        }
193
    }
194
195
    /**
196
     * Small helper to render templates from strings
197
     *
198
     * @param  string $templateString
199
     * @param  null   $data
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $data is correct as it would always require null to be passed?
Loading history...
200
     * @param  bool   $cacheTemplate
201
     * @return string
202
     */
203
    public function render($templateString, $data = null, $cacheTemplate = false)
204
    {
205
        $t = SSViewer::fromString($templateString, $cacheTemplate);
206
        if (!$data) {
207
            $data = new SSViewerTest\TestFixture();
208
        }
209
        return trim('' . $t->process($data));
210
    }
211
212
    public function testRequirements()
213
    {
214
        /** @var Requirements_Backend|PHPUnit_Framework_MockObject_MockObject $requirements */
215
        $requirements = $this
216
            ->getMockBuilder(Requirements_Backend::class)
217
            ->setMethods(array("javascript", "css"))
218
            ->getMock();
219
        $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js';
220
        $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js';
221
222
        $requirements->expects($this->once())->method('javascript')->with($jsFile);
223
        $requirements->expects($this->once())->method('css')->with($cssFile);
224
225
        $origReq = Requirements::backend();
226
        Requirements::set_backend($requirements);
227
        $template = $this->render(
228
            "<% require javascript($jsFile) %>
229
		<% require css($cssFile) %>"
230
        );
231
        Requirements::set_backend($origReq);
232
233
        $this->assertFalse((bool)trim($template), "Should be no content in this return.");
234
    }
235
236
    public function testRequirementsCombine()
237
    {
238
        /** @var Requirements_Backend $testBackend */
239
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
240
        $testBackend->setSuffixRequirements(false);
241
        $testBackend->setCombinedFilesEnabled(true);
242
243
        $jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js';
244
        $jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile);
245
        $testBackend->combineFiles('testRequirementsCombine.js', array($jsFile));
246
247
        // secondly, make sure that requirements is generated, even though minification failed
248
        $testBackend->processCombinedFiles();
249
        $js = array_keys($testBackend->getJavascript());
250
        $combinedTestFilePath = Director::publicFolder() . reset($js);
251
        $this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath);
252
253
        // and make sure the combined content matches the input content, i.e. no loss of functionality
254
        if (!file_exists($combinedTestFilePath)) {
255
            $this->fail('No combined file was created at expected path: ' . $combinedTestFilePath);
256
        }
257
        $combinedTestFileContents = file_get_contents($combinedTestFilePath);
258
        $this->assertContains($jsFileContents, $combinedTestFileContents);
259
    }
260
261
    public function testRequirementsMinification()
262
    {
263
        /** @var Requirements_Backend $testBackend */
264
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
265
        $testBackend->setSuffixRequirements(false);
266
        $testBackend->setMinifyCombinedFiles(true);
267
        $testBackend->setCombinedFilesEnabled(true);
268
269
        $testFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/RequirementsTest_a.js';
270
        $testFileContent = file_get_contents($testFile);
271
272
        $mockMinifier = $this->getMockBuilder(Requirements_Minifier::class)
273
        ->setMethods(['minify'])
274
        ->getMock();
275
276
        $mockMinifier->expects($this->once())
277
        ->method('minify')
278
        ->with(
279
            $testFileContent,
280
            'js',
281
            $testFile
282
        );
283
        $testBackend->setMinifier($mockMinifier);
284
        $testBackend->combineFiles('testRequirementsMinified.js', array($testFile));
285
        $testBackend->processCombinedFiles();
286
287
        $testBackend->setMinifyCombinedFiles(false);
288
        $mockMinifier->expects($this->never())
289
        ->method('minify');
290
        $testBackend->processCombinedFiles();
291
292
        $this->expectException(Exception::class);
293
        $this->expectExceptionMessageRegExp('/^Cannot minify files without a minification service defined./');
294
295
        $testBackend->setMinifyCombinedFiles(true);
296
        $testBackend->setMinifier(null);
297
        $testBackend->processCombinedFiles();
298
    }
299
300
301
302
    public function testComments()
303
    {
304
        $input = <<<SS
305
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content
306
<%-- Alone multi
307
	line comment --%>
308
Some more content
309
Mixing content and <%-- multi
310
	line comment --%> Final final
311
content
312
SS;
313
        $output = $this->render($input);
314
        $shouldbe = <<<SS
315
This is my templateThis is some contentFinal content
316
317
Some more content
318
Mixing content and  Final final
319
content
320
SS;
321
        $this->assertEquals($shouldbe, $output);
322
    }
323
324
    public function testBasicText()
325
    {
326
        $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone');
327
        $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone');
328
        $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped');
329
        $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped');
330
    }
331
332
    public function testBasicInjection()
333
    {
334
        $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection');
335
        $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection');
336
        $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection');
337
        $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection');
338
339
        $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped');
340
        $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character');
341
        $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character');
342
343
        $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection');
344
        $this->assertEquals(
345
            '{\\[out:Test]}',
346
            $this->render('{\\\\$Test}'),
347
            'Escapes before injections are correctly unescaped'
348
        );
349
    }
350
351
352
    public function testGlobalVariableCalls()
353
    {
354
        $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic'));
355
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString'));
356
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray'));
357
    }
358
359
    public function testGlobalVariableCallsWithArguments()
360
    {
361
        $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments'));
362
        $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")'));
363
        $this->assertEquals(
364
            'zFoo:Bar:Bazz',
365
            $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
366
        );
367
        $this->assertEquals(
368
            'zreferencez',
369
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
370
        );
371
    }
372
373
    public function testGlobalVariablesAreEscaped()
374
    {
375
        $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLFragment'));
376
        $this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped'));
377
378
        $this->assertEquals(
379
            'z<div></div>z',
380
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
381
        );
382
        $this->assertEquals(
383
            'z&lt;div&gt;&lt;/div&gt;z',
384
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
385
        );
386
    }
387
388
    public function testCoreGlobalVariableCalls()
389
    {
390
        $this->assertEquals(
391
            Director::absoluteBaseURL(),
392
            $this->render('{$absoluteBaseURL}'),
393
            'Director::absoluteBaseURL can be called from within template'
394
        );
395
        $this->assertEquals(
396
            Director::absoluteBaseURL(),
397
            $this->render('{$AbsoluteBaseURL}'),
398
            'Upper-case %AbsoluteBaseURL can be called from within template'
399
        );
400
401
        $this->assertEquals(
402
            Director::is_ajax(),
403
            $this->render('{$isAjax}'),
404
            'All variations of is_ajax result in the correct call'
405
        );
406
        $this->assertEquals(
407
            Director::is_ajax(),
408
            $this->render('{$IsAjax}'),
409
            'All variations of is_ajax result in the correct call'
410
        );
411
        $this->assertEquals(
412
            Director::is_ajax(),
413
            $this->render('{$is_ajax}'),
414
            'All variations of is_ajax result in the correct call'
415
        );
416
        $this->assertEquals(
417
            Director::is_ajax(),
418
            $this->render('{$Is_ajax}'),
419
            'All variations of is_ajax result in the correct call'
420
        );
421
422
        $this->assertEquals(
423
            i18n::get_locale(),
424
            $this->render('{$i18nLocale}'),
425
            'i18n template functions result correct result'
426
        );
427
        $this->assertEquals(
428
            i18n::get_locale(),
429
            $this->render('{$get_locale}'),
430
            'i18n template functions result correct result'
431
        );
432
433
        $this->assertEquals(
434
            (string)Security::getCurrentUser(),
435
            $this->render('{$CurrentMember}'),
436
            'Member template functions result correct result'
437
        );
438
        $this->assertEquals(
439
            (string)Security::getCurrentUser(),
440
            $this->render('{$CurrentUser}'),
441
            'Member template functions result correct result'
442
        );
443
        $this->assertEquals(
444
            (string)Security::getCurrentUser(),
445
            $this->render('{$currentMember}'),
446
            'Member template functions result correct result'
447
        );
448
        $this->assertEquals(
449
            (string)Security::getCurrentUser(),
450
            $this->render('{$currentUser}'),
451
            'Member template functions result correct result'
452
        );
453
454
        $this->assertEquals(
455
            SecurityToken::getSecurityID(),
456
            $this->render('{$getSecurityID}'),
457
            'SecurityToken template functions result correct result'
458
        );
459
        $this->assertEquals(
460
            SecurityToken::getSecurityID(),
461
            $this->render('{$SecurityID}'),
462
            'SecurityToken template functions result correct result'
463
        );
464
465
        $this->assertEquals(
466
            Permission::check("ADMIN"),
467
            (bool)$this->render('{$HasPerm(\'ADMIN\')}'),
468
            'Permissions template functions result correct result'
469
        );
470
        $this->assertEquals(
471
            Permission::check("ADMIN"),
472
            (bool)$this->render('{$hasPerm(\'ADMIN\')}'),
473
            'Permissions template functions result correct result'
474
        );
475
    }
476
477
    public function testNonFieldCastingHelpersNotUsedInHasValue()
478
    {
479
        // check if Link without $ in front of variable
480
        $result = $this->render(
481
            'A<% if Link %>$Link<% end_if %>B',
482
            new SSViewerTest\TestObject()
483
        );
484
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>');
485
486
        // check if Link with $ in front of variable
487
        $result = $this->render(
488
            'A<% if $Link %>$Link<% end_if %>B',
489
            new SSViewerTest\TestObject()
490
        );
491
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>');
492
    }
493
494
    public function testLocalFunctionsTakePriorityOverGlobals()
495
    {
496
        $data = new ArrayData([
497
            'Page' => new SSViewerTest\TestObject()
498
        ]);
499
500
        //call method with lots of arguments
501
        $result = $this->render(
502
            '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>',
503
            $data
504
        );
505
        $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments");
506
507
        //call method that does not exist
508
        $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data);
509
        $this->assertEquals("", $result, "Method does not exist - empty result");
510
511
        //call if that does not exist
512
        $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data);
513
        $this->assertEquals("", $result, "Method does not exist - empty result");
514
515
        //call method with same name as a global method (local call should take priority)
516
        $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data);
517
        $this->assertEquals(
518
            "testLocalFunctionPriorityCalled",
519
            $result,
520
            "Local Object's public function called. Did not return the actual baseURL of the current site"
521
        );
522
    }
523
524
    public function testCurrentScopeLoopWith()
525
    {
526
        // Data to run the loop tests on - one sequence of three items, each with a subitem
527
        $data = new ArrayData([
528
            'Foo' => new ArrayList([
529
                'Subocean' => new ArrayData([
530
                    'Name' => 'Higher'
531
                ]),
532
                new ArrayData([
533
                    'Sub' => new ArrayData([
534
                        'Name' => 'SubKid1'
535
                    ])
536
                ]),
537
                new ArrayData([
538
                    'Sub' => new ArrayData([
539
                        'Name' => 'SubKid2'
540
                    ])
541
                ]),
542
                new SSViewerTest\TestObject('Number6')
543
            ])
544
        ]);
545
546
        $result = $this->render(
547
            '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
548
            $data
549
        );
550
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
551
552
        $result = $this->render(
553
            '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
554
            $data
555
        );
556
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
557
558
        $result = $this->render('<% with Foo %>$Count<% end_with %>', $data);
559
        $this->assertEquals("4", $result, "4 items in the DataObjectSet");
560
561
        $result = $this->render(
562
            '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
563
            . '<% end_if %><% end_loop %><% end_with %>',
564
            $data
565
        );
566
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works");
567
568
        $result = $this->render(
569
            '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
570
            . '<% end_if %><% end_loop %><% end_with %>',
571
            $data
572
        );
573
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works");
574
    }
575
576
    public function testObjectDotArguments()
577
    {
578
        $this->assertEquals(
579
            '[out:TestObject.methodWithOneArgument(one)]
580
				[out:TestObject.methodWithTwoArguments(one,two)]
581
				[out:TestMethod(Arg1,Arg2).Bar.Val]
582
				[out:TestMethod(Arg1,Arg2).Bar]
583
				[out:TestMethod(Arg1,Arg2)]
584
				[out:TestMethod(Arg1).Bar.Val]
585
				[out:TestMethod(Arg1).Bar]
586
				[out:TestMethod(Arg1)]',
587
            $this->render(
588
                '$TestObject.methodWithOneArgument(one)
589
				$TestObject.methodWithTwoArguments(one,two)
590
				$TestMethod(Arg1, Arg2).Bar.Val
591
				$TestMethod(Arg1, Arg2).Bar
592
				$TestMethod(Arg1, Arg2)
593
				$TestMethod(Arg1).Bar.Val
594
				$TestMethod(Arg1).Bar
595
				$TestMethod(Arg1)'
596
            )
597
        );
598
    }
599
600
    public function testEscapedArguments()
601
    {
602
        $this->assertEquals(
603
            '[out:Foo(Arg1,Arg2).Bar.Val].Suffix
604
				[out:Foo(Arg1,Arg2).Val]_Suffix
605
				[out:Foo(Arg1,Arg2)]/Suffix
606
				[out:Foo(Arg1).Bar.Val]textSuffix
607
				[out:Foo(Arg1).Bar].Suffix
608
				[out:Foo(Arg1)].Suffix
609
				[out:Foo.Bar.Val].Suffix
610
				[out:Foo.Bar].Suffix
611
				[out:Foo].Suffix',
612
            $this->render(
613
                '{$Foo(Arg1, Arg2).Bar.Val}.Suffix
614
				{$Foo(Arg1, Arg2).Val}_Suffix
615
				{$Foo(Arg1, Arg2)}/Suffix
616
				{$Foo(Arg1).Bar.Val}textSuffix
617
				{$Foo(Arg1).Bar}.Suffix
618
				{$Foo(Arg1)}.Suffix
619
				{$Foo.Bar.Val}.Suffix
620
				{$Foo.Bar}.Suffix
621
				{$Foo}.Suffix'
622
            )
623
        );
624
    }
625
626
    public function testLoopWhitespace()
627
    {
628
        $this->assertEquals(
629
            'before[out:SingleItem.Test]after
630
				beforeTestafter',
631
            $this->render(
632
                'before<% loop SingleItem %>$Test<% end_loop %>after
633
				before<% loop SingleItem %>Test<% end_loop %>after'
634
            )
635
        );
636
637
        // The control tags are removed from the output, but no whitespace
638
        // This is a quirk that could be changed, but included in the test to make the current
639
        // behaviour explicit
640
        $this->assertEquals(
641
            'before
642
643
[out:SingleItem.ItemOnItsOwnLine]
644
645
after',
646
            $this->render(
647
                'before
648
<% loop SingleItem %>
649
$ItemOnItsOwnLine
650
<% end_loop %>
651
after'
652
            )
653
        );
654
655
        // The whitespace within the control tags is preserve in a loop
656
        // This is a quirk that could be changed, but included in the test to make the current
657
        // behaviour explicit
658
        $this->assertEquals(
659
            'before
660
661
[out:Loop3.ItemOnItsOwnLine]
662
663
[out:Loop3.ItemOnItsOwnLine]
664
665
[out:Loop3.ItemOnItsOwnLine]
666
667
after',
668
            $this->render(
669
                'before
670
<% loop Loop3 %>
671
$ItemOnItsOwnLine
672
<% end_loop %>
673
after'
674
            )
675
        );
676
    }
677
678
    public function testControls()
679
    {
680
        // Single item controls
681
        $this->assertEquals(
682
            'a[out:Foo.Bar.Item]b
683
				[out:Foo.Bar(Arg1).Item]
684
				[out:Foo(Arg1).Item]
685
				[out:Foo(Arg1,Arg2).Item]
686
				[out:Foo(Arg1,Arg2,Arg3).Item]',
687
            $this->render(
688
                '<% with Foo.Bar %>a{$Item}b<% end_with %>
689
				<% with Foo.Bar(Arg1) %>$Item<% end_with %>
690
				<% with Foo(Arg1) %>$Item<% end_with %>
691
				<% with Foo(Arg1, Arg2) %>$Item<% end_with %>
692
				<% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>'
693
            )
694
        );
695
696
        // Loop controls
697
        $this->assertEquals(
698
            'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
699
            $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
700
        );
701
702
        $this->assertEquals(
703
            '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
704
            $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
705
        );
706
707
        $this->assertEquals(
708
            '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
709
            $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
710
        );
711
712
        $this->assertEquals(
713
            '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
714
            $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
715
        );
716
717
        $this->assertEquals(
718
            '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
719
            $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
720
        );
721
    }
722
723
    public function testIfBlocks()
724
    {
725
        // Basic test
726
        $this->assertEquals(
727
            'AC',
728
            $this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
729
        );
730
731
        // Nested test
732
        $this->assertEquals(
733
            'AB1C',
734
            $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
735
        );
736
737
        // else_if
738
        $this->assertEquals(
739
            'ACD',
740
            $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
741
        );
742
        $this->assertEquals(
743
            'AD',
744
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
745
        );
746
        $this->assertEquals(
747
            'ADE',
748
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
749
        );
750
751
        $this->assertEquals(
752
            'ADE',
753
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
754
        );
755
756
        // Dot syntax
757
        $this->assertEquals(
758
            'ACD',
759
            $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
760
        );
761
        $this->assertEquals(
762
            'ACD',
763
            $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
764
        );
765
766
        // Params
767
        $this->assertEquals(
768
            'ACD',
769
            $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
770
        );
771
        $this->assertEquals(
772
            'ABD',
773
            $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
774
        );
775
776
        // Negation
777
        $this->assertEquals(
778
            'AC',
779
            $this->render('A<% if not IsSet %>B<% end_if %>C')
780
        );
781
        $this->assertEquals(
782
            'ABC',
783
            $this->render('A<% if not NotSet %>B<% end_if %>C')
784
        );
785
786
        // Or
787
        $this->assertEquals(
788
            'ABD',
789
            $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
790
        );
791
        $this->assertEquals(
792
            'ACD',
793
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
794
        );
795
        $this->assertEquals(
796
            'AD',
797
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
798
        );
799
        $this->assertEquals(
800
            'ACD',
801
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
802
        );
803
        $this->assertEquals(
804
            'AD',
805
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
806
        );
807
808
        // Negated Or
809
        $this->assertEquals(
810
            'ACD',
811
            $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
812
        );
813
        $this->assertEquals(
814
            'ABD',
815
            $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
816
        );
817
        $this->assertEquals(
818
            'ABD',
819
            $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
820
        );
821
822
        // And
823
        $this->assertEquals(
824
            'ABD',
825
            $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
826
        );
827
        $this->assertEquals(
828
            'ACD',
829
            $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
830
        );
831
        $this->assertEquals(
832
            'AD',
833
            $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
834
        );
835
        $this->assertEquals(
836
            'ACD',
837
            $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
838
        );
839
        $this->assertEquals(
840
            'AD',
841
            $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
842
        );
843
844
        // Equality
845
        $this->assertEquals(
846
            'ABC',
847
            $this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
848
        );
849
        $this->assertEquals(
850
            'ACD',
851
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
852
        );
853
        $this->assertEquals(
854
            'ABC',
855
            $this->render('A<% if Right != Wrong %>B<% end_if %>C')
856
        );
857
        $this->assertEquals(
858
            'AD',
859
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
860
        );
861
862
        // test inequalities with simple numbers
863
        $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D'));
864
        $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D'));
865
        $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D'));
866
        $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D'));
867
868
        $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D'));
869
        $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D'));
870
        $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D'));
871
        $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D'));
872
873
        $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D'));
874
        $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D'));
875
        $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D'));
876
        $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D'));
877
878
        // empty else_if and else tags, if this would not be supported,
879
        // the output would stop after A, thereby failing the assert
880
        $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D'));
881
        $this->assertEquals(
882
            'AD',
883
            $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
884
        );
885
        $this->assertEquals(
886
            'AD',
887
            $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
888
        );
889
890
        // Bare words with ending space
891
        $this->assertEquals(
892
            'ABC',
893
            $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
894
        );
895
896
        // Else
897
        $this->assertEquals(
898
            'ADE',
899
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
900
        );
901
902
        // Empty if with else
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
903
        $this->assertEquals(
904
            'ABC',
905
            $this->render('A<% if NotSet %><% else %>B<% end_if %>C')
906
        );
907
    }
908
909
    public function testBaseTagGeneration()
910
    {
911
        // XHTML wil have a closed base tag
912
        $tmpl1 = '<?xml version="1.0" encoding="UTF-8"?>
913
			<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
914
            . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
915
			<html>
916
				<head><% base_tag %></head>
917
				<body><p>test</p><body>
918
			</html>';
919
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $this->render($tmpl1));
920
921
        // HTML4 and 5 will only have it for IE
922
        $tmpl2 = '<!DOCTYPE html>
923
			<html>
924
				<head><% base_tag %></head>
925
				<body><p>test</p><body>
926
			</html>';
927
        $this->assertRegExp(
928
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
929
            $this->render($tmpl2)
930
        );
931
932
933
        $tmpl3 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
934
			<html>
935
				<head><% base_tag %></head>
936
				<body><p>test</p><body>
937
			</html>';
938
        $this->assertRegExp(
939
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
940
            $this->render($tmpl3)
941
        );
942
943
        // Check that the content negotiator converts to the equally legal formats
944
        $negotiator = new ContentNegotiator();
945
946
        $response = new HTTPResponse($this->render($tmpl1));
947
        $negotiator->html($response);
948
        $this->assertRegExp(
949
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
950
            $response->getBody()
951
        );
952
953
        $response = new HTTPResponse($this->render($tmpl1));
954
        $negotiator->xhtml($response);
955
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
956
    }
957
958
    public function testIncludeWithArguments()
959
    {
960
        $this->assertEquals(
961
            $this->render('<% include SSViewerTestIncludeWithArguments %>'),
962
            '<p>[out:Arg1]</p><p>[out:Arg2]</p>'
963
        );
964
965
        $this->assertEquals(
966
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
967
            '<p>A</p><p>[out:Arg2]</p>'
968
        );
969
970
        $this->assertEquals(
971
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
972
            '<p>A</p><p>B</p>'
973
        );
974
975
        $this->assertEquals(
976
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
977
            '<p>A Bare String</p><p>B Bare String</p>'
978
        );
979
980
        $this->assertEquals(
981
            $this->render(
982
                '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
983
                new ArrayData(array('B' => 'Bar'))
984
            ),
985
            '<p>A</p><p>Bar</p>'
986
        );
987
988
        $this->assertEquals(
989
            $this->render(
990
                '<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
991
                new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
992
            ),
993
            '<p>A</p><p>Bar</p>'
994
        );
995
996
        $this->assertEquals(
997
            $this->render(
998
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
999
                new ArrayData(
1000
                    array('Items' => new ArrayList(
1001
                        array(
1002
                        new ArrayData(array('Title' => 'Foo')),
1003
                        new ArrayData(array('Title' => 'Bar'))
1004
                        )
1005
                    ))
1006
                )
1007
            ),
1008
            'SomeArg - Foo - Bar - SomeArg'
1009
        );
1010
1011
        $this->assertEquals(
1012
            $this->render(
1013
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
1014
                new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))
1015
            ),
1016
            'A - B - A'
1017
        );
1018
1019
        $this->assertEquals(
1020
            $this->render(
1021
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
1022
                new ArrayData(
1023
                    array(
1024
                    'Item' => new ArrayData(
1025
                        array(
1026
                        'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1027
                        )
1028
                    ))
1029
                )
1030
            ),
1031
            'A - B - C - B - A'
1032
        );
1033
1034
        $this->assertEquals(
1035
            $this->render(
1036
                '<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
1037
                new ArrayData(
1038
                    array(
1039
                    'Item' => new ArrayData(
1040
                        array(
1041
                        'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1042
                        )
1043
                    ))
1044
                )
1045
            ),
1046
            'A - A - A'
1047
        );
1048
1049
        $data = new ArrayData(
1050
            array(
1051
            'Nested' => new ArrayData(
1052
                array(
1053
                'Object' => new ArrayData(array('Key' => 'A'))
1054
                )
1055
            ),
1056
            'Object' => new ArrayData(array('Key' => 'B'))
1057
            )
1058
        );
1059
1060
        $tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>');
1061
        $res  = $tmpl->process($data);
1062
        $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments');
1063
    }
1064
1065
    public function testNamespaceInclude()
1066
    {
1067
        $data = new ArrayData([]);
1068
1069
        $this->assertEquals(
1070
            "tests:( NamespaceInclude\n )",
1071
            $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data),
1072
            'Backslashes work for namespace references in includes'
1073
        );
1074
1075
        $this->assertEquals(
1076
            "tests:( NamespaceInclude\n )",
1077
            $this->render('tests:( <% include Namespace\\NamespaceInclude %> )', $data),
1078
            'Escaped backslashes work for namespace references in includes'
1079
        );
1080
1081
        $this->assertEquals(
1082
            "tests:( NamespaceInclude\n )",
1083
            $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data),
1084
            'Forward slashes work for namespace references in includes'
1085
        );
1086
    }
1087
1088
    /**
1089
     * Test search for includes fallback to non-includes folder
1090
     */
1091
    public function testIncludeFallbacks()
1092
    {
1093
        $data = new ArrayData([]);
1094
1095
        $this->assertEquals(
1096
            "tests:( Namespace/Includes/IncludedTwice.ss\n )",
1097
            $this->render('tests:( <% include Namespace\\IncludedTwice %> )', $data),
1098
            'Prefer Includes in the Includes folder'
1099
        );
1100
1101
        $this->assertEquals(
1102
            "tests:( Namespace/Includes/IncludedOnceSub.ss\n )",
1103
            $this->render('tests:( <% include Namespace\\IncludedOnceSub %> )', $data),
1104
            'Includes in only Includes folder can be found'
1105
        );
1106
1107
        $this->assertEquals(
1108
            "tests:( Namespace/IncludedOnceBase.ss\n )",
1109
            $this->render('tests:( <% include Namespace\\IncludedOnceBase %> )', $data),
1110
            'Includes outside of Includes folder can be found'
1111
        );
1112
    }
1113
1114
    public function testRecursiveInclude()
1115
    {
1116
        $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude'));
1117
1118
        $data = new ArrayData(
1119
            array(
1120
            'Title' => 'A',
1121
            'Children' => new ArrayList(
1122
                array(
1123
                new ArrayData(
1124
                    array(
1125
                    'Title' => 'A1',
1126
                    'Children' => new ArrayList(
1127
                        array(
1128
                        new ArrayData(array( 'Title' => 'A1 i', )),
1129
                        new ArrayData(array( 'Title' => 'A1 ii', )),
1130
                        )
1131
                    ),
1132
                    )
1133
                ),
1134
                new ArrayData(array( 'Title' => 'A2', )),
1135
                new ArrayData(array( 'Title' => 'A3', )),
1136
                )
1137
            ),
1138
            )
1139
        );
1140
1141
        $result = $view->process($data);
1142
        // We don't care about whitespace
1143
        $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result));
1144
1145
        $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
1146
    }
1147
1148
    public function assertEqualIgnoringWhitespace($a, $b, $message = '')
1149
    {
1150
        $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message);
1151
    }
1152
1153
    /**
1154
     * See {@link ViewableDataTest} for more extensive casting tests,
1155
     * this test just ensures that basic casting is correctly applied during template parsing.
1156
     */
1157
    public function testCastingHelpers()
1158
    {
1159
        $vd = new SSViewerTest\TestViewableData();
1160
        $vd->TextValue = '<b>html</b>';
1161
        $vd->HTMLValue = '<b>html</b>';
1162
        $vd->UncastedValue = '<b>html</b>';
0 ignored issues
show
Bug Best Practice introduced by
The property UncastedValue does not exist on SilverStripe\View\Tests\...erTest\TestViewableData. Since you implemented __set, consider adding a @property annotation.
Loading history...
1163
1164
        // Value casted as "Text"
1165
        $this->assertEquals(
1166
            '&lt;b&gt;html&lt;/b&gt;',
1167
            $t = SSViewer::fromString('$TextValue')->process($vd)
1168
        );
1169
        $this->assertEquals(
1170
            '<b>html</b>',
1171
            $t = SSViewer::fromString('$TextValue.RAW')->process($vd)
1172
        );
1173
        $this->assertEquals(
1174
            '&lt;b&gt;html&lt;/b&gt;',
1175
            $t = SSViewer::fromString('$TextValue.XML')->process($vd)
1176
        );
1177
1178
        // Value casted as "HTMLText"
1179
        $this->assertEquals(
1180
            '<b>html</b>',
1181
            $t = SSViewer::fromString('$HTMLValue')->process($vd)
1182
        );
1183
        $this->assertEquals(
1184
            '<b>html</b>',
1185
            $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
1186
        );
1187
        $this->assertEquals(
1188
            '&lt;b&gt;html&lt;/b&gt;',
1189
            $t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
1190
        );
1191
1192
        // Uncasted value (falls back to ViewableData::$default_cast="Text")
1193
        $vd = new SSViewerTest\TestViewableData();
1194
        $vd->UncastedValue = '<b>html</b>';
1195
        $this->assertEquals(
1196
            '&lt;b&gt;html&lt;/b&gt;',
1197
            $t = SSViewer::fromString('$UncastedValue')->process($vd)
1198
        );
1199
        $this->assertEquals(
1200
            '<b>html</b>',
1201
            $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
1202
        );
1203
        $this->assertEquals(
1204
            '&lt;b&gt;html&lt;/b&gt;',
1205
            $t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
1206
        );
1207
    }
1208
1209
    public function testSSViewerBasicIteratorSupport()
1210
    {
1211
        $data = new ArrayData(
1212
            array(
1213
            'Set' => new ArrayList(
1214
                array(
1215
                new SSViewerTest\TestObject("1"),
1216
                new SSViewerTest\TestObject("2"),
1217
                new SSViewerTest\TestObject("3"),
1218
                new SSViewerTest\TestObject("4"),
1219
                new SSViewerTest\TestObject("5"),
1220
                new SSViewerTest\TestObject("6"),
1221
                new SSViewerTest\TestObject("7"),
1222
                new SSViewerTest\TestObject("8"),
1223
                new SSViewerTest\TestObject("9"),
1224
                new SSViewerTest\TestObject("10"),
1225
                )
1226
            )
1227
            )
1228
        );
1229
1230
        //base test
1231
        $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data);
1232
        $this->assertEquals("12345678910", $result, "Numbers rendered in order");
1233
1234
        //test First
1235
        $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data);
1236
        $this->assertEquals("1", $result, "Only the first number is rendered");
1237
1238
        //test Last
1239
        $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data);
1240
        $this->assertEquals("10", $result, "Only the last number is rendered");
1241
1242
        //test Even
1243
        $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data);
1244
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1245
1246
        //test Even with quotes
1247
        $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data);
1248
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1249
1250
        //test Even without quotes
1251
        $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data);
1252
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1253
1254
        //test Even with zero-based start index
1255
        $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data);
1256
        $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order");
1257
1258
        //test Odd
1259
        $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data);
1260
        $this->assertEquals("13579", $result, "Odd numbers rendered in order");
1261
1262
        //test FirstLast
1263
        $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data);
1264
        $this->assertEquals("1first10last", $result, "First and last numbers rendered in order");
1265
1266
        //test Middle
1267
        $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data);
1268
        $this->assertEquals("23456789", $result, "Middle numbers rendered in order");
1269
1270
        //test MiddleString
1271
        $result = $this->render(
1272
            '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
1273
            . '<% end_loop %>',
1274
            $data
1275
        );
1276
        $this->assertEquals(
1277
            "2middle3middle4middle5middle6middle7middle8middle9middle",
1278
            $result,
1279
            "Middle numbers rendered in order"
1280
        );
1281
1282
        //test EvenOdd
1283
        $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data);
1284
        $this->assertEquals(
1285
            "oddevenoddevenoddevenoddevenoddeven",
1286
            $result,
1287
            "Even and Odd is returned in sequence numbers rendered in order"
1288
        );
1289
1290
        //test Pos
1291
        $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data);
1292
        $this->assertEquals("12345678910", $result, '$Pos is rendered in order');
1293
1294
        //test Pos
1295
        $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data);
1296
        $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order');
1297
1298
        //test FromEnd
1299
        $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data);
1300
        $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order');
1301
1302
        //test FromEnd
1303
        $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data);
1304
        $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order');
1305
1306
        //test Total
1307
        $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data);
1308
        $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned");
1309
1310
        //test Modulus
1311
        $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data);
1312
        $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order");
1313
1314
        //test MultipleOf 3
1315
        $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data);
1316
        $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned");
1317
1318
        //test MultipleOf 4
1319
        $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data);
1320
        $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned");
1321
1322
        //test MultipleOf 5
1323
        $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data);
1324
        $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned");
1325
1326
        //test MultipleOf 10
1327
        $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data);
1328
        $this->assertEquals(
1329
            "10",
1330
            $result,
1331
            "Only numbers that are multiples of 10 (with 1-based indexing) are returned"
1332
        );
1333
1334
        //test MultipleOf 9 zero-based
1335
        $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data);
1336
        $this->assertEquals(
1337
            "110",
1338
            $result,
1339
            "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
1340
        );
1341
1342
        //test MultipleOf 11
1343
        $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data);
1344
        $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned");
1345
    }
1346
1347
    /**
1348
     * Test $Up works when the scope $Up refers to was entered with a "with" block
1349
     */
1350
    public function testUpInWith()
1351
    {
1352
1353
        // Data to run the loop tests on - three levels deep
1354
        $data = new ArrayData(
1355
            array(
1356
            'Name' => 'Top',
1357
            'Foo' => new ArrayData(
1358
                array(
1359
                'Name' => 'Foo',
1360
                'Bar' => new ArrayData(
1361
                    array(
1362
                    'Name' => 'Bar',
1363
                    'Baz' => new ArrayData(
1364
                        array(
1365
                        'Name' => 'Baz'
1366
                        )
1367
                    ),
1368
                    'Qux' => new ArrayData(
1369
                        array(
1370
                        'Name' => 'Qux'
1371
                        )
1372
                    )
1373
                    )
1374
                )
1375
                )
1376
            )
1377
            )
1378
        );
1379
1380
        // Basic functionality
1381
        $this->assertEquals(
1382
            'BarFoo',
1383
            $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data)
1384
        );
1385
1386
        // Two level with block, up refers to internally referenced Bar
1387
        $this->assertEquals(
1388
            'BarFoo',
1389
            $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data)
1390
        );
1391
1392
        // Stepping up & back down the scope tree
1393
        $this->assertEquals(
1394
            'BazBarQux',
1395
            $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data)
1396
        );
1397
1398
        // Using $Up in a with block
1399
        $this->assertEquals(
1400
            'BazBarQux',
1401
            $this->render(
1402
                '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>'
1403
                . '<% end_with %>',
1404
                $data
1405
            )
1406
        );
1407
1408
        // Stepping up & back down the scope tree with with blocks
1409
        $this->assertEquals(
1410
            'BazBarQuxBarBaz',
1411
            $this->render(
1412
                '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>'
1413
                . '{$Name}<% end_with %>{$Name}<% end_with %>',
1414
                $data
1415
            )
1416
        );
1417
1418
        // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
1419
        $this->assertEquals(
1420
            'Foo',
1421
            $this->render(
1422
                '<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>'
1423
                . '<% end_with %>',
1424
                $data
1425
            )
1426
        );
1427
1428
        // Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
1429
        $this->assertEquals(
1430
            'Foo',
1431
            $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data)
1432
        );
1433
    }
1434
1435
    /**
1436
     * Test $Up works when the scope $Up refers to was entered with a "loop" block
1437
     */
1438
    public function testUpInLoop()
1439
    {
1440
1441
        // Data to run the loop tests on - one sequence of three items, each with a subitem
1442
        $data = new ArrayData(
1443
            array(
1444
            'Name' => 'Top',
1445
            'Foo' => new ArrayList(
1446
                array(
1447
                new ArrayData(
1448
                    array(
1449
                    'Name' => '1',
1450
                    'Sub' => new ArrayData(
1451
                        array(
1452
                        'Name' => 'Bar'
1453
                        )
1454
                    )
1455
                    )
1456
                ),
1457
                new ArrayData(
1458
                    array(
1459
                    'Name' => '2',
1460
                    'Sub' => new ArrayData(
1461
                        array(
1462
                        'Name' => 'Baz'
1463
                        )
1464
                    )
1465
                    )
1466
                ),
1467
                new ArrayData(
1468
                    array(
1469
                    'Name' => '3',
1470
                    'Sub' => new ArrayData(
1471
                        array(
1472
                        'Name' => 'Qux'
1473
                        )
1474
                    )
1475
                    )
1476
                )
1477
                )
1478
            )
1479
            )
1480
        );
1481
1482
        // Make sure inside a loop, $Up refers to the current item of the loop
1483
        $this->assertEqualIgnoringWhitespace(
1484
            '111 222 333',
1485
            $this->render(
1486
                '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
1487
                $data
1488
            )
1489
        );
1490
1491
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1492
        // and doesn't interfere with the original iterator
1493
        $this->assertEqualIgnoringWhitespace(
1494
            '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
1495
            $this->render(
1496
                '<% loop $Foo %>
1497
					$Name
1498
					<% with $Sub %>
1499
						$Name
1500
						<% loop $Up %>$Name<% end_loop %>
1501
						$Name
1502
					<% end_with %>
1503
					$Name
1504
				<% end_loop %>',
1505
                $data
1506
            )
1507
        );
1508
1509
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1510
        // and doesn't interfere with the original iterator or local lookups
1511
        $this->assertEqualIgnoringWhitespace(
1512
            '1 Bar1 123 1Bar 1   2 Baz2 123 2Baz 2   3 Qux3 123 3Qux 3',
1513
            $this->render(
1514
                '<% loop $Foo %>
1515
					$Name
1516
					<% with $Sub %>
1517
						{$Name}{$Up.Name}
1518
						<% loop $Up %>$Name<% end_loop %>
1519
						{$Up.Name}{$Name}
1520
					<% end_with %>
1521
					$Name
1522
				<% end_loop %>',
1523
                $data
1524
            )
1525
        );
1526
    }
1527
1528
    /**
1529
     * Test that nested loops restore the loop variables correctly when pushing and popping states
1530
     */
1531
    public function testNestedLoops()
1532
    {
1533
1534
        // Data to run the loop tests on - one sequence of three items, one with child elements
1535
        // (of a different size to the main sequence)
1536
        $data = new ArrayData(
1537
            array(
1538
            'Foo' => new ArrayList(
1539
                array(
1540
                new ArrayData(
1541
                    array(
1542
                    'Name' => '1',
1543
                    'Children' => new ArrayList(
1544
                        array(
1545
                        new ArrayData(
1546
                            array(
1547
                            'Name' => 'a'
1548
                            )
1549
                        ),
1550
                        new ArrayData(
1551
                            array(
1552
                            'Name' => 'b'
1553
                            )
1554
                        ),
1555
                        )
1556
                    ),
1557
                    )
1558
                ),
1559
                new ArrayData(
1560
                    array(
1561
                    'Name' => '2',
1562
                    'Children' => new ArrayList(),
1563
                    )
1564
                ),
1565
                new ArrayData(
1566
                    array(
1567
                    'Name' => '3',
1568
                    'Children' => new ArrayList(),
1569
                    )
1570
                ),
1571
                )
1572
            ),
1573
            )
1574
        );
1575
1576
        // Make sure that including a loop inside a loop will not destroy the internal count of
1577
        // items, checked by using "Last"
1578
        $this->assertEqualIgnoringWhitespace(
1579
            '1ab23last',
1580
            $this->render(
1581
                '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
1582
                . '<% end_loop %>',
1583
                $data
1584
            )
1585
        );
1586
    }
1587
1588
    public function testLayout()
1589
    {
1590
        $this->useTestTheme(
1591
            __DIR__ . '/SSViewerTest',
1592
            'layouttest',
1593
            function () {
1594
                $template = new SSViewer(array('Page'));
1595
                $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
1596
1597
                $template = new SSViewer(array('Shortcodes', 'Page'));
1598
                $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
1599
            }
1600
        );
1601
    }
1602
1603
    /**
1604
     * @covers \SilverStripe\View\SSViewer::get_templates_by_class()
1605
     */
1606
    public function testGetTemplatesByClass()
1607
    {
1608
        $this->useTestTheme(
1609
            __DIR__ . '/SSViewerTest',
1610
            'layouttest',
1611
            function () {
1612
            // Test passing a string
1613
                $templates = SSViewer::get_templates_by_class(
1614
                    SSViewerTestModelController::class,
1615
                    '',
1616
                    Controller::class
1617
                );
1618
                $this->assertEquals(
1619
                    [
1620
                    SSViewerTestModelController::class,
1621
                    [
1622
                        'type' => 'Includes',
1623
                        SSViewerTestModelController::class,
1624
                    ],
1625
                    SSViewerTestModel::class,
1626
                    Controller::class,
1627
                    [
1628
                        'type' => 'Includes',
1629
                        Controller::class,
1630
                    ],
1631
                    ],
1632
                    $templates
1633
                );
1634
1635
            // Test to ensure we're stopping at the base class.
1636
                $templates = SSViewer::get_templates_by_class(
1637
                    SSViewerTestModelController::class,
1638
                    '',
1639
                    SSViewerTestModelController::class
1640
                );
1641
                $this->assertEquals(
1642
                    [
1643
                    SSViewerTestModelController::class,
1644
                    [
1645
                        'type' => 'Includes',
1646
                        SSViewerTestModelController::class,
1647
                    ],
1648
                    SSViewerTestModel::class,
1649
                    ],
1650
                    $templates
1651
                );
1652
1653
            // Make sure we can search templates by suffix.
1654
                $templates = SSViewer::get_templates_by_class(
1655
                    SSViewerTestModel::class,
1656
                    'Controller',
1657
                    DataObject::class
1658
                );
1659
                $this->assertEquals(
1660
                    [
1661
                    SSViewerTestModelController::class,
1662
                    [
1663
                        'type' => 'Includes',
1664
                        SSViewerTestModelController::class,
1665
                    ],
1666
                    DataObject::class . 'Controller',
1667
                    [
1668
                        'type' => 'Includes',
1669
                        DataObject::class . 'Controller',
1670
                    ],
1671
                    ],
1672
                    $templates
1673
                );
1674
1675
                // Let's throw something random in there.
1676
                $this->expectException(InvalidArgumentException::class);
1677
                SSViewer::get_templates_by_class(null);
1678
            }
1679
        );
1680
    }
1681
1682
    public function testRewriteHashlinks()
1683
    {
1684
        SSViewer::setRewriteHashLinksDefault(true);
1685
1686
        $_SERVER['HTTP_HOST'] = 'www.mysite.com';
1687
        $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
1688
1689
        // Emulate SSViewer::process()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1690
        // Note that leading double slashes have been rewritten to prevent these being mis-interepreted
1691
        // as protocol-less absolute urls
1692
        $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
1693
1694
        $tmplFile = TEMP_PATH . DIRECTORY_SEPARATOR . 'SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss';
1695
1696
        // Note: SSViewer_FromString doesn't rewrite hash links.
1697
        file_put_contents(
1698
            $tmplFile,
1699
            '<!DOCTYPE html>
1700
			<html>
1701
				<head><% base_tag %></head>
1702
				<body>
1703
				<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
1704
				$ExternalInsertedLink
1705
				<a class="inline" href="#anchor">InlineLink</a>
1706
				$InsertedLink
1707
				<svg><use xlink:href="#sprite"></use></svg>
1708
				<body>
1709
			</html>'
1710
        );
1711
        $tmpl = new SSViewer($tmplFile);
1712
        $obj = new ViewableData();
1713
        $obj->InsertedLink = DBField::create_field(
0 ignored issues
show
Bug Best Practice introduced by
The property InsertedLink does not exist on SilverStripe\View\ViewableData. Since you implemented __set, consider adding a @property annotation.
Loading history...
1714
            'HTMLFragment',
1715
            '<a class="inserted" href="#anchor">InsertedLink</a>'
1716
        );
1717
        $obj->ExternalInsertedLink = DBField::create_field(
0 ignored issues
show
Bug Best Practice introduced by
The property ExternalInsertedLink does not exist on SilverStripe\View\ViewableData. Since you implemented __set, consider adding a @property annotation.
Loading history...
1718
            'HTMLFragment',
1719
            '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
1720
        );
1721
        $result = $tmpl->process($obj);
1722
        $this->assertContains(
1723
            '<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
1724
            $result
1725
        );
1726
        $this->assertContains(
1727
            '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
1728
            $result
1729
        );
1730
        $this->assertContains(
1731
            '<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
1732
            $result
1733
        );
1734
        $this->assertContains(
1735
            '<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
1736
            $result
1737
        );
1738
        $this->assertContains(
1739
            '<svg><use xlink:href="#sprite"></use></svg>',
1740
            $result,
1741
            'SSTemplateParser should only rewrite anchor hrefs'
1742
        );
1743
1744
        unlink($tmplFile);
1745
    }
1746
1747
    public function testRewriteHashlinksInPhpMode()
1748
    {
1749
        SSViewer::setRewriteHashLinksDefault('php');
0 ignored issues
show
Bug introduced by
'php' of type string is incompatible with the type boolean expected by parameter $rewrite of SilverStripe\View\SSView...writeHashLinksDefault(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1749
        SSViewer::setRewriteHashLinksDefault(/** @scrutinizer ignore-type */ 'php');
Loading history...
1750
1751
        $tmplFile = TEMP_PATH . DIRECTORY_SEPARATOR . 'SSViewerTest_testRewriteHashlinksInPhpMode_'
1752
            . sha1(rand()) . '.ss';
1753
1754
        // Note: SSViewer_FromString doesn't rewrite hash links.
1755
        file_put_contents(
1756
            $tmplFile,
1757
            '<!DOCTYPE html>
1758
			<html>
1759
				<head><% base_tag %></head>
1760
				<body>
1761
				<a class="inline" href="#anchor">InlineLink</a>
1762
				$InsertedLink
1763
				<svg><use xlink:href="#sprite"></use></svg>
1764
				<body>
1765
			</html>'
1766
        );
1767
        $tmpl = new SSViewer($tmplFile);
1768
        $obj = new ViewableData();
1769
        $obj->InsertedLink = DBField::create_field(
0 ignored issues
show
Bug Best Practice introduced by
The property InsertedLink does not exist on SilverStripe\View\ViewableData. Since you implemented __set, consider adding a @property annotation.
Loading history...
1770
            'HTMLFragment',
1771
            '<a class="inserted" href="#anchor">InsertedLink</a>'
1772
        );
1773
        $result = $tmpl->process($obj);
1774
1775
        $code = '<a class="inserted" href="<?php echo \SilverStripe\Core\Convert::raw2att(preg_replace("/^(\/)+/", "/",'
1776
            . ' $_SERVER[\'REQUEST_URI\'])); ?>#anchor">InsertedLink</a>';
1777
        $this->assertContains($code, $result);
1778
        // TODO Fix inline links in PHP mode
1779
        // $this->assertContains(
1780
        //  '<a class="inline" href="<?php echo str_replace(',
1781
        //  $result
1782
        // );
1783
        $this->assertContains(
1784
            '<svg><use xlink:href="#sprite"></use></svg>',
1785
            $result,
1786
            'SSTemplateParser should only rewrite anchor hrefs'
1787
        );
1788
1789
        unlink($tmplFile);
1790
    }
1791
1792
    public function testRenderWithSourceFileComments()
1793
    {
1794
        SSViewer::config()->update('source_file_comments', true);
1795
        $i = __DIR__ . '/SSViewerTest/templates/Includes';
1796
        $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
1797
        $templates = array(
1798
        array(
1799
            'name' => 'SSViewerTestCommentsFullSource',
1800
            'expected' => ""
1801
                . "<!doctype html>"
1802
                . "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
1803
                . "<html>"
1804
                . "\t<head></head>"
1805
                . "\t<body></body>"
1806
                . "</html>"
1807
                . "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
1808
        ),
1809
        array(
1810
            'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
1811
            'expected' => ""
1812
                . "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
1813
                . "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
1814
                . "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
1815
                . "<html>"
1816
                . "\t<head></head>"
1817
                . "\t<body></body>"
1818
                . "</html>"
1819
                . "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
1820
        ),
1821
        array(
1822
            'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
1823
            'expected' => ""
1824
                . "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
1825
                . "\t<head></head>"
1826
                . "\t<body></body>"
1827
                . "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
1828
        ),
1829
        array(
1830
            'name' => 'SSViewerTestCommentsFullSourceIfIE',
1831
            'expected' => ""
1832
                . "<!doctype html>"
1833
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
1834
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1835
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1836
                . "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
1837
                . "\t<head></head>"
1838
                . "\t<body></body>"
1839
                . "</html>"
1840
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
1841
        ),
1842
        array(
1843
            'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
1844
            'expected' => ""
1845
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1846
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1847
                . "<!--[if !IE]><!--> <html class='no-ie'>"
1848
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
1849
                . " <!--<![endif]-->"
1850
                . "\t<head></head>"
1851
                . "\t<body></body>"
1852
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
1853
        ),
1854
        array(
1855
            'name' => 'SSViewerTestCommentsPartialSource',
1856
            'expected' =>
1857
            "<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
1858
                . "<div class='typography'></div>"
1859
                . "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
1860
        ),
1861
        array(
1862
            'name' => 'SSViewerTestCommentsWithInclude',
1863
            'expected' =>
1864
            "<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
1865
                . "<div class='typography'>"
1866
                . "<!-- include 'SSViewerTestCommentsInclude' -->"
1867
                . "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
1868
                . "Included"
1869
                . "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
1870
                . "<!-- end include 'SSViewerTestCommentsInclude' -->"
1871
                . "</div>"
1872
                . "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
1873
        ),
1874
        );
1875
        foreach ($templates as $template) {
1876
            $this->_renderWithSourceFileComments('SSViewerTestComments/' . $template['name'], $template['expected']);
1877
        }
1878
    }
1879
    private function _renderWithSourceFileComments($name, $expected)
1880
    {
1881
        $viewer = new SSViewer(array($name));
1882
        $data = new ArrayData(array());
1883
        $result = $viewer->process($data);
1884
        $expected = str_replace(array("\r", "\n"), '', $expected);
1885
        $result = str_replace(array("\r", "\n"), '', $result);
1886
        $this->assertEquals($result, $expected);
1887
    }
1888
1889
    public function testLoopIteratorIterator()
1890
    {
1891
        $list = new PaginatedList(new ArrayList());
1892
        $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName<br /><% end_loop %>');
1893
        $result = $viewer->process(new ArrayData(array('List' => $list)));
1894
        $this->assertEquals($result, '');
1895
    }
1896
1897
    public function testProcessOnlyIncludesRequirementsOnce()
1898
    {
1899
        $template = new SSViewer(array('SSViewerTestProcess'));
1900
        $basePath = $this->getCurrentRelativePath() . '/SSViewerTest';
1901
1902
        $backend = Injector::inst()->create(Requirements_Backend::class);
1903
        $backend->setCombinedFilesEnabled(false);
1904
        $backend->combineFiles(
1905
            'RequirementsTest_ab.css',
1906
            array(
1907
            $basePath . '/css/RequirementsTest_a.css',
1908
            $basePath . '/css/RequirementsTest_b.css'
1909
            )
1910
        );
1911
1912
        Requirements::set_backend($backend);
1913
1914
        $this->assertEquals(1, substr_count($template->process(new ViewableData()), "a.css"));
1915
        $this->assertEquals(1, substr_count($template->process(new ViewableData()), "b.css"));
1916
1917
        // if we disable the requirements then we should get nothing
1918
        $template->includeRequirements(false);
1919
        $this->assertEquals(0, substr_count($template->process(new ViewableData()), "a.css"));
1920
        $this->assertEquals(0, substr_count($template->process(new ViewableData()), "b.css"));
1921
    }
1922
1923
    public function testRequireCallInTemplateInclude()
1924
    {
1925
        //TODO undo skip test on the event that templates ever obtain the ability to reference
1926
        //MODULE_DIR (or something to that effect)
1927
        if (FRAMEWORK_DIR === 'framework') {
1928
            $template = new SSViewer(array('SSViewerTestProcess'));
1929
1930
            Requirements::set_suffix_requirements(false);
1931
1932
            $this->assertEquals(
1933
                1,
1934
                substr_count(
1935
                    $template->process(new ViewableData()),
1936
                    "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
1937
                )
1938
            );
1939
        } else {
1940
            $this->markTestSkipped(
1941
                'Requirement will always fail if the framework dir is not ' .
1942
                'named \'framework\', since templates require hard coded paths'
1943
            );
1944
        }
1945
    }
1946
1947
    public function testCallsWithArguments()
1948
    {
1949
        $data = new ArrayData(
1950
            array(
1951
            'Set' => new ArrayList(
1952
                array(
1953
                new SSViewerTest\TestObject("1"),
1954
                new SSViewerTest\TestObject("2"),
1955
                new SSViewerTest\TestObject("3"),
1956
                new SSViewerTest\TestObject("4"),
1957
                new SSViewerTest\TestObject("5"),
1958
                )
1959
            ),
1960
            'Level' => new SSViewerTest\LevelTestData(1),
1961
            'Nest' => array(
1962
            'Level' => new SSViewerTest\LevelTestData(2),
1963
            ),
1964
            )
1965
        );
1966
1967
        $tests = array(
1968
        '$Level.output(1)' => '1-1',
1969
        '$Nest.Level.output($Set.First.Number)' => '2-1',
1970
        '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
1971
        '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
1972
        '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
1973
        '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
1974
        '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
1975
        '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
1976
        '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
1977
        '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
1978
        '<% with $Nest %>
1979
				<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
1980
			<% end_with %>' => '1-hi',
1981
        '<% with $Nest %>
1982
				<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
1983
			<% end_with %>' => '!0!1!2!3!4',
1984
        );
1985
1986
        foreach ($tests as $template => $expected) {
1987
            $this->assertEquals($expected, trim($this->render($template, $data)));
1988
        }
1989
    }
1990
1991
    public function testRepeatedCallsAreCached()
1992
    {
1993
        $data = new SSViewerTest\CacheTestData();
1994
        $template = '
1995
			<% if $TestWithCall %>
1996
				<% with $TestWithCall %>
1997
					{$Message}
1998
				<% end_with %>
1999
2000
				{$TestWithCall.Message}
2001
			<% end_if %>';
2002
2003
        $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data)));
2004
        $this->assertEquals(
2005
            1,
2006
            $data->testWithCalls,
2007
            'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
2008
        );
2009
2010
        $data = new SSViewerTest\CacheTestData();
2011
        $template = '
2012
			<% if $TestLoopCall %>
2013
				<% loop $TestLoopCall %>
2014
					{$Message}
2015
				<% end_loop %>
2016
			<% end_if %>';
2017
2018
        $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data)));
2019
        $this->assertEquals(
2020
            1,
2021
            $data->testLoopCalls,
2022
            'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
2023
        );
2024
    }
2025
2026
    public function testClosedBlockExtension()
2027
    {
2028
        $count = 0;
2029
        $parser = new SSTemplateParser();
2030
        $parser->addClosedBlock(
2031
            'test',
2032
            function ($res) use (&$count) {
0 ignored issues
show
Unused Code introduced by
The parameter $res is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

2032
            function (/** @scrutinizer ignore-unused */ $res) use (&$count) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2033
                $count++;
2034
            }
2035
        );
2036
2037
        $template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
2038
        $template->process(new SSViewerTest\TestFixture());
2039
2040
        $this->assertEquals(1, $count);
2041
    }
2042
2043
    public function testOpenBlockExtension()
2044
    {
2045
        $count = 0;
2046
        $parser = new SSTemplateParser();
2047
        $parser->addOpenBlock(
2048
            'test',
2049
            function ($res) use (&$count) {
0 ignored issues
show
Unused Code introduced by
The parameter $res is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

2049
            function (/** @scrutinizer ignore-unused */ $res) use (&$count) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2050
                $count++;
2051
            }
2052
        );
2053
2054
        $template = new SSViewer_FromString("<% test %>", $parser);
2055
        $template->process(new SSViewerTest\TestFixture());
2056
2057
        $this->assertEquals(1, $count);
2058
    }
2059
2060
    /**
2061
     * Tests if caching for SSViewer_FromString is working
2062
     */
2063
    public function testFromStringCaching()
2064
    {
2065
        $content = 'Test content';
2066
        $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache.' . sha1($content);
2067
        if (file_exists($cacheFile)) {
2068
            unlink($cacheFile);
2069
        }
2070
2071
        // Test global behaviors
2072
        $this->render($content, null, null);
2073
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2074
2075
        SSViewer_FromString::config()->update('cache_template', true);
2076
        $this->render($content, null, null);
2077
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2078
        unlink($cacheFile);
2079
2080
        // Test instance behaviors
2081
        $this->render($content, null, false);
2082
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2083
2084
        $this->render($content, null, true);
2085
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2086
        unlink($cacheFile);
2087
    }
2088
}
2089