Passed
Push — master ( 3f5943...604c83 )
by Damian
07:59
created

SSViewerTest::testCoreGlobalVariableCalls()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 86
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 64
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
    /**
113
     * Ensure global methods aren't executed
114
     */
115
    public function testTemplateExecution()
116
    {
117
        $data = new ArrayData([ 'Var' => 'phpinfo' ]);
118
        $result = $data->renderWith("SSViewerTestPartialTemplate");
119
        $this->assertEquals('Test partial template: phpinfo', trim(preg_replace("/<!--.*-->/U", '', $result)));
120
    }
121
122
    public function testIncludeScopeInheritance()
123
    {
124
        $data = $this->getScopeInheritanceTestData();
125
        $expected = array(
126
        'Item 1 - First-ODD top:Item 1',
127
        'Item 2 - EVEN top:Item 2',
128
        'Item 3 - ODD top:Item 3',
129
        'Item 4 - EVEN top:Item 4',
130
        'Item 5 - ODD top:Item 5',
131
        'Item 6 - Last-EVEN top:Item 6',
132
        );
133
134
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritance');
135
        $this->assertExpectedStrings($result, $expected);
136
137
        // reset results for the tests that include arguments (the title is passed as an arg)
138
        $expected = array(
139
        'Item 1 _ Item 1 - First-ODD top:Item 1',
140
        'Item 2 _ Item 2 - EVEN top:Item 2',
141
        'Item 3 _ Item 3 - ODD top:Item 3',
142
        'Item 4 _ Item 4 - EVEN top:Item 4',
143
        'Item 5 _ Item 5 - ODD top:Item 5',
144
        'Item 6 _ Item 6 - Last-EVEN top:Item 6',
145
        );
146
147
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
148
        $this->assertExpectedStrings($result, $expected);
149
    }
150
151
    public function testIncludeTruthyness()
152
    {
153
        $data = new ArrayData([
154
            'Title' => 'TruthyTest',
155
            'Items' => new ArrayList([
156
                new ArrayData(['Title' => 'Item 1']),
157
                new ArrayData(['Title' => '']),
158
                new ArrayData(['Title' => true]),
159
                new ArrayData(['Title' => false]),
160
                new ArrayData(['Title' => null]),
161
                new ArrayData(['Title' => 0]),
162
                new ArrayData(['Title' => 7])
163
            ])
164
        ]);
165
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
166
167
        // We should not end up with empty values appearing as empty
168
        $expected = [
169
            'Item 1 _ Item 1 - First-ODD top:Item 1',
170
            'Untitled - EVEN top:',
171
            '1 _ 1 - ODD top:1',
172
            'Untitled - EVEN top:',
173
            'Untitled - ODD top:',
174
            'Untitled - EVEN top:0',
175
            '7 _ 7 - Last-ODD top:7',
176
        ];
177
        $this->assertExpectedStrings($result, $expected);
178
    }
179
180
    private function getScopeInheritanceTestData()
181
    {
182
        return new ArrayData([
183
            'Title' => 'TopTitleValue',
184
            'Items' => new ArrayList([
185
                new ArrayData(['Title' => 'Item 1']),
186
                new ArrayData(['Title' => 'Item 2']),
187
                new ArrayData(['Title' => 'Item 3']),
188
                new ArrayData(['Title' => 'Item 4']),
189
                new ArrayData(['Title' => 'Item 5']),
190
                new ArrayData(['Title' => 'Item 6'])
191
            ])
192
        ]);
193
    }
194
195
    private function assertExpectedStrings($result, $expected)
196
    {
197
        foreach ($expected as $expectedStr) {
198
            $this->assertTrue(
199
                (boolean) preg_match("/{$expectedStr}/", $result),
200
                "Didn't find '{$expectedStr}' in:\n{$result}"
201
            );
202
        }
203
    }
204
205
    /**
206
     * Small helper to render templates from strings
207
     *
208
     * @param  string $templateString
209
     * @param  null   $data
210
     * @param  bool   $cacheTemplate
211
     * @return string
212
     */
213
    public function render($templateString, $data = null, $cacheTemplate = false)
214
    {
215
        $t = SSViewer::fromString($templateString, $cacheTemplate);
216
        if (!$data) {
217
            $data = new SSViewerTest\TestFixture();
218
        }
219
        return trim('' . $t->process($data));
220
    }
221
222
    public function testRequirements()
223
    {
224
        /** @var Requirements_Backend|PHPUnit_Framework_MockObject_MockObject $requirements */
225
        $requirements = $this
226
            ->getMockBuilder(Requirements_Backend::class)
227
            ->setMethods(array("javascript", "css"))
228
            ->getMock();
229
        $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js';
230
        $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js';
231
232
        $requirements->expects($this->once())->method('javascript')->with($jsFile);
233
        $requirements->expects($this->once())->method('css')->with($cssFile);
234
235
        $origReq = Requirements::backend();
236
        Requirements::set_backend($requirements);
237
        $template = $this->render(
238
            "<% require javascript($jsFile) %>
239
		<% require css($cssFile) %>"
240
        );
241
        Requirements::set_backend($origReq);
242
243
        $this->assertFalse((bool)trim($template), "Should be no content in this return.");
244
    }
245
246
    public function testRequirementsCombine()
247
    {
248
        /** @var Requirements_Backend $testBackend */
249
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
250
        $testBackend->setSuffixRequirements(false);
251
        $testBackend->setCombinedFilesEnabled(true);
252
253
        $jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js';
254
        $jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile);
255
        $testBackend->combineFiles('testRequirementsCombine.js', array($jsFile));
256
257
        // secondly, make sure that requirements is generated, even though minification failed
258
        $testBackend->processCombinedFiles();
259
        $js = array_keys($testBackend->getJavascript());
260
        $combinedTestFilePath = Director::publicFolder() . reset($js);
261
        $this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath);
262
263
        // and make sure the combined content matches the input content, i.e. no loss of functionality
264
        if (!file_exists($combinedTestFilePath)) {
265
            $this->fail('No combined file was created at expected path: ' . $combinedTestFilePath);
266
        }
267
        $combinedTestFileContents = file_get_contents($combinedTestFilePath);
268
        $this->assertContains($jsFileContents, $combinedTestFileContents);
269
    }
270
271
    public function testRequirementsMinification()
272
    {
273
        /** @var Requirements_Backend $testBackend */
274
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
275
        $testBackend->setSuffixRequirements(false);
276
        $testBackend->setMinifyCombinedFiles(true);
277
        $testBackend->setCombinedFilesEnabled(true);
278
279
        $testFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/RequirementsTest_a.js';
280
        $testFileContent = file_get_contents($testFile);
281
282
        $mockMinifier = $this->getMockBuilder(Requirements_Minifier::class)
283
        ->setMethods(['minify'])
284
        ->getMock();
285
286
        $mockMinifier->expects($this->once())
287
        ->method('minify')
288
        ->with(
289
            $testFileContent,
290
            'js',
291
            $testFile
292
        );
293
        $testBackend->setMinifier($mockMinifier);
294
        $testBackend->combineFiles('testRequirementsMinified.js', array($testFile));
295
        $testBackend->processCombinedFiles();
296
297
        $testBackend->setMinifyCombinedFiles(false);
298
        $mockMinifier->expects($this->never())
299
        ->method('minify');
300
        $testBackend->processCombinedFiles();
301
302
        $this->expectException(Exception::class);
303
        $this->expectExceptionMessageRegExp('/^Cannot minify files without a minification service defined./');
304
305
        $testBackend->setMinifyCombinedFiles(true);
306
        $testBackend->setMinifier(null);
307
        $testBackend->processCombinedFiles();
308
    }
309
310
311
312
    public function testComments()
313
    {
314
        $input = <<<SS
315
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content
316
<%-- Alone multi
317
	line comment --%>
318
Some more content
319
Mixing content and <%-- multi
320
	line comment --%> Final final
321
content
322
SS;
323
        $output = $this->render($input);
324
        $shouldbe = <<<SS
325
This is my templateThis is some contentFinal content
326
327
Some more content
328
Mixing content and  Final final
329
content
330
SS;
331
        $this->assertEquals($shouldbe, $output);
332
    }
333
334
    public function testBasicText()
335
    {
336
        $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone');
337
        $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone');
338
        $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped');
339
        $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped');
340
    }
341
342
    public function testBasicInjection()
343
    {
344
        $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection');
345
        $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection');
346
        $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection');
347
        $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection');
348
349
        $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped');
350
        $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character');
351
        $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character');
352
353
        $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection');
354
        $this->assertEquals(
355
            '{\\[out:Test]}',
356
            $this->render('{\\\\$Test}'),
357
            'Escapes before injections are correctly unescaped'
358
        );
359
    }
360
361
362
    public function testGlobalVariableCalls()
363
    {
364
        $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic'));
365
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString'));
366
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray'));
367
    }
368
369
    public function testGlobalVariableCallsWithArguments()
370
    {
371
        $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments'));
372
        $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")'));
373
        $this->assertEquals(
374
            'zFoo:Bar:Bazz',
375
            $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
376
        );
377
        $this->assertEquals(
378
            'zreferencez',
379
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
380
        );
381
    }
382
383
    public function testGlobalVariablesAreEscaped()
384
    {
385
        $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLFragment'));
386
        $this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped'));
387
388
        $this->assertEquals(
389
            'z<div></div>z',
390
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
391
        );
392
        $this->assertEquals(
393
            'z&lt;div&gt;&lt;/div&gt;z',
394
            $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
395
        );
396
    }
397
398
    public function testCoreGlobalVariableCalls()
399
    {
400
        $this->assertEquals(
401
            Director::absoluteBaseURL(),
402
            $this->render('{$absoluteBaseURL}'),
403
            'Director::absoluteBaseURL can be called from within template'
404
        );
405
        $this->assertEquals(
406
            Director::absoluteBaseURL(),
407
            $this->render('{$AbsoluteBaseURL}'),
408
            'Upper-case %AbsoluteBaseURL can be called from within template'
409
        );
410
411
        $this->assertEquals(
412
            Director::is_ajax(),
413
            $this->render('{$isAjax}'),
414
            'All variations of is_ajax result in the correct call'
415
        );
416
        $this->assertEquals(
417
            Director::is_ajax(),
418
            $this->render('{$IsAjax}'),
419
            'All variations of is_ajax result in the correct call'
420
        );
421
        $this->assertEquals(
422
            Director::is_ajax(),
423
            $this->render('{$is_ajax}'),
424
            'All variations of is_ajax result in the correct call'
425
        );
426
        $this->assertEquals(
427
            Director::is_ajax(),
428
            $this->render('{$Is_ajax}'),
429
            'All variations of is_ajax result in the correct call'
430
        );
431
432
        $this->assertEquals(
433
            i18n::get_locale(),
434
            $this->render('{$i18nLocale}'),
435
            'i18n template functions result correct result'
436
        );
437
        $this->assertEquals(
438
            i18n::get_locale(),
439
            $this->render('{$get_locale}'),
440
            'i18n template functions result correct result'
441
        );
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
        $this->assertEquals(
454
            (string)Security::getCurrentUser(),
455
            $this->render('{$currentMember}'),
456
            'Member template functions result correct result'
457
        );
458
        $this->assertEquals(
459
            (string)Security::getCurrentUser(),
460
            $this->render('{$currentUser}'),
461
            'Member template functions result correct result'
462
        );
463
464
        $this->assertEquals(
465
            SecurityToken::getSecurityID(),
466
            $this->render('{$getSecurityID}'),
467
            'SecurityToken template functions result correct result'
468
        );
469
        $this->assertEquals(
470
            SecurityToken::getSecurityID(),
471
            $this->render('{$SecurityID}'),
472
            'SecurityToken template functions result correct result'
473
        );
474
475
        $this->assertEquals(
476
            Permission::check("ADMIN"),
477
            (bool)$this->render('{$HasPerm(\'ADMIN\')}'),
478
            'Permissions template functions result correct result'
479
        );
480
        $this->assertEquals(
481
            Permission::check("ADMIN"),
482
            (bool)$this->render('{$hasPerm(\'ADMIN\')}'),
483
            'Permissions template functions result correct result'
484
        );
485
    }
486
487
    public function testNonFieldCastingHelpersNotUsedInHasValue()
488
    {
489
        // check if Link without $ in front of variable
490
        $result = $this->render(
491
            'A<% if Link %>$Link<% end_if %>B',
492
            new SSViewerTest\TestObject()
493
        );
494
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>');
495
496
        // check if Link with $ in front of variable
497
        $result = $this->render(
498
            'A<% if $Link %>$Link<% end_if %>B',
499
            new SSViewerTest\TestObject()
500
        );
501
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>');
502
    }
503
504
    public function testLocalFunctionsTakePriorityOverGlobals()
505
    {
506
        $data = new ArrayData([
507
            'Page' => new SSViewerTest\TestObject()
508
        ]);
509
510
        //call method with lots of arguments
511
        $result = $this->render(
512
            '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>',
513
            $data
514
        );
515
        $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments");
516
517
        //call method that does not exist
518
        $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data);
519
        $this->assertEquals("", $result, "Method does not exist - empty result");
520
521
        //call if that does not exist
522
        $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data);
523
        $this->assertEquals("", $result, "Method does not exist - empty result");
524
525
        //call method with same name as a global method (local call should take priority)
526
        $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data);
527
        $this->assertEquals(
528
            "testLocalFunctionPriorityCalled",
529
            $result,
530
            "Local Object's public function called. Did not return the actual baseURL of the current site"
531
        );
532
    }
533
534
    public function testCurrentScopeLoopWith()
535
    {
536
        // Data to run the loop tests on - one sequence of three items, each with a subitem
537
        $data = new ArrayData([
538
            'Foo' => new ArrayList([
539
                'Subocean' => new ArrayData([
540
                    'Name' => 'Higher'
541
                ]),
542
                new ArrayData([
543
                    'Sub' => new ArrayData([
544
                        'Name' => 'SubKid1'
545
                    ])
546
                ]),
547
                new ArrayData([
548
                    'Sub' => new ArrayData([
549
                        'Name' => 'SubKid2'
550
                    ])
551
                ]),
552
                new SSViewerTest\TestObject('Number6')
553
            ])
554
        ]);
555
556
        $result = $this->render(
557
            '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
558
            $data
559
        );
560
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
561
562
        $result = $this->render(
563
            '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
564
            $data
565
        );
566
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
567
568
        $result = $this->render('<% with Foo %>$Count<% end_with %>', $data);
569
        $this->assertEquals("4", $result, "4 items in the DataObjectSet");
570
571
        $result = $this->render(
572
            '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
573
            . '<% end_if %><% end_loop %><% end_with %>',
574
            $data
575
        );
576
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works");
577
578
        $result = $this->render(
579
            '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
580
            . '<% end_if %><% end_loop %><% end_with %>',
581
            $data
582
        );
583
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works");
584
    }
585
586
    public function testObjectDotArguments()
587
    {
588
        $this->assertEquals(
589
            '[out:TestObject.methodWithOneArgument(one)]
590
				[out:TestObject.methodWithTwoArguments(one,two)]
591
				[out:TestMethod(Arg1,Arg2).Bar.Val]
592
				[out:TestMethod(Arg1,Arg2).Bar]
593
				[out:TestMethod(Arg1,Arg2)]
594
				[out:TestMethod(Arg1).Bar.Val]
595
				[out:TestMethod(Arg1).Bar]
596
				[out:TestMethod(Arg1)]',
597
            $this->render(
598
                '$TestObject.methodWithOneArgument(one)
599
				$TestObject.methodWithTwoArguments(one,two)
600
				$TestMethod(Arg1, Arg2).Bar.Val
601
				$TestMethod(Arg1, Arg2).Bar
602
				$TestMethod(Arg1, Arg2)
603
				$TestMethod(Arg1).Bar.Val
604
				$TestMethod(Arg1).Bar
605
				$TestMethod(Arg1)'
606
            )
607
        );
608
    }
609
610
    public function testEscapedArguments()
611
    {
612
        $this->assertEquals(
613
            '[out:Foo(Arg1,Arg2).Bar.Val].Suffix
614
				[out:Foo(Arg1,Arg2).Val]_Suffix
615
				[out:Foo(Arg1,Arg2)]/Suffix
616
				[out:Foo(Arg1).Bar.Val]textSuffix
617
				[out:Foo(Arg1).Bar].Suffix
618
				[out:Foo(Arg1)].Suffix
619
				[out:Foo.Bar.Val].Suffix
620
				[out:Foo.Bar].Suffix
621
				[out:Foo].Suffix',
622
            $this->render(
623
                '{$Foo(Arg1, Arg2).Bar.Val}.Suffix
624
				{$Foo(Arg1, Arg2).Val}_Suffix
625
				{$Foo(Arg1, Arg2)}/Suffix
626
				{$Foo(Arg1).Bar.Val}textSuffix
627
				{$Foo(Arg1).Bar}.Suffix
628
				{$Foo(Arg1)}.Suffix
629
				{$Foo.Bar.Val}.Suffix
630
				{$Foo.Bar}.Suffix
631
				{$Foo}.Suffix'
632
            )
633
        );
634
    }
635
636
    public function testLoopWhitespace()
637
    {
638
        $this->assertEquals(
639
            'before[out:SingleItem.Test]after
640
				beforeTestafter',
641
            $this->render(
642
                'before<% loop SingleItem %>$Test<% end_loop %>after
643
				before<% loop SingleItem %>Test<% end_loop %>after'
644
            )
645
        );
646
647
        // The control tags are removed from the output, but no whitespace
648
        // This is a quirk that could be changed, but included in the test to make the current
649
        // behaviour explicit
650
        $this->assertEquals(
651
            'before
652
653
[out:SingleItem.ItemOnItsOwnLine]
654
655
after',
656
            $this->render(
657
                'before
658
<% loop SingleItem %>
659
$ItemOnItsOwnLine
660
<% end_loop %>
661
after'
662
            )
663
        );
664
665
        // The whitespace within the control tags is preserve in a loop
666
        // This is a quirk that could be changed, but included in the test to make the current
667
        // behaviour explicit
668
        $this->assertEquals(
669
            'before
670
671
[out:Loop3.ItemOnItsOwnLine]
672
673
[out:Loop3.ItemOnItsOwnLine]
674
675
[out:Loop3.ItemOnItsOwnLine]
676
677
after',
678
            $this->render(
679
                'before
680
<% loop Loop3 %>
681
$ItemOnItsOwnLine
682
<% end_loop %>
683
after'
684
            )
685
        );
686
    }
687
688
    public function testControls()
689
    {
690
        // Single item controls
691
        $this->assertEquals(
692
            'a[out:Foo.Bar.Item]b
693
				[out:Foo.Bar(Arg1).Item]
694
				[out:Foo(Arg1).Item]
695
				[out:Foo(Arg1,Arg2).Item]
696
				[out:Foo(Arg1,Arg2,Arg3).Item]',
697
            $this->render(
698
                '<% with Foo.Bar %>a{$Item}b<% end_with %>
699
				<% with Foo.Bar(Arg1) %>$Item<% end_with %>
700
				<% with Foo(Arg1) %>$Item<% end_with %>
701
				<% with Foo(Arg1, Arg2) %>$Item<% end_with %>
702
				<% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>'
703
            )
704
        );
705
706
        // Loop controls
707
        $this->assertEquals(
708
            'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
709
            $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
710
        );
711
712
        $this->assertEquals(
713
            '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
714
            $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
715
        );
716
717
        $this->assertEquals(
718
            '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
719
            $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
720
        );
721
722
        $this->assertEquals(
723
            '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
724
            $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
725
        );
726
727
        $this->assertEquals(
728
            '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
729
            $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
730
        );
731
    }
732
733
    public function testIfBlocks()
734
    {
735
        // Basic test
736
        $this->assertEquals(
737
            'AC',
738
            $this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
739
        );
740
741
        // Nested test
742
        $this->assertEquals(
743
            'AB1C',
744
            $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
745
        );
746
747
        // else_if
748
        $this->assertEquals(
749
            'ACD',
750
            $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
751
        );
752
        $this->assertEquals(
753
            'AD',
754
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
755
        );
756
        $this->assertEquals(
757
            'ADE',
758
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
759
        );
760
761
        $this->assertEquals(
762
            'ADE',
763
            $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
764
        );
765
766
        // Dot syntax
767
        $this->assertEquals(
768
            'ACD',
769
            $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
770
        );
771
        $this->assertEquals(
772
            'ACD',
773
            $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
774
        );
775
776
        // Params
777
        $this->assertEquals(
778
            'ACD',
779
            $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
780
        );
781
        $this->assertEquals(
782
            'ABD',
783
            $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
784
        );
785
786
        // Negation
787
        $this->assertEquals(
788
            'AC',
789
            $this->render('A<% if not IsSet %>B<% end_if %>C')
790
        );
791
        $this->assertEquals(
792
            'ABC',
793
            $this->render('A<% if not NotSet %>B<% end_if %>C')
794
        );
795
796
        // Or
797
        $this->assertEquals(
798
            'ABD',
799
            $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
800
        );
801
        $this->assertEquals(
802
            'ACD',
803
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
804
        );
805
        $this->assertEquals(
806
            'AD',
807
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
808
        );
809
        $this->assertEquals(
810
            'ACD',
811
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
812
        );
813
        $this->assertEquals(
814
            'AD',
815
            $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
816
        );
817
818
        // Negated Or
819
        $this->assertEquals(
820
            'ACD',
821
            $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
822
        );
823
        $this->assertEquals(
824
            'ABD',
825
            $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
826
        );
827
        $this->assertEquals(
828
            'ABD',
829
            $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
830
        );
831
832
        // And
833
        $this->assertEquals(
834
            'ABD',
835
            $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
836
        );
837
        $this->assertEquals(
838
            'ACD',
839
            $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
840
        );
841
        $this->assertEquals(
842
            'AD',
843
            $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
844
        );
845
        $this->assertEquals(
846
            'ACD',
847
            $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
848
        );
849
        $this->assertEquals(
850
            'AD',
851
            $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
852
        );
853
854
        // Equality
855
        $this->assertEquals(
856
            'ABC',
857
            $this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
858
        );
859
        $this->assertEquals(
860
            'ACD',
861
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
862
        );
863
        $this->assertEquals(
864
            'ABC',
865
            $this->render('A<% if Right != Wrong %>B<% end_if %>C')
866
        );
867
        $this->assertEquals(
868
            'AD',
869
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
870
        );
871
872
        // test inequalities with simple numbers
873
        $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D'));
874
        $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D'));
875
        $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D'));
876
        $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D'));
877
878
        $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D'));
879
        $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D'));
880
        $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D'));
881
        $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D'));
882
883
        $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D'));
884
        $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D'));
885
        $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D'));
886
        $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D'));
887
888
        // empty else_if and else tags, if this would not be supported,
889
        // the output would stop after A, thereby failing the assert
890
        $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D'));
891
        $this->assertEquals(
892
            'AD',
893
            $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
894
        );
895
        $this->assertEquals(
896
            'AD',
897
            $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
898
        );
899
900
        // Bare words with ending space
901
        $this->assertEquals(
902
            'ABC',
903
            $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
904
        );
905
906
        // Else
907
        $this->assertEquals(
908
            'ADE',
909
            $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
910
        );
911
912
        // 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...
913
        $this->assertEquals(
914
            'ABC',
915
            $this->render('A<% if NotSet %><% else %>B<% end_if %>C')
916
        );
917
    }
918
919
    public function testBaseTagGeneration()
920
    {
921
        // XHTML wil have a closed base tag
922
        $tmpl1 = '<?xml version="1.0" encoding="UTF-8"?>
923
			<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
924
            . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
925
			<html>
926
				<head><% base_tag %></head>
927
				<body><p>test</p><body>
928
			</html>';
929
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $this->render($tmpl1));
930
931
        // HTML4 and 5 will only have it for IE
932
        $tmpl2 = '<!DOCTYPE html>
933
			<html>
934
				<head><% base_tag %></head>
935
				<body><p>test</p><body>
936
			</html>';
937
        $this->assertRegExp(
938
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
939
            $this->render($tmpl2)
940
        );
941
942
943
        $tmpl3 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
944
			<html>
945
				<head><% base_tag %></head>
946
				<body><p>test</p><body>
947
			</html>';
948
        $this->assertRegExp(
949
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
950
            $this->render($tmpl3)
951
        );
952
953
        // Check that the content negotiator converts to the equally legal formats
954
        $negotiator = new ContentNegotiator();
955
956
        $response = new HTTPResponse($this->render($tmpl1));
957
        $negotiator->html($response);
958
        $this->assertRegExp(
959
            '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
960
            $response->getBody()
961
        );
962
963
        $response = new HTTPResponse($this->render($tmpl1));
964
        $negotiator->xhtml($response);
965
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
966
    }
967
968
    public function testIncludeWithArguments()
969
    {
970
        $this->assertEquals(
971
            $this->render('<% include SSViewerTestIncludeWithArguments %>'),
972
            '<p>[out:Arg1]</p><p>[out:Arg2]</p>'
973
        );
974
975
        $this->assertEquals(
976
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
977
            '<p>A</p><p>[out:Arg2]</p>'
978
        );
979
980
        $this->assertEquals(
981
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
982
            '<p>A</p><p>B</p>'
983
        );
984
985
        $this->assertEquals(
986
            $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
987
            '<p>A Bare String</p><p>B Bare String</p>'
988
        );
989
990
        $this->assertEquals(
991
            $this->render(
992
                '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
993
                new ArrayData(array('B' => 'Bar'))
994
            ),
995
            '<p>A</p><p>Bar</p>'
996
        );
997
998
        $this->assertEquals(
999
            $this->render(
1000
                '<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
1001
                new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
1002
            ),
1003
            '<p>A</p><p>Bar</p>'
1004
        );
1005
1006
        $this->assertEquals(
1007
            $this->render(
1008
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
1009
                new ArrayData(
1010
                    array('Items' => new ArrayList(
1011
                        array(
1012
                        new ArrayData(array('Title' => 'Foo')),
1013
                        new ArrayData(array('Title' => 'Bar'))
1014
                        )
1015
                    ))
1016
                )
1017
            ),
1018
            'SomeArg - Foo - Bar - SomeArg'
1019
        );
1020
1021
        $this->assertEquals(
1022
            $this->render(
1023
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
1024
                new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))
1025
            ),
1026
            'A - B - A'
1027
        );
1028
1029
        $this->assertEquals(
1030
            $this->render(
1031
                '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
1032
                new ArrayData(
1033
                    array(
1034
                    'Item' => new ArrayData(
1035
                        array(
1036
                        'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1037
                        )
1038
                    ))
1039
                )
1040
            ),
1041
            'A - B - C - B - A'
1042
        );
1043
1044
        $this->assertEquals(
1045
            $this->render(
1046
                '<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
1047
                new ArrayData(
1048
                    array(
1049
                    'Item' => new ArrayData(
1050
                        array(
1051
                        'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1052
                        )
1053
                    ))
1054
                )
1055
            ),
1056
            'A - A - A'
1057
        );
1058
1059
        $data = new ArrayData(
1060
            array(
1061
            'Nested' => new ArrayData(
1062
                array(
1063
                'Object' => new ArrayData(array('Key' => 'A'))
1064
                )
1065
            ),
1066
            'Object' => new ArrayData(array('Key' => 'B'))
1067
            )
1068
        );
1069
1070
        $tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>');
1071
        $res  = $tmpl->process($data);
1072
        $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments');
1073
    }
1074
1075
    public function testNamespaceInclude()
1076
    {
1077
        $data = new ArrayData([]);
1078
1079
        $this->assertEquals(
1080
            "tests:( NamespaceInclude\n )",
1081
            $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data),
1082
            'Backslashes work for namespace references in includes'
1083
        );
1084
1085
        $this->assertEquals(
1086
            "tests:( NamespaceInclude\n )",
1087
            $this->render('tests:( <% include Namespace\\NamespaceInclude %> )', $data),
1088
            'Escaped backslashes work for namespace references in includes'
1089
        );
1090
1091
        $this->assertEquals(
1092
            "tests:( NamespaceInclude\n )",
1093
            $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data),
1094
            'Forward slashes work for namespace references in includes'
1095
        );
1096
    }
1097
1098
    /**
1099
     * Test search for includes fallback to non-includes folder
1100
     */
1101
    public function testIncludeFallbacks()
1102
    {
1103
        $data = new ArrayData([]);
1104
1105
        $this->assertEquals(
1106
            "tests:( Namespace/Includes/IncludedTwice.ss\n )",
1107
            $this->render('tests:( <% include Namespace\\IncludedTwice %> )', $data),
1108
            'Prefer Includes in the Includes folder'
1109
        );
1110
1111
        $this->assertEquals(
1112
            "tests:( Namespace/Includes/IncludedOnceSub.ss\n )",
1113
            $this->render('tests:( <% include Namespace\\IncludedOnceSub %> )', $data),
1114
            'Includes in only Includes folder can be found'
1115
        );
1116
1117
        $this->assertEquals(
1118
            "tests:( Namespace/IncludedOnceBase.ss\n )",
1119
            $this->render('tests:( <% include Namespace\\IncludedOnceBase %> )', $data),
1120
            'Includes outside of Includes folder can be found'
1121
        );
1122
    }
1123
1124
    public function testRecursiveInclude()
1125
    {
1126
        $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude'));
1127
1128
        $data = new ArrayData(
1129
            array(
1130
            'Title' => 'A',
1131
            'Children' => new ArrayList(
1132
                array(
1133
                new ArrayData(
1134
                    array(
1135
                    'Title' => 'A1',
1136
                    'Children' => new ArrayList(
1137
                        array(
1138
                        new ArrayData(array( 'Title' => 'A1 i', )),
1139
                        new ArrayData(array( 'Title' => 'A1 ii', )),
1140
                        )
1141
                    ),
1142
                    )
1143
                ),
1144
                new ArrayData(array( 'Title' => 'A2', )),
1145
                new ArrayData(array( 'Title' => 'A3', )),
1146
                )
1147
            ),
1148
            )
1149
        );
1150
1151
        $result = $view->process($data);
1152
        // We don't care about whitespace
1153
        $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result));
1154
1155
        $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
1156
    }
1157
1158
    public function assertEqualIgnoringWhitespace($a, $b, $message = '')
1159
    {
1160
        $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message);
1161
    }
1162
1163
    /**
1164
     * See {@link ViewableDataTest} for more extensive casting tests,
1165
     * this test just ensures that basic casting is correctly applied during template parsing.
1166
     */
1167
    public function testCastingHelpers()
1168
    {
1169
        $vd = new SSViewerTest\TestViewableData();
1170
        $vd->TextValue = '<b>html</b>';
1171
        $vd->HTMLValue = '<b>html</b>';
1172
        $vd->UncastedValue = '<b>html</b>';
1173
1174
        // Value casted as "Text"
1175
        $this->assertEquals(
1176
            '&lt;b&gt;html&lt;/b&gt;',
1177
            $t = SSViewer::fromString('$TextValue')->process($vd)
1178
        );
1179
        $this->assertEquals(
1180
            '<b>html</b>',
1181
            $t = SSViewer::fromString('$TextValue.RAW')->process($vd)
1182
        );
1183
        $this->assertEquals(
1184
            '&lt;b&gt;html&lt;/b&gt;',
1185
            $t = SSViewer::fromString('$TextValue.XML')->process($vd)
1186
        );
1187
1188
        // Value casted as "HTMLText"
1189
        $this->assertEquals(
1190
            '<b>html</b>',
1191
            $t = SSViewer::fromString('$HTMLValue')->process($vd)
1192
        );
1193
        $this->assertEquals(
1194
            '<b>html</b>',
1195
            $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
1196
        );
1197
        $this->assertEquals(
1198
            '&lt;b&gt;html&lt;/b&gt;',
1199
            $t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
1200
        );
1201
1202
        // Uncasted value (falls back to ViewableData::$default_cast="Text")
1203
        $vd = new SSViewerTest\TestViewableData();
1204
        $vd->UncastedValue = '<b>html</b>';
1205
        $this->assertEquals(
1206
            '&lt;b&gt;html&lt;/b&gt;',
1207
            $t = SSViewer::fromString('$UncastedValue')->process($vd)
1208
        );
1209
        $this->assertEquals(
1210
            '<b>html</b>',
1211
            $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
1212
        );
1213
        $this->assertEquals(
1214
            '&lt;b&gt;html&lt;/b&gt;',
1215
            $t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
1216
        );
1217
    }
1218
1219
    public function testSSViewerBasicIteratorSupport()
1220
    {
1221
        $data = new ArrayData(
1222
            array(
1223
            'Set' => new ArrayList(
1224
                array(
1225
                new SSViewerTest\TestObject("1"),
1226
                new SSViewerTest\TestObject("2"),
1227
                new SSViewerTest\TestObject("3"),
1228
                new SSViewerTest\TestObject("4"),
1229
                new SSViewerTest\TestObject("5"),
1230
                new SSViewerTest\TestObject("6"),
1231
                new SSViewerTest\TestObject("7"),
1232
                new SSViewerTest\TestObject("8"),
1233
                new SSViewerTest\TestObject("9"),
1234
                new SSViewerTest\TestObject("10"),
1235
                )
1236
            )
1237
            )
1238
        );
1239
1240
        //base test
1241
        $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data);
1242
        $this->assertEquals("12345678910", $result, "Numbers rendered in order");
1243
1244
        //test First
1245
        $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data);
1246
        $this->assertEquals("1", $result, "Only the first number is rendered");
1247
1248
        //test Last
1249
        $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data);
1250
        $this->assertEquals("10", $result, "Only the last number is rendered");
1251
1252
        //test Even
1253
        $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data);
1254
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1255
1256
        //test Even with quotes
1257
        $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data);
1258
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1259
1260
        //test Even without quotes
1261
        $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data);
1262
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1263
1264
        //test Even with zero-based start index
1265
        $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data);
1266
        $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order");
1267
1268
        //test Odd
1269
        $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data);
1270
        $this->assertEquals("13579", $result, "Odd numbers rendered in order");
1271
1272
        //test FirstLast
1273
        $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data);
1274
        $this->assertEquals("1first10last", $result, "First and last numbers rendered in order");
1275
1276
        //test Middle
1277
        $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data);
1278
        $this->assertEquals("23456789", $result, "Middle numbers rendered in order");
1279
1280
        //test MiddleString
1281
        $result = $this->render(
1282
            '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
1283
            . '<% end_loop %>',
1284
            $data
1285
        );
1286
        $this->assertEquals(
1287
            "2middle3middle4middle5middle6middle7middle8middle9middle",
1288
            $result,
1289
            "Middle numbers rendered in order"
1290
        );
1291
1292
        //test EvenOdd
1293
        $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data);
1294
        $this->assertEquals(
1295
            "oddevenoddevenoddevenoddevenoddeven",
1296
            $result,
1297
            "Even and Odd is returned in sequence numbers rendered in order"
1298
        );
1299
1300
        //test Pos
1301
        $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data);
1302
        $this->assertEquals("12345678910", $result, '$Pos is rendered in order');
1303
1304
        //test Pos
1305
        $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data);
1306
        $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order');
1307
1308
        //test FromEnd
1309
        $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data);
1310
        $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order');
1311
1312
        //test FromEnd
1313
        $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data);
1314
        $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order');
1315
1316
        //test Total
1317
        $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data);
1318
        $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned");
1319
1320
        //test Modulus
1321
        $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data);
1322
        $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order");
1323
1324
        //test MultipleOf 3
1325
        $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data);
1326
        $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned");
1327
1328
        //test MultipleOf 4
1329
        $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data);
1330
        $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned");
1331
1332
        //test MultipleOf 5
1333
        $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data);
1334
        $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned");
1335
1336
        //test MultipleOf 10
1337
        $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data);
1338
        $this->assertEquals(
1339
            "10",
1340
            $result,
1341
            "Only numbers that are multiples of 10 (with 1-based indexing) are returned"
1342
        );
1343
1344
        //test MultipleOf 9 zero-based
1345
        $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data);
1346
        $this->assertEquals(
1347
            "110",
1348
            $result,
1349
            "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
1350
        );
1351
1352
        //test MultipleOf 11
1353
        $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data);
1354
        $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned");
1355
    }
1356
1357
    /**
1358
     * Test $Up works when the scope $Up refers to was entered with a "with" block
1359
     */
1360
    public function testUpInWith()
1361
    {
1362
1363
        // Data to run the loop tests on - three levels deep
1364
        $data = new ArrayData([
1365
            'Name' => 'Top',
1366
            'Foo' => new ArrayData([
1367
                'Name' => 'Foo',
1368
                'Bar' => new ArrayData([
1369
                    'Name' => 'Bar',
1370
                    'Baz' => new ArrayData([
1371
                        'Name' => 'Baz'
1372
                    ]),
1373
                    'Qux' => new ArrayData([
1374
                        'Name' => 'Qux'
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
            'BarTop',
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
            'BazFooBar',
1395
            $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Foo.Name}{$Up.Foo.Bar.Name}<% end_with %>', $data)
1396
        );
1397
1398
        // Using $Up in a with block
1399
        $this->assertEquals(
1400
            'BazTopBar',
1401
            $this->render(
1402
                '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Foo.Bar.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
            'BazTopBarTopBaz',
1411
            $this->render(
1412
                '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Foo.Bar %>{$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 %><% with Bar %><% with Baz %>{$Up.Up.Name}<% end_with %><% end_with %>'
1423
                . '<% end_with %>',
1424
                $data
1425
            )
1426
        );
1427
1428
        // Using $Up as part of a lookup chain in <% with %>
1429
        $this->assertEquals(
1430
            'Top',
1431
            $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Name}<% end_with %>', $data)
1432
        );
1433
    }
1434
1435
    /**
1436
     * @expectedException \LogicException
1437
     * @expectedExceptionMessage Up called when we're already at the top of the scope
1438
     */
1439
    public function testTooManyUps()
1440
    {
1441
        $data = new ArrayData([
1442
            'Foo' => new ArrayData([
1443
                'Name' => 'Foo',
1444
                'Bar' => new ArrayData([
1445
                    'Name' => 'Bar'
1446
                ])
1447
            ])
1448
        ]);
1449
1450
        $this->assertEquals(
1451
            'Foo',
1452
            $this->render('<% with Foo.Bar %>{$Up.Up.Name}<% end_with %>', $data)
1453
        );
1454
    }
1455
1456
    /**
1457
     * Test $Up works when the scope $Up refers to was entered with a "loop" block
1458
     */
1459
    public function testUpInLoop()
1460
    {
1461
1462
        // Data to run the loop tests on - one sequence of three items, each with a subitem
1463
        $data = new ArrayData(
1464
            array(
1465
            'Name' => 'Top',
1466
            'Foo' => new ArrayList(
1467
                array(
1468
                new ArrayData(
1469
                    array(
1470
                    'Name' => '1',
1471
                    'Sub' => new ArrayData(
1472
                        array(
1473
                        'Name' => 'Bar'
1474
                        )
1475
                    )
1476
                    )
1477
                ),
1478
                new ArrayData(
1479
                    array(
1480
                    'Name' => '2',
1481
                    'Sub' => new ArrayData(
1482
                        array(
1483
                        'Name' => 'Baz'
1484
                        )
1485
                    )
1486
                    )
1487
                ),
1488
                new ArrayData(
1489
                    array(
1490
                    'Name' => '3',
1491
                    'Sub' => new ArrayData(
1492
                        array(
1493
                        'Name' => 'Qux'
1494
                        )
1495
                    )
1496
                    )
1497
                )
1498
                )
1499
            )
1500
            )
1501
        );
1502
1503
        // Make sure inside a loop, $Up refers to the current item of the loop
1504
        $this->assertEqualIgnoringWhitespace(
1505
            '111 222 333',
1506
            $this->render(
1507
                '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
1508
                $data
1509
            )
1510
        );
1511
1512
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1513
        // and doesn't interfere with the original iterator
1514
        $this->assertEqualIgnoringWhitespace(
1515
            '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
1516
            $this->render(
1517
                '<% loop $Foo %>
1518
					$Name
1519
					<% with $Sub %>
1520
						$Name
1521
						<% loop $Up %>$Name<% end_loop %>
1522
						$Name
1523
					<% end_with %>
1524
					$Name
1525
				<% end_loop %>',
1526
                $data
1527
            )
1528
        );
1529
1530
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1531
        // and doesn't interfere with the original iterator or local lookups
1532
        $this->assertEqualIgnoringWhitespace(
1533
            '1 Bar1 123 1Bar 1   2 Baz2 123 2Baz 2   3 Qux3 123 3Qux 3',
1534
            $this->render(
1535
                '<% loop $Foo %>
1536
					$Name
1537
					<% with $Sub %>
1538
						{$Name}{$Up.Name}
1539
						<% loop $Up %>$Name<% end_loop %>
1540
						{$Up.Name}{$Name}
1541
					<% end_with %>
1542
					$Name
1543
				<% end_loop %>',
1544
                $data
1545
            )
1546
        );
1547
    }
1548
1549
    /**
1550
     * Test that nested loops restore the loop variables correctly when pushing and popping states
1551
     */
1552
    public function testNestedLoops()
1553
    {
1554
1555
        // Data to run the loop tests on - one sequence of three items, one with child elements
1556
        // (of a different size to the main sequence)
1557
        $data = new ArrayData(
1558
            array(
1559
            'Foo' => new ArrayList(
1560
                array(
1561
                new ArrayData(
1562
                    array(
1563
                    'Name' => '1',
1564
                    'Children' => new ArrayList(
1565
                        array(
1566
                        new ArrayData(
1567
                            array(
1568
                            'Name' => 'a'
1569
                            )
1570
                        ),
1571
                        new ArrayData(
1572
                            array(
1573
                            'Name' => 'b'
1574
                            )
1575
                        ),
1576
                        )
1577
                    ),
1578
                    )
1579
                ),
1580
                new ArrayData(
1581
                    array(
1582
                    'Name' => '2',
1583
                    'Children' => new ArrayList(),
1584
                    )
1585
                ),
1586
                new ArrayData(
1587
                    array(
1588
                    'Name' => '3',
1589
                    'Children' => new ArrayList(),
1590
                    )
1591
                ),
1592
                )
1593
            ),
1594
            )
1595
        );
1596
1597
        // Make sure that including a loop inside a loop will not destroy the internal count of
1598
        // items, checked by using "Last"
1599
        $this->assertEqualIgnoringWhitespace(
1600
            '1ab23last',
1601
            $this->render(
1602
                '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
1603
                . '<% end_loop %>',
1604
                $data
1605
            )
1606
        );
1607
    }
1608
1609
    public function testLayout()
1610
    {
1611
        $this->useTestTheme(
1612
            __DIR__ . '/SSViewerTest',
1613
            'layouttest',
1614
            function () {
1615
                $template = new SSViewer(array('Page'));
1616
                $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
1617
1618
                $template = new SSViewer(array('Shortcodes', 'Page'));
1619
                $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
1620
            }
1621
        );
1622
    }
1623
1624
    /**
1625
     * @covers \SilverStripe\View\SSViewer::get_templates_by_class()
1626
     */
1627
    public function testGetTemplatesByClass()
1628
    {
1629
        $this->useTestTheme(
1630
            __DIR__ . '/SSViewerTest',
1631
            'layouttest',
1632
            function () {
1633
            // Test passing a string
1634
                $templates = SSViewer::get_templates_by_class(
1635
                    SSViewerTestModelController::class,
1636
                    '',
1637
                    Controller::class
1638
                );
1639
                $this->assertEquals(
1640
                    [
1641
                    SSViewerTestModelController::class,
1642
                    [
1643
                        'type' => 'Includes',
1644
                        SSViewerTestModelController::class,
1645
                    ],
1646
                    SSViewerTestModel::class,
1647
                    Controller::class,
1648
                    [
1649
                        'type' => 'Includes',
1650
                        Controller::class,
1651
                    ],
1652
                    ],
1653
                    $templates
1654
                );
1655
1656
            // Test to ensure we're stopping at the base class.
1657
                $templates = SSViewer::get_templates_by_class(
1658
                    SSViewerTestModelController::class,
1659
                    '',
1660
                    SSViewerTestModelController::class
1661
                );
1662
                $this->assertEquals(
1663
                    [
1664
                    SSViewerTestModelController::class,
1665
                    [
1666
                        'type' => 'Includes',
1667
                        SSViewerTestModelController::class,
1668
                    ],
1669
                    SSViewerTestModel::class,
1670
                    ],
1671
                    $templates
1672
                );
1673
1674
            // Make sure we can search templates by suffix.
1675
                $templates = SSViewer::get_templates_by_class(
1676
                    SSViewerTestModel::class,
1677
                    'Controller',
1678
                    DataObject::class
1679
                );
1680
                $this->assertEquals(
1681
                    [
1682
                    SSViewerTestModelController::class,
1683
                    [
1684
                        'type' => 'Includes',
1685
                        SSViewerTestModelController::class,
1686
                    ],
1687
                    DataObject::class . 'Controller',
1688
                    [
1689
                        'type' => 'Includes',
1690
                        DataObject::class . 'Controller',
1691
                    ],
1692
                    ],
1693
                    $templates
1694
                );
1695
1696
                // Let's throw something random in there.
1697
                $this->expectException(InvalidArgumentException::class);
1698
                SSViewer::get_templates_by_class(null);
1699
            }
1700
        );
1701
    }
1702
1703
    public function testRewriteHashlinks()
1704
    {
1705
        SSViewer::setRewriteHashLinksDefault(true);
1706
1707
        $_SERVER['HTTP_HOST'] = 'www.mysite.com';
1708
        $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
1709
1710
        // 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...
1711
        // Note that leading double slashes have been rewritten to prevent these being mis-interepreted
1712
        // as protocol-less absolute urls
1713
        $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
1714
1715
        $tmplFile = TEMP_PATH . DIRECTORY_SEPARATOR . 'SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss';
1716
1717
        // Note: SSViewer_FromString doesn't rewrite hash links.
1718
        file_put_contents(
1719
            $tmplFile,
1720
            '<!DOCTYPE html>
1721
			<html>
1722
				<head><% base_tag %></head>
1723
				<body>
1724
				<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
1725
				$ExternalInsertedLink
1726
				<a class="inline" href="#anchor">InlineLink</a>
1727
				$InsertedLink
1728
				<svg><use xlink:href="#sprite"></use></svg>
1729
				<body>
1730
			</html>'
1731
        );
1732
        $tmpl = new SSViewer($tmplFile);
1733
        $obj = new ViewableData();
1734
        $obj->InsertedLink = DBField::create_field(
1735
            'HTMLFragment',
1736
            '<a class="inserted" href="#anchor">InsertedLink</a>'
1737
        );
1738
        $obj->ExternalInsertedLink = DBField::create_field(
1739
            'HTMLFragment',
1740
            '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
1741
        );
1742
        $result = $tmpl->process($obj);
1743
        $this->assertContains(
1744
            '<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
1745
            $result
1746
        );
1747
        $this->assertContains(
1748
            '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
1749
            $result
1750
        );
1751
        $this->assertContains(
1752
            '<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
1753
            $result
1754
        );
1755
        $this->assertContains(
1756
            '<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
1757
            $result
1758
        );
1759
        $this->assertContains(
1760
            '<svg><use xlink:href="#sprite"></use></svg>',
1761
            $result,
1762
            'SSTemplateParser should only rewrite anchor hrefs'
1763
        );
1764
1765
        unlink($tmplFile);
1766
    }
1767
1768
    public function testRewriteHashlinksInPhpMode()
1769
    {
1770
        SSViewer::setRewriteHashLinksDefault('php');
1771
1772
        $tmplFile = TEMP_PATH . DIRECTORY_SEPARATOR . 'SSViewerTest_testRewriteHashlinksInPhpMode_'
1773
            . sha1(rand()) . '.ss';
1774
1775
        // Note: SSViewer_FromString doesn't rewrite hash links.
1776
        file_put_contents(
1777
            $tmplFile,
1778
            '<!DOCTYPE html>
1779
			<html>
1780
				<head><% base_tag %></head>
1781
				<body>
1782
				<a class="inline" href="#anchor">InlineLink</a>
1783
				$InsertedLink
1784
				<svg><use xlink:href="#sprite"></use></svg>
1785
				<body>
1786
			</html>'
1787
        );
1788
        $tmpl = new SSViewer($tmplFile);
1789
        $obj = new ViewableData();
1790
        $obj->InsertedLink = DBField::create_field(
1791
            'HTMLFragment',
1792
            '<a class="inserted" href="#anchor">InsertedLink</a>'
1793
        );
1794
        $result = $tmpl->process($obj);
1795
1796
        $code = '<a class="inserted" href="<?php echo \SilverStripe\Core\Convert::raw2att(preg_replace("/^(\/)+/", "/",'
1797
            . ' $_SERVER[\'REQUEST_URI\'])); ?>#anchor">InsertedLink</a>';
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_VARIABLE, expecting ',' or ')' on line 1797 at column 16
Loading history...
1798
        $this->assertContains($code, $result);
1799
        // TODO Fix inline links in PHP mode
1800
        // $this->assertContains(
1801
        //  '<a class="inline" href="<?php echo str_replace(',
1802
        //  $result
1803
        // );
1804
        $this->assertContains(
1805
            '<svg><use xlink:href="#sprite"></use></svg>',
1806
            $result,
1807
            'SSTemplateParser should only rewrite anchor hrefs'
1808
        );
1809
1810
        unlink($tmplFile);
1811
    }
1812
1813
    public function testRenderWithSourceFileComments()
1814
    {
1815
        SSViewer::config()->update('source_file_comments', true);
1816
        $i = __DIR__ . '/SSViewerTest/templates/Includes';
1817
        $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
1818
        $templates = array(
1819
        array(
1820
            'name' => 'SSViewerTestCommentsFullSource',
1821
            'expected' => ""
1822
                . "<!doctype html>"
1823
                . "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
1824
                . "<html>"
1825
                . "\t<head></head>"
1826
                . "\t<body></body>"
1827
                . "</html>"
1828
                . "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
1829
        ),
1830
        array(
1831
            'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
1832
            'expected' => ""
1833
                . "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
1834
                . "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
1835
                . "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
1836
                . "<html>"
1837
                . "\t<head></head>"
1838
                . "\t<body></body>"
1839
                . "</html>"
1840
                . "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
1841
        ),
1842
        array(
1843
            'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
1844
            'expected' => ""
1845
                . "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
1846
                . "\t<head></head>"
1847
                . "\t<body></body>"
1848
                . "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
1849
        ),
1850
        array(
1851
            'name' => 'SSViewerTestCommentsFullSourceIfIE',
1852
            'expected' => ""
1853
                . "<!doctype html>"
1854
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
1855
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1856
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1857
                . "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
1858
                . "\t<head></head>"
1859
                . "\t<body></body>"
1860
                . "</html>"
1861
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
1862
        ),
1863
        array(
1864
            'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
1865
            'expected' => ""
1866
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1867
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1868
                . "<!--[if !IE]><!--> <html class='no-ie'>"
1869
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
1870
                . " <!--<![endif]-->"
1871
                . "\t<head></head>"
1872
                . "\t<body></body>"
1873
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
1874
        ),
1875
        array(
1876
            'name' => 'SSViewerTestCommentsPartialSource',
1877
            'expected' =>
1878
            "<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
1879
                . "<div class='typography'></div>"
1880
                . "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
1881
        ),
1882
        array(
1883
            'name' => 'SSViewerTestCommentsWithInclude',
1884
            'expected' =>
1885
            "<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
1886
                . "<div class='typography'>"
1887
                . "<!-- include 'SSViewerTestCommentsInclude' -->"
1888
                . "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
1889
                . "Included"
1890
                . "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
1891
                . "<!-- end include 'SSViewerTestCommentsInclude' -->"
1892
                . "</div>"
1893
                . "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
1894
        ),
1895
        );
1896
        foreach ($templates as $template) {
1897
            $this->_renderWithSourceFileComments('SSViewerTestComments/' . $template['name'], $template['expected']);
1898
        }
1899
    }
1900
    private function _renderWithSourceFileComments($name, $expected)
1901
    {
1902
        $viewer = new SSViewer(array($name));
1903
        $data = new ArrayData(array());
1904
        $result = $viewer->process($data);
1905
        $expected = str_replace(array("\r", "\n"), '', $expected);
1906
        $result = str_replace(array("\r", "\n"), '', $result);
1907
        $this->assertEquals($result, $expected);
1908
    }
1909
1910
    public function testLoopIteratorIterator()
1911
    {
1912
        $list = new PaginatedList(new ArrayList());
1913
        $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName<br /><% end_loop %>');
1914
        $result = $viewer->process(new ArrayData(array('List' => $list)));
1915
        $this->assertEquals($result, '');
1916
    }
1917
1918
    public function testProcessOnlyIncludesRequirementsOnce()
1919
    {
1920
        $template = new SSViewer(array('SSViewerTestProcess'));
1921
        $basePath = $this->getCurrentRelativePath() . '/SSViewerTest';
1922
1923
        $backend = Injector::inst()->create(Requirements_Backend::class);
1924
        $backend->setCombinedFilesEnabled(false);
1925
        $backend->combineFiles(
1926
            'RequirementsTest_ab.css',
1927
            array(
1928
            $basePath . '/css/RequirementsTest_a.css',
1929
            $basePath . '/css/RequirementsTest_b.css'
1930
            )
1931
        );
1932
1933
        Requirements::set_backend($backend);
1934
1935
        $this->assertEquals(1, substr_count($template->process(new ViewableData()), "a.css"));
1936
        $this->assertEquals(1, substr_count($template->process(new ViewableData()), "b.css"));
1937
1938
        // if we disable the requirements then we should get nothing
1939
        $template->includeRequirements(false);
1940
        $this->assertEquals(0, substr_count($template->process(new ViewableData()), "a.css"));
1941
        $this->assertEquals(0, substr_count($template->process(new ViewableData()), "b.css"));
1942
    }
1943
1944
    public function testRequireCallInTemplateInclude()
1945
    {
1946
        //TODO undo skip test on the event that templates ever obtain the ability to reference
1947
        //MODULE_DIR (or something to that effect)
1948
        if (FRAMEWORK_DIR === 'framework') {
1949
            $template = new SSViewer(array('SSViewerTestProcess'));
1950
1951
            Requirements::set_suffix_requirements(false);
1952
1953
            $this->assertEquals(
1954
                1,
1955
                substr_count(
1956
                    $template->process(new ViewableData()),
1957
                    "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
1958
                )
1959
            );
1960
        } else {
1961
            $this->markTestSkipped(
1962
                'Requirement will always fail if the framework dir is not ' .
1963
                'named \'framework\', since templates require hard coded paths'
1964
            );
1965
        }
1966
    }
1967
1968
    public function testCallsWithArguments()
1969
    {
1970
        $data = new ArrayData(
1971
            array(
1972
            'Set' => new ArrayList(
1973
                array(
1974
                new SSViewerTest\TestObject("1"),
1975
                new SSViewerTest\TestObject("2"),
1976
                new SSViewerTest\TestObject("3"),
1977
                new SSViewerTest\TestObject("4"),
1978
                new SSViewerTest\TestObject("5"),
1979
                )
1980
            ),
1981
            'Level' => new SSViewerTest\LevelTestData(1),
1982
            'Nest' => array(
1983
            'Level' => new SSViewerTest\LevelTestData(2),
1984
            ),
1985
            )
1986
        );
1987
1988
        $tests = array(
1989
        '$Level.output(1)' => '1-1',
1990
        '$Nest.Level.output($Set.First.Number)' => '2-1',
1991
        '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
1992
        '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
1993
        '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
1994
        '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
1995
        '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
1996
        '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
1997
        '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
1998
        '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
1999
        '<% with $Nest %>
2000
				<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
2001
			<% end_with %>' => '1-hi',
2002
        '<% with $Nest %>
2003
				<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
2004
			<% end_with %>' => '!0!1!2!3!4',
2005
        );
2006
2007
        foreach ($tests as $template => $expected) {
2008
            $this->assertEquals($expected, trim($this->render($template, $data)));
2009
        }
2010
    }
2011
2012
    public function testRepeatedCallsAreCached()
2013
    {
2014
        $data = new SSViewerTest\CacheTestData();
2015
        $template = '
2016
			<% if $TestWithCall %>
2017
				<% with $TestWithCall %>
2018
					{$Message}
2019
				<% end_with %>
2020
2021
				{$TestWithCall.Message}
2022
			<% end_if %>';
2023
2024
        $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data)));
2025
        $this->assertEquals(
2026
            1,
2027
            $data->testWithCalls,
2028
            'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
2029
        );
2030
2031
        $data = new SSViewerTest\CacheTestData();
2032
        $template = '
2033
			<% if $TestLoopCall %>
2034
				<% loop $TestLoopCall %>
2035
					{$Message}
2036
				<% end_loop %>
2037
			<% end_if %>';
2038
2039
        $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data)));
2040
        $this->assertEquals(
2041
            1,
2042
            $data->testLoopCalls,
2043
            'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
2044
        );
2045
    }
2046
2047
    public function testClosedBlockExtension()
2048
    {
2049
        $count = 0;
2050
        $parser = new SSTemplateParser();
2051
        $parser->addClosedBlock(
2052
            'test',
2053
            function ($res) use (&$count) {
2054
                $count++;
2055
            }
2056
        );
2057
2058
        $template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
2059
        $template->process(new SSViewerTest\TestFixture());
2060
2061
        $this->assertEquals(1, $count);
2062
    }
2063
2064
    public function testOpenBlockExtension()
2065
    {
2066
        $count = 0;
2067
        $parser = new SSTemplateParser();
2068
        $parser->addOpenBlock(
2069
            'test',
2070
            function ($res) use (&$count) {
2071
                $count++;
2072
            }
2073
        );
2074
2075
        $template = new SSViewer_FromString("<% test %>", $parser);
2076
        $template->process(new SSViewerTest\TestFixture());
2077
2078
        $this->assertEquals(1, $count);
2079
    }
2080
2081
    /**
2082
     * Tests if caching for SSViewer_FromString is working
2083
     */
2084
    public function testFromStringCaching()
2085
    {
2086
        $content = 'Test content';
2087
        $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache.' . sha1($content);
2088
        if (file_exists($cacheFile)) {
2089
            unlink($cacheFile);
2090
        }
2091
2092
        // Test global behaviors
2093
        $this->render($content, null, null);
2094
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2095
2096
        SSViewer_FromString::config()->update('cache_template', true);
2097
        $this->render($content, null, null);
2098
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2099
        unlink($cacheFile);
2100
2101
        // Test instance behaviors
2102
        $this->render($content, null, false);
2103
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2104
2105
        $this->render($content, null, true);
2106
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2107
        unlink($cacheFile);
2108
    }
2109
}
2110