From 7fa47e234f9082d770e4618a4e78ea2c5f74c80b Mon Sep 17 00:00:00 2001 From: Aaron Carlino Date: Wed, 10 May 2017 16:24:30 +1200 Subject: [PATCH] New API for minified files using injectable service --- .../01_Templates/03_Requirements.md | 51 +- src/View/Requirements_Backend.php | 61 +- tests/php/View/RequirementsTest.php | 2 +- tests/php/View/SSViewerTest.php | 3173 +++++++++-------- 4 files changed, 1698 insertions(+), 1589 deletions(-) diff --git a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md index 251362c87..8cbeb9767 100644 --- a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md +++ b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md @@ -169,13 +169,13 @@ replaced. For instance, the below will set a new set of dependencies to write to --- Name: myrequirements --- - Requirements: + SilverStripe\View\Requirements: disable_flush_combined: true - Requirements_Backend: + SilverStripe\View\Requirements_Backend: combine_in_dev: true combine_hash_querystring: true default_combined_files_folder: 'combined' - Injector: + SilverStripe\Core\Injector\Injector: MySiteAdapter: class: 'SilverStripe\Filesystem\Flysystem\AssetAdapter' constructor: @@ -192,7 +192,7 @@ replaced. For instance, the below will set a new set of dependencies to write to class: SilverStripe\Filesystem\Storage\FlysystemGeneratedAssetHandler properties: Filesystem: '%$MySiteBackend' - Requirements_Backend: + SilverStripe\View\Requirements_Backend: properties: AssetHandler: '%$MySiteAssetHandler' @@ -248,6 +248,49 @@ $scripts = array( Requirements::combine_files('scripts.js', $scripts, array('async' => true, 'defer' => true)); ``` +### Minification of CSS and JS files + +You can minify combined Javascript and CSS files at runtime using an implementation of the +`SilverStripe\View\Requirements_Minifier` interface. + +```php +namespace MyProject; + +use SilverStripe\View\Requirements_Minifier; + +class MyMinifier implements Requirements_Minifier +{ + /** + * Minify the given content + * + * @param string $content + * @param string $type Either js or css + * @param string $filename Name of file to display in case of error + * @return string minified content + */ + public function minify ($content, $type, $fileName) + { + // Minify $content; + + return $minifiedContent; + } +} +``` + +Then, inject this service in `Requirements_Backend`. + +```yaml +SilverStripe\Core\Injector\Injector: + SilverStripe\View\Requirements_Backend: + properties: + Minifier: %$MyProject\MyMinifier +``` + +
+While the framework does afford you the option of minification at runtime, we recommend using one of many frontend build +tools to do this for you, e.g. [Webpack](https://webpack.github.io/), [Gulp](http://gulpjs.com/), or [Grunt](https://gruntjs.com/). +
+ ## Clearing assets diff --git a/src/View/Requirements_Backend.php b/src/View/Requirements_Backend.php index 78f642ca7..f41ef33c3 100644 --- a/src/View/Requirements_Backend.php +++ b/src/View/Requirements_Backend.php @@ -3,6 +3,7 @@ namespace SilverStripe\View; use InvalidArgumentException; +use Exception; use SilverStripe\Assets\File; use SilverStripe\Assets\Storage\GeneratedAssetHandler; use SilverStripe\Control\Director; @@ -10,7 +11,6 @@ use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; use SilverStripe\Core\Injector\Injectable; -use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\Debug; use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\SapphireTest; @@ -121,11 +121,11 @@ class Requirements_Backend protected $combinedFiles = array(); /** - * Use the JSMin library to minify any javascript file passed to {@link combine_files()}. + * Use the injected minification service to minify any javascript file passed to {@link combine_files()}. * * @var bool */ - protected $minifyCombinedJSFiles = true; + protected $minifyCombinedFiles = false; /** * Whether or not file headers should be written when combining files @@ -191,6 +191,11 @@ class Requirements_Backend */ protected $assetHandler = null; + /** + * @var Requirements_Minifier + */ + protected $minifier = null; + /** * Gets the backend storage for generated files * @@ -211,6 +216,26 @@ class Requirements_Backend $this->assetHandler = $handler; } + /** + * Gets the minification service for this backend + * + * @return Requirements_Minifier + */ + public function getMinifier() + { + return $this->minifier; + } + + /** + * Set a new minification service for this backend + * + * @param Requirements_Minifier $minifier + */ + public function setMinifier(Requirements_Minifier $minifier = null) + { + $this->minifier = $minifier; + } + /** * Enable or disable the combination of CSS and JavaScript files * @@ -340,24 +365,24 @@ class Requirements_Backend } /** - * Check if minify js files should be combined + * Check if minify files should be combined * * @return bool */ - public function getMinifyCombinedJSFiles() + public function getMinifyCombinedFiles() { - return $this->minifyCombinedJSFiles; + return $this->minifyCombinedFiles; } /** - * Set if combined js files should be minified + * Set if combined files should be minified * * @param bool $minify * @return $this */ - public function setMinifyCombinedJSFiles($minify) + public function setMinifyCombinedFiles($minify) { - $this->minifyCombinedJSFiles = $minify; + $this->minifyCombinedFiles = $minify; return $this; } @@ -1278,7 +1303,20 @@ class Requirements_Backend $combinedFileID = File::join_paths($this->getCombinedFilesFolder(), $combinedFile); // Send file combination request to the backend, with an optional callback to perform regeneration - $minify = $this->getMinifyCombinedJSFiles(); + $minify = $this->getMinifyCombinedFiles(); + if ($minify && !$this->minifier) { + throw new Exception( + sprintf( + 'Cannot minify files without a minification service defined. + Set %s::minifyCombinedFiles to false, or inject a %s service on + %s.properties.minifier', + __CLASS__, + Requirements_Minifier::class, + __CLASS__ + ) + ); + } + $combinedURL = $this ->getAssetHandler() ->getContentURL( @@ -1287,12 +1325,11 @@ class Requirements_Backend // Physically combine all file content $combinedData = ''; $base = Director::baseFolder() . '/'; - $minifier = Injector::inst()->get('SilverStripe\\View\\Requirements_Minifier'); foreach ($fileList as $file) { $fileContent = file_get_contents($base . $file); // Use configured minifier if ($minify) { - $fileContent = $minifier->minify($fileContent, $type, $file); + $fileContent = $this->minifier->minify($fileContent, $type, $file); } if ($this->writeHeaderComment) { diff --git a/tests/php/View/RequirementsTest.php b/tests/php/View/RequirementsTest.php index 6208fa3e4..85da1a3de 100644 --- a/tests/php/View/RequirementsTest.php +++ b/tests/php/View/RequirementsTest.php @@ -86,7 +86,7 @@ class RequirementsTest extends SapphireTest $backend->clear(); $backend->clearCombinedFiles(); $backend->setCombinedFilesFolder('_combinedfiles'); - $backend->setMinifyCombinedJSFiles(false); + $backend->setMinifyCombinedFiles(false); Requirements::flush(); } diff --git a/tests/php/View/SSViewerTest.php b/tests/php/View/SSViewerTest.php index ab83cd0d9..a3403719e 100644 --- a/tests/php/View/SSViewerTest.php +++ b/tests/php/View/SSViewerTest.php @@ -20,6 +20,7 @@ use SilverStripe\Security\SecurityToken; use SilverStripe\Security\Permission; use SilverStripe\View\ArrayData; use SilverStripe\View\Requirements_Backend; +use SilverStripe\View\Requirements_Minifier; use SilverStripe\View\SSViewer; use SilverStripe\View\Requirements; use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel; @@ -28,10 +29,9 @@ use SilverStripe\View\ViewableData; use SilverStripe\View\SSViewer_FromString; use SilverStripe\View\SSTemplateParser; use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore; -use JSMin; use Exception; -class SSViewerTest extends SapphireTest +classSSViewerTest extends SapphireTest { /** @@ -39,148 +39,148 @@ class SSViewerTest extends SapphireTest * * @var array */ - protected $oldServer = array(); +protected $oldServer = array(); - protected static $extra_dataobjects = array( +protected static $extra_dataobjects = array( SSViewerTest\TestObject::class, ); - protected function setUp() - { - parent::setUp(); - SSViewer::config()->update('source_file_comments', false); - SSViewer_FromString::config()->update('cache_template', false); - TestAssetStore::activate('SSViewerTest'); - $this->oldServer = $_SERVER; - } +protected function setUp() +{ + parent::setUp(); + SSViewer::config()->update('source_file_comments', false); + SSViewer_FromString::config()->update('cache_template', false); + TestAssetStore::activate('SSViewerTest'); + $this->oldServer = $_SERVER; +} - protected function tearDown() - { - $_SERVER = $this->oldServer; - TestAssetStore::reset(); - parent::tearDown(); - } +protected function tearDown() +{ + $_SERVER = $this->oldServer; + TestAssetStore::reset(); + parent::tearDown(); +} /** * Tests for {@link Config::inst()->get('SSViewer', 'theme')} for different behaviour * of user defined themes via {@link SiteConfig} and default theme * when no user themes are defined. */ - public function testCurrentTheme() - { - SSViewer::config()->update('theme', 'mytheme'); - $this->assertEquals( - 'mytheme', - SSViewer::config()->uninherited('theme'), - 'Current theme is the default - user has not defined one' - ); - } +public function testCurrentTheme() +{ + SSViewer::config()->update('theme', 'mytheme'); + $this->assertEquals( + 'mytheme', + SSViewer::config()->uninherited('theme'), + 'Current theme is the default - user has not defined one' + ); +} /** * Test that a template without a tag still renders. */ - public function testTemplateWithoutHeadRenders() - { - $data = new ArrayData( +public function testTemplateWithoutHeadRenders() +{ + $data = new ArrayData( + array( + 'Var' => 'var value' + ) + ); + + $result = $data->renderWith("SSViewerTestPartialTemplate"); + $this->assertEquals('Test partial template: var value', trim(preg_replace("//U", '', $result))); +} + +public function testIncludeScopeInheritance() +{ + $data = $this->getScopeInheritanceTestData(); + $expected = array( + 'Item 1 - First-ODD top:Item 1', + 'Item 2 - EVEN top:Item 2', + 'Item 3 - ODD top:Item 3', + 'Item 4 - EVEN top:Item 4', + 'Item 5 - ODD top:Item 5', + 'Item 6 - Last-EVEN top:Item 6', + ); + + $result = $data->renderWith('SSViewerTestIncludeScopeInheritance'); + $this->assertExpectedStrings($result, $expected); + + // reset results for the tests that include arguments (the title is passed as an arg) + $expected = array( + 'Item 1 _ Item 1 - First-ODD top:Item 1', + 'Item 2 _ Item 2 - EVEN top:Item 2', + 'Item 3 _ Item 3 - ODD top:Item 3', + 'Item 4 _ Item 4 - EVEN top:Item 4', + 'Item 5 _ Item 5 - ODD top:Item 5', + 'Item 6 _ Item 6 - Last-EVEN top:Item 6', + ); + + $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs'); + $this->assertExpectedStrings($result, $expected); +} + +public function testIncludeTruthyness() +{ + $data = new ArrayData( + array( + 'Title' => 'TruthyTest', + 'Items' => new ArrayList( array( - 'Var' => 'var value' + new ArrayData(array('Title' => 'Item 1')), + new ArrayData(array('Title' => '')), + new ArrayData(array('Title' => true)), + new ArrayData(array('Title' => false)), + new ArrayData(array('Title' => null)), + new ArrayData(array('Title' => 0)), + new ArrayData(array('Title' => 7)) ) - ); + ) + ) + ); + $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs'); - $result = $data->renderWith("SSViewerTestPartialTemplate"); - $this->assertEquals('Test partial template: var value', trim(preg_replace("//U", '', $result))); - } + // We should not end up with empty values appearing as empty + $expected = array( + 'Item 1 _ Item 1 - First-ODD top:Item 1', + 'Untitled - EVEN top:', + '1 _ 1 - ODD top:1', + 'Untitled - EVEN top:', + 'Untitled - ODD top:', + 'Untitled - EVEN top:0', + '7 _ 7 - Last-ODD top:7' + ); + $this->assertExpectedStrings($result, $expected); +} - public function testIncludeScopeInheritance() - { - $data = $this->getScopeInheritanceTestData(); - $expected = array( - 'Item 1 - First-ODD top:Item 1', - 'Item 2 - EVEN top:Item 2', - 'Item 3 - ODD top:Item 3', - 'Item 4 - EVEN top:Item 4', - 'Item 5 - ODD top:Item 5', - 'Item 6 - Last-EVEN top:Item 6', - ); - - $result = $data->renderWith('SSViewerTestIncludeScopeInheritance'); - $this->assertExpectedStrings($result, $expected); - - // reset results for the tests that include arguments (the title is passed as an arg) - $expected = array( - 'Item 1 _ Item 1 - First-ODD top:Item 1', - 'Item 2 _ Item 2 - EVEN top:Item 2', - 'Item 3 _ Item 3 - ODD top:Item 3', - 'Item 4 _ Item 4 - EVEN top:Item 4', - 'Item 5 _ Item 5 - ODD top:Item 5', - 'Item 6 _ Item 6 - Last-EVEN top:Item 6', - ); - - $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs'); - $this->assertExpectedStrings($result, $expected); - } - - public function testIncludeTruthyness() - { - $data = new ArrayData( +private function getScopeInheritanceTestData() +{ + return new ArrayData( + array( + 'Title' => 'TopTitleValue', + 'Items' => new ArrayList( array( - 'Title' => 'TruthyTest', - 'Items' => new ArrayList( - array( - new ArrayData(array('Title' => 'Item 1')), - new ArrayData(array('Title' => '')), - new ArrayData(array('Title' => true)), - new ArrayData(array('Title' => false)), - new ArrayData(array('Title' => null)), - new ArrayData(array('Title' => 0)), - new ArrayData(array('Title' => 7)) - ) + new ArrayData(array('Title' => 'Item 1')), + new ArrayData(array('Title' => 'Item 2')), + new ArrayData(array('Title' => 'Item 3')), + new ArrayData(array('Title' => 'Item 4')), + new ArrayData(array('Title' => 'Item 5')), + new ArrayData(array('Title' => 'Item 6')) ) - ) - ); - $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs'); + ) + ) + ); +} - // We should not end up with empty values appearing as empty - $expected = array( - 'Item 1 _ Item 1 - First-ODD top:Item 1', - 'Untitled - EVEN top:', - '1 _ 1 - ODD top:1', - 'Untitled - EVEN top:', - 'Untitled - ODD top:', - 'Untitled - EVEN top:0', - '7 _ 7 - Last-ODD top:7' - ); - $this->assertExpectedStrings($result, $expected); - } - - private function getScopeInheritanceTestData() - { - return new ArrayData( - array( - 'Title' => 'TopTitleValue', - 'Items' => new ArrayList( - array( - new ArrayData(array('Title' => 'Item 1')), - new ArrayData(array('Title' => 'Item 2')), - new ArrayData(array('Title' => 'Item 3')), - new ArrayData(array('Title' => 'Item 4')), - new ArrayData(array('Title' => 'Item 5')), - new ArrayData(array('Title' => 'Item 6')) - ) - ) - ) +private function assertExpectedStrings($result, $expected) +{ + foreach ($expected as $expectedStr) { + $this->assertTrue( + (boolean) preg_match("/{$expectedStr}/", $result), + "Didn't find '{$expectedStr}' in:\n{$result}" ); } - - private function assertExpectedStrings($result, $expected) - { - foreach ($expected as $expectedStr) { - $this->assertTrue( - (boolean) preg_match("/{$expectedStr}/", $result), - "Didn't find '{$expectedStr}' in:\n{$result}" - ); - } - } +} /** * Small helper to render templates from strings @@ -190,75 +190,104 @@ class SSViewerTest extends SapphireTest * @param bool $cacheTemplate * @return string */ - public function render($templateString, $data = null, $cacheTemplate = false) - { - $t = SSViewer::fromString($templateString, $cacheTemplate); - if (!$data) { - $data = new SSViewerTest\TestFixture(); - } - return trim(''.$t->process($data)); +public function render($templateString, $data = null, $cacheTemplate = false) +{ + $t = SSViewer::fromString($templateString, $cacheTemplate); + if (!$data) { + $data = new SSViewerTest\TestFixture(); } + return trim(''.$t->process($data)); +} - public function testRequirements() - { - $requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css")) - ->getMock(); - $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js'; - $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js'; +public function testRequirements() +{ + $requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css")) + ->getMock(); + $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js'; + $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js'; - $requirements->expects($this->once())->method('javascript')->with($jsFile); - $requirements->expects($this->once())->method('css')->with($cssFile); + $requirements->expects($this->once())->method('javascript')->with($jsFile); + $requirements->expects($this->once())->method('css')->with($cssFile); - $origReq = Requirements::backend(); - Requirements::set_backend($requirements); - $template = $this->render( - "<% require javascript($jsFile) %> + $origReq = Requirements::backend(); + Requirements::set_backend($requirements); + $template = $this->render( + "<% require javascript($jsFile) %> <% require css($cssFile) %>" + ); + Requirements::set_backend($origReq); + + $this->assertFalse((bool)trim($template), "Should be no content in this return."); +} + +public function testRequirementsCombine() +{ + $testBackend = Injector::inst()->create(Requirements_Backend::class); + $testBackend->setSuffixRequirements(false); + //$combinedTestFilePath = BASE_PATH . '/' . $testBackend->getCombinedFilesFolder() . '/testRequirementsCombine.js'; + + $jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js'; + $jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile); + $testBackend->combineFiles('testRequirementsCombine.js', array($jsFile)); + + // secondly, make sure that requirements is generated, even though minification failed + $testBackend->processCombinedFiles(); + $js = array_keys($testBackend->getJavascript()); + $combinedTestFilePath = BASE_PATH . reset($js); + $this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath); + + // and make sure the combined content matches the input content, i.e. no loss of functionality + if (!file_exists($combinedTestFilePath)) { + $this->fail('No combined file was created at expected path: '.$combinedTestFilePath); + } + $combinedTestFileContents = file_get_contents($combinedTestFilePath); + $this->assertContains($jsFileContents, $combinedTestFileContents); +} + +public function testRequirementsMinification() +{ + $testBackend = Injector::inst()->create(Requirements_Backend::class); + $testBackend->setSuffixRequirements(false); + $testBackend->setMinifyCombinedFiles(true); + $testFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/RequirementsTest_a.js'; + $testFileContent = file_get_contents($testFile); + + $mockMinifier = $this->getMockBuilder(Requirements_Minifier::class) + ->setMethods(['minify']) + ->getMock(); + + $mockMinifier->expects($this->once()) + ->method('minify') + ->with( + $testFileContent, + 'js', + $testFile ); - Requirements::set_backend($origReq); + $testBackend->setMinifier($mockMinifier); + $testBackend->combineFiles('testRequirementsMinified.js', array($testFile)); + $testBackend->processCombinedFiles(); - $this->assertFalse((bool)trim($template), "Should be no content in this return."); - } + $testBackend->setMinifyCombinedFiles(false); + $mockMinifier->expects($this->never()) + ->method('minify'); + $testBackend->processCombinedFiles(); - public function testRequirementsCombine() - { - $testBackend = Injector::inst()->create(Requirements_Backend::class); - $testBackend->setSuffixRequirements(false); - //$combinedTestFilePath = BASE_PATH . '/' . $testBackend->getCombinedFilesFolder() . '/testRequirementsCombine.js'; + $this->setExpectedExceptionRegExp( + Exception::class, + '/minification service/' + ); - $jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js'; - $jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile); - $testBackend->combineFiles('testRequirementsCombine.js', array($jsFile)); - - // first make sure that our test js file causes an exception to be thrown - try { - include_once 'jsmin/jsmin.php'; - JSMin::minify($jsFileContents); - $this->fail('JSMin did not throw exception on minify bad file: '); - } catch (Exception $e) { - // exception thrown... good - } - - // secondly, make sure that requirements is generated, even though minification failed - $testBackend->processCombinedFiles(); - $js = array_keys($testBackend->getJavascript()); - $combinedTestFilePath = BASE_PATH . reset($js); - $this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath); - - // and make sure the combined content matches the input content, i.e. no loss of functionality - if (!file_exists($combinedTestFilePath)) { - $this->fail('No combined file was created at expected path: '.$combinedTestFilePath); - } - $combinedTestFileContents = file_get_contents($combinedTestFilePath); - $this->assertContains($jsFileContents, $combinedTestFileContents); - } + $testBackend->setMinifyCombinedFiles(true); + $testBackend->setMinifier(null); + $testBackend->processCombinedFiles(); +} - public function testComments() - { - $output = $this->render( - <<render( + <<This is some content<%-- this is another comment --%>Final content <%-- Alone multi line comment --%> @@ -267,8 +296,8 @@ Mixing content and <%-- multi line comment --%> Final final content SS - ); - $shouldbe = <<assertEquals($shouldbe, $output); - } + $this->assertEquals($shouldbe, $output); +} - public function testBasicText() - { - $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone'); - $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone'); - $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped'); - $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped'); - } +public function testBasicText() +{ + $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone'); + $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone'); + $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped'); + $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped'); +} - public function testBasicInjection() - { - $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection'); - $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection'); - $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection'); - $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection'); +public function testBasicInjection() +{ + $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection'); + $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection'); + $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection'); + $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection'); - $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped'); - $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character'); - $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character'); + $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped'); + $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character'); + $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character'); - $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection'); - $this->assertEquals( - '{\\[out:Test]}', - $this->render('{\\\\$Test}'), - 'Escapes before injections are correctly unescaped' - ); - } + $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection'); + $this->assertEquals( + '{\\[out:Test]}', + $this->render('{\\\\$Test}'), + 'Escapes before injections are correctly unescaped' + ); +} - public function testGlobalVariableCalls() - { - $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic')); - $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString')); - $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray')); - } +public function testGlobalVariableCalls() +{ + $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic')); + $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString')); + $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray')); +} - public function testGlobalVariableCallsWithArguments() - { - $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments')); - $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")')); - $this->assertEquals( - 'zFoo:Bar:Bazz', - $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")') - ); - $this->assertEquals( - 'zreferencez', - $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)') - ); - } +public function testGlobalVariableCallsWithArguments() +{ + $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments')); + $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")')); + $this->assertEquals( + 'zFoo:Bar:Bazz', + $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")') + ); + $this->assertEquals( + 'zreferencez', + $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)') + ); +} - public function testGlobalVariablesAreEscaped() - { - $this->assertEquals('
', $this->render('$SSViewerTest_GlobalHTMLFragment')); - $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLEscaped')); +public function testGlobalVariablesAreEscaped() +{ + $this->assertEquals('
', $this->render('$SSViewerTest_GlobalHTMLFragment')); + $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLEscaped')); - $this->assertEquals( - 'z
z', - $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)') - ); - $this->assertEquals( - 'z<div></div>z', - $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)') - ); - } + $this->assertEquals( + 'z
z', + $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)') + ); + $this->assertEquals( + 'z<div></div>z', + $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)') + ); +} - public function testCoreGlobalVariableCalls() - { - $this->assertEquals( - Director::absoluteBaseURL(), - $this->render('{$absoluteBaseURL}'), - 'Director::absoluteBaseURL can be called from within template' - ); - $this->assertEquals( - Director::absoluteBaseURL(), - $this->render('{$AbsoluteBaseURL}'), - 'Upper-case %AbsoluteBaseURL can be called from within template' - ); +public function testCoreGlobalVariableCalls() +{ + $this->assertEquals( + Director::absoluteBaseURL(), + $this->render('{$absoluteBaseURL}'), + 'Director::absoluteBaseURL can be called from within template' + ); + $this->assertEquals( + Director::absoluteBaseURL(), + $this->render('{$AbsoluteBaseURL}'), + 'Upper-case %AbsoluteBaseURL can be called from within template' + ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$isAjax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$IsAjax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$is_ajax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$Is_ajax}'), - 'All variations of is_ajax result in the correct call' - ); + $this->assertEquals( + Director::is_ajax(), + $this->render('{$isAjax}'), + 'All variations of is_ajax result in the correct call' + ); + $this->assertEquals( + Director::is_ajax(), + $this->render('{$IsAjax}'), + 'All variations of is_ajax result in the correct call' + ); + $this->assertEquals( + Director::is_ajax(), + $this->render('{$is_ajax}'), + 'All variations of is_ajax result in the correct call' + ); + $this->assertEquals( + Director::is_ajax(), + $this->render('{$Is_ajax}'), + 'All variations of is_ajax result in the correct call' + ); - $this->assertEquals( - i18n::get_locale(), - $this->render('{$i18nLocale}'), - 'i18n template functions result correct result' - ); - $this->assertEquals( - i18n::get_locale(), - $this->render('{$get_locale}'), - 'i18n template functions result correct result' - ); + $this->assertEquals( + i18n::get_locale(), + $this->render('{$i18nLocale}'), + 'i18n template functions result correct result' + ); + $this->assertEquals( + i18n::get_locale(), + $this->render('{$get_locale}'), + 'i18n template functions result correct result' + ); - $this->assertEquals( - (string)Member::currentUser(), - $this->render('{$CurrentMember}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - (string)Member::currentUser(), - $this->render('{$CurrentUser}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - (string)Member::currentUser(), - $this->render('{$currentMember}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - (string)Member::currentUser(), - $this->render('{$currentUser}'), - 'Member template functions result correct result' - ); + $this->assertEquals( + (string)Member::currentUser(), + $this->render('{$CurrentMember}'), + 'Member template functions result correct result' + ); + $this->assertEquals( + (string)Member::currentUser(), + $this->render('{$CurrentUser}'), + 'Member template functions result correct result' + ); + $this->assertEquals( + (string)Member::currentUser(), + $this->render('{$currentMember}'), + 'Member template functions result correct result' + ); + $this->assertEquals( + (string)Member::currentUser(), + $this->render('{$currentUser}'), + 'Member template functions result correct result' + ); - $this->assertEquals( - SecurityToken::getSecurityID(), - $this->render('{$getSecurityID}'), - 'SecurityToken template functions result correct result' - ); - $this->assertEquals( - SecurityToken::getSecurityID(), - $this->render('{$SecurityID}'), - 'SecurityToken template functions result correct result' - ); + $this->assertEquals( + SecurityToken::getSecurityID(), + $this->render('{$getSecurityID}'), + 'SecurityToken template functions result correct result' + ); + $this->assertEquals( + SecurityToken::getSecurityID(), + $this->render('{$SecurityID}'), + 'SecurityToken template functions result correct result' + ); - $this->assertEquals( - Permission::check("ADMIN"), - (bool)$this->render('{$HasPerm(\'ADMIN\')}'), - 'Permissions template functions result correct result' - ); - $this->assertEquals( - Permission::check("ADMIN"), - (bool)$this->render('{$hasPerm(\'ADMIN\')}'), - 'Permissions template functions result correct result' - ); - } + $this->assertEquals( + Permission::check("ADMIN"), + (bool)$this->render('{$HasPerm(\'ADMIN\')}'), + 'Permissions template functions result correct result' + ); + $this->assertEquals( + Permission::check("ADMIN"), + (bool)$this->render('{$hasPerm(\'ADMIN\')}'), + 'Permissions template functions result correct result' + ); +} - public function testNonFieldCastingHelpersNotUsedInHasValue() - { - // check if Link without $ in front of variable - $result = $this->render( - 'A<% if Link %>$Link<% end_if %>B', - new SSViewerTest\TestObject() - ); - $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>'); +public function testNonFieldCastingHelpersNotUsedInHasValue() +{ + // check if Link without $ in front of variable + $result = $this->render( + 'A<% if Link %>$Link<% end_if %>B', + new SSViewerTest\TestObject() + ); + $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>'); - // check if Link with $ in front of variable - $result = $this->render( - 'A<% if $Link %>$Link<% end_if %>B', - new SSViewerTest\TestObject() - ); - $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>'); - } + // check if Link with $ in front of variable + $result = $this->render( + 'A<% if $Link %>$Link<% end_if %>B', + new SSViewerTest\TestObject() + ); + $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>'); +} - public function testLocalFunctionsTakePriorityOverGlobals() - { - $data = new ArrayData( +public function testLocalFunctionsTakePriorityOverGlobals() +{ + $data = new ArrayData( + array( + 'Page' => new SSViewerTest\TestObject() + ) + ); + + //call method with lots of arguments + $result = $this->render( + '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>', + $data + ); + $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments"); + + //call method that does not exist + $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data); + $this->assertEquals("", $result, "Method does not exist - empty result"); + + //call if that does not exist + $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data); + $this->assertEquals("", $result, "Method does not exist - empty result"); + + //call method with same name as a global method (local call should take priority) + $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data); + $this->assertEquals( + "testLocalFunctionPriorityCalled", + $result, + "Local Object's public function called. Did not return the actual baseURL of the current site" + ); +} + +public function testCurrentScopeLoopWith() +{ + // Data to run the loop tests on - one sequence of three items, each with a subitem + $data = new ArrayData( + array( + 'Foo' => new ArrayList( array( - 'Page' => new SSViewerTest\TestObject() - ) - ); - - //call method with lots of arguments - $result = $this->render( - '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>', - $data - ); - $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments"); - - //call method that does not exist - $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data); - $this->assertEquals("", $result, "Method does not exist - empty result"); - - //call if that does not exist - $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data); - $this->assertEquals("", $result, "Method does not exist - empty result"); - - //call method with same name as a global method (local call should take priority) - $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data); - $this->assertEquals( - "testLocalFunctionPriorityCalled", - $result, - "Local Object's public function called. Did not return the actual baseURL of the current site" - ); - } - - public function testCurrentScopeLoopWith() - { - // Data to run the loop tests on - one sequence of three items, each with a subitem - $data = new ArrayData( - array( - 'Foo' => new ArrayList( + 'Subocean' => new ArrayData( array( - 'Subocean' => new ArrayData( - array( - 'Name' => 'Higher' - ) - ), - new ArrayData( - array( - 'Sub' => new ArrayData( - array( - 'Name' => 'SubKid1' - ) - ) - ) - ), - new ArrayData( - array( - 'Sub' => new ArrayData( - array( - 'Name' => 'SubKid2' - ) - ) - ) - ), - new SSViewerTest\TestObject('Number6') + 'Name' => 'Higher' ) + ), + new ArrayData( + array( + 'Sub' => new ArrayData( + array( + 'Name' => 'SubKid1' + ) + ) + ) + ), + new ArrayData( + array( + 'Sub' => new ArrayData( + array( + 'Name' => 'SubKid2' + ) + ) + ) + ), + new SSViewerTest\TestObject('Number6') ) - ) - ); + ) + ) + ); - $result = $this->render( - '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); + $result = $this->render( + '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', + $data + ); + $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); - $result = $this->render( - '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); + $result = $this->render( + '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', + $data + ); + $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); - $result = $this->render('<% with Foo %>$Count<% end_with %>', $data); - $this->assertEquals("4", $result, "4 items in the DataObjectSet"); + $result = $this->render('<% with Foo %>$Count<% end_with %>', $data); + $this->assertEquals("4", $result, "4 items in the DataObjectSet"); - $result = $this->render( - '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' - . '<% end_if %><% end_loop %><% end_with %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works"); + $result = $this->render( + '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' + . '<% end_if %><% end_loop %><% end_with %>', + $data + ); + $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works"); - $result = $this->render( - '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' - . '<% end_if %><% end_loop %><% end_with %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works"); - } + $result = $this->render( + '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' + . '<% end_if %><% end_loop %><% end_with %>', + $data + ); + $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works"); +} - public function testObjectDotArguments() - { - $this->assertEquals( - '[out:TestObject.methodWithOneArgument(one)] +public function testObjectDotArguments() +{ + $this->assertEquals( + '[out:TestObject.methodWithOneArgument(one)] [out:TestObject.methodWithTwoArguments(one,two)] [out:TestMethod(Arg1,Arg2).Bar.Val] [out:TestMethod(Arg1,Arg2).Bar] @@ -558,8 +587,8 @@ SS; [out:TestMethod(Arg1).Bar.Val] [out:TestMethod(Arg1).Bar] [out:TestMethod(Arg1)]', - $this->render( - '$TestObject.methodWithOneArgument(one) + $this->render( + '$TestObject.methodWithOneArgument(one) $TestObject.methodWithTwoArguments(one,two) $TestMethod(Arg1, Arg2).Bar.Val $TestMethod(Arg1, Arg2).Bar @@ -567,14 +596,14 @@ SS; $TestMethod(Arg1).Bar.Val $TestMethod(Arg1).Bar $TestMethod(Arg1)' - ) - ); - } + ) + ); +} - public function testEscapedArguments() - { - $this->assertEquals( - '[out:Foo(Arg1,Arg2).Bar.Val].Suffix +public function testEscapedArguments() +{ + $this->assertEquals( + '[out:Foo(Arg1,Arg2).Bar.Val].Suffix [out:Foo(Arg1,Arg2).Val]_Suffix [out:Foo(Arg1,Arg2)]/Suffix [out:Foo(Arg1).Bar.Val]textSuffix @@ -583,8 +612,8 @@ SS; [out:Foo.Bar.Val].Suffix [out:Foo.Bar].Suffix [out:Foo].Suffix', - $this->render( - '{$Foo(Arg1, Arg2).Bar.Val}.Suffix + $this->render( + '{$Foo(Arg1, Arg2).Bar.Val}.Suffix {$Foo(Arg1, Arg2).Val}_Suffix {$Foo(Arg1, Arg2)}/Suffix {$Foo(Arg1).Bar.Val}textSuffix @@ -593,44 +622,44 @@ SS; {$Foo.Bar.Val}.Suffix {$Foo.Bar}.Suffix {$Foo}.Suffix' - ) - ); - } + ) + ); +} - public function testLoopWhitespace() - { - $this->assertEquals( - 'before[out:SingleItem.Test]after +public function testLoopWhitespace() +{ + $this->assertEquals( + 'before[out:SingleItem.Test]after beforeTestafter', - $this->render( - 'before<% loop SingleItem %>$Test<% end_loop %>after + $this->render( + 'before<% loop SingleItem %>$Test<% end_loop %>after before<% loop SingleItem %>Test<% end_loop %>after' - ) - ); + ) + ); - // The control tags are removed from the output, but no whitespace - // This is a quirk that could be changed, but included in the test to make the current - // behaviour explicit - $this->assertEquals( - 'before + // The control tags are removed from the output, but no whitespace + // This is a quirk that could be changed, but included in the test to make the current + // behaviour explicit + $this->assertEquals( + 'before [out:SingleItem.ItemOnItsOwnLine] after', - $this->render( - 'before + $this->render( + 'before <% loop SingleItem %> $ItemOnItsOwnLine <% end_loop %> after' - ) - ); + ) + ); - // The whitespace within the control tags is preserve in a loop - // This is a quirk that could be changed, but included in the test to make the current - // behaviour explicit - $this->assertEquals( - 'before + // The whitespace within the control tags is preserve in a loop + // This is a quirk that could be changed, but included in the test to make the current + // behaviour explicit + $this->assertEquals( + 'before [out:Loop3.ItemOnItsOwnLine] @@ -639,800 +668,800 @@ after' [out:Loop3.ItemOnItsOwnLine] after', - $this->render( - 'before + $this->render( + 'before <% loop Loop3 %> $ItemOnItsOwnLine <% end_loop %> after' - ) - ); - } + ) + ); +} - public function testControls() - { - // Single item controls - $this->assertEquals( - 'a[out:Foo.Bar.Item]b +public function testControls() +{ + // Single item controls + $this->assertEquals( + 'a[out:Foo.Bar.Item]b [out:Foo.Bar(Arg1).Item] [out:Foo(Arg1).Item] [out:Foo(Arg1,Arg2).Item] [out:Foo(Arg1,Arg2,Arg3).Item]', - $this->render( - '<% with Foo.Bar %>a{$Item}b<% end_with %> + $this->render( + '<% with Foo.Bar %>a{$Item}b<% end_with %> <% with Foo.Bar(Arg1) %>$Item<% end_with %> <% with Foo(Arg1) %>$Item<% end_with %> <% with Foo(Arg1, Arg2) %>$Item<% end_with %> <% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>' - ) - ); + ) + ); - // Loop controls - $this->assertEquals( - 'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b', - $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>') - ); + // Loop controls + $this->assertEquals( + 'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b', + $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>') + ); - $this->assertEquals( - '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]', - $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>') - ); + $this->assertEquals( + '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]', + $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>') + ); - $this->assertEquals( - '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]', - $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>') - ); + $this->assertEquals( + '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]', + $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>') + ); - $this->assertEquals( - '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]', - $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>') - ); + $this->assertEquals( + '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]', + $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>') + ); - $this->assertEquals( - '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]', - $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>') - ); - } + $this->assertEquals( + '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]', + $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>') + ); +} - public function testIfBlocks() - { - // Basic test - $this->assertEquals( - 'AC', - $this->render('A<% if NotSet %>B$NotSet<% end_if %>C') - ); +public function testIfBlocks() +{ + // Basic test + $this->assertEquals( + 'AC', + $this->render('A<% if NotSet %>B$NotSet<% end_if %>C') + ); - // Nested test - $this->assertEquals( - 'AB1C', - $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C') - ); + // Nested test + $this->assertEquals( + 'AB1C', + $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C') + ); - // else_if - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D') - ); - $this->assertEquals( - 'ADE', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') - ); + // else_if + $this->assertEquals( + 'ACD', + $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D') + ); + $this->assertEquals( + 'ADE', + $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') + ); - $this->assertEquals( - 'ADE', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') - ); + $this->assertEquals( + 'ADE', + $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') + ); - // Dot syntax - $this->assertEquals( - 'ACD', - $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D') - ); + // Dot syntax + $this->assertEquals( + 'ACD', + $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D') + ); - // Params - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D') - ); + // Params + $this->assertEquals( + 'ACD', + $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D') + ); + $this->assertEquals( + 'ABD', + $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D') + ); - // Negation - $this->assertEquals( - 'AC', - $this->render('A<% if not IsSet %>B<% end_if %>C') - ); - $this->assertEquals( - 'ABC', - $this->render('A<% if not NotSet %>B<% end_if %>C') - ); + // Negation + $this->assertEquals( + 'AC', + $this->render('A<% if not IsSet %>B<% end_if %>C') + ); + $this->assertEquals( + 'ABC', + $this->render('A<% if not NotSet %>B<% end_if %>C') + ); - // Or - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D') - ); + // Or + $this->assertEquals( + 'ABD', + $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D') + ); - // Negated Or - $this->assertEquals( - 'ACD', - $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); + // Negated Or + $this->assertEquals( + 'ACD', + $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') + ); + $this->assertEquals( + 'ABD', + $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') + ); + $this->assertEquals( + 'ABD', + $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D') + ); - // And - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D') - ); + // And + $this->assertEquals( + 'ABD', + $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D') + ); - // Equality - $this->assertEquals( - 'ABC', - $this->render('A<% if RawVal == RawVal %>B<% end_if %>C') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABC', - $this->render('A<% if Right != Wrong %>B<% end_if %>C') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D') - ); + // Equality + $this->assertEquals( + 'ABC', + $this->render('A<% if RawVal == RawVal %>B<% end_if %>C') + ); + $this->assertEquals( + 'ACD', + $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D') + ); + $this->assertEquals( + 'ABC', + $this->render('A<% if Right != Wrong %>B<% end_if %>C') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D') + ); - // test inequalities with simple numbers - $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D')); + // test inequalities with simple numbers + $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D')); + $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D')); - // empty else_if and else tags, if this would not be supported, - // the output would stop after A, thereby failing the assert - $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D')); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D') - ); + // empty else_if and else tags, if this would not be supported, + // the output would stop after A, thereby failing the assert + $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D')); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D') + ); + $this->assertEquals( + 'AD', + $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D') + ); - // Bare words with ending space - $this->assertEquals( - 'ABC', - $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C') - ); + // Bare words with ending space + $this->assertEquals( + 'ABC', + $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C') + ); - // Else - $this->assertEquals( - 'ADE', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E') - ); + // Else + $this->assertEquals( + 'ADE', + $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E') + ); - // Empty if with else - $this->assertEquals( - 'ABC', - $this->render('A<% if NotSet %><% else %>B<% end_if %>C') - ); - } + // Empty if with else + $this->assertEquals( + 'ABC', + $this->render('A<% if NotSet %><% else %>B<% end_if %>C') + ); +} - public function testBaseTagGeneration() - { - // XHTML wil have a closed base tag - $tmpl1 = ' +public function testBaseTagGeneration() +{ + // XHTML wil have a closed base tag + $tmpl1 = ' + . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <% base_tag %>

test

'; - $this->assertRegExp('/<\/head>/', $this->render($tmpl1)); + $this->assertRegExp('/<\/head>/', $this->render($tmpl1)); - // HTML4 and 5 will only have it for IE - $tmpl2 = ' + // HTML4 and 5 will only have it for IE + $tmpl2 = ' <% base_tag %>

test

'; - $this->assertRegExp( - '/<\/head>/', - $this->render($tmpl2) - ); + $this->assertRegExp( + '/<\/head>/', + $this->render($tmpl2) + ); - $tmpl3 = ' + $tmpl3 = ' <% base_tag %>

test

'; - $this->assertRegExp( - '/<\/head>/', - $this->render($tmpl3) - ); + $this->assertRegExp( + '/<\/head>/', + $this->render($tmpl3) + ); - // Check that the content negotiator converts to the equally legal formats - $negotiator = new ContentNegotiator(); + // Check that the content negotiator converts to the equally legal formats + $negotiator = new ContentNegotiator(); - $response = new HTTPResponse($this->render($tmpl1)); - $negotiator->html($response); - $this->assertRegExp( - '/<\/head>/', - $response->getBody() - ); + $response = new HTTPResponse($this->render($tmpl1)); + $negotiator->html($response); + $this->assertRegExp( + '/<\/head>/', + $response->getBody() + ); - $response = new HTTPResponse($this->render($tmpl1)); - $negotiator->xhtml($response); - $this->assertRegExp('/<\/head>/', $response->getBody()); - } + $response = new HTTPResponse($this->render($tmpl1)); + $negotiator->xhtml($response); + $this->assertRegExp('/<\/head>/', $response->getBody()); +} - public function testIncludeWithArguments() - { - $this->assertEquals( - $this->render('<% include SSViewerTestIncludeWithArguments %>'), - '

[out:Arg1]

[out:Arg2]

' - ); +public function testIncludeWithArguments() +{ + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeWithArguments %>'), + '

[out:Arg1]

[out:Arg2]

' + ); - $this->assertEquals( - $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'), - '

A

[out:Arg2]

' - ); + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'), + '

A

[out:Arg2]

' + ); - $this->assertEquals( - $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'), - '

A

B

' - ); + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'), + '

A

B

' + ); - $this->assertEquals( - $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'), - '

A Bare String

B Bare String

' - ); + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'), + '

A Bare String

B Bare String

' + ); - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>', - new ArrayData(array('B' => 'Bar')) - ), - '

A

Bar

' - ); + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>', + new ArrayData(array('B' => 'Bar')) + ), + '

A

Bar

' + ); - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeWithArguments Arg1="A" %>', - new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar')) - ), - '

A

Bar

' - ); + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeWithArguments Arg1="A" %>', + new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar')) + ), + '

A

Bar

' + ); - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>', - new ArrayData( - array('Items' => new ArrayList( - array( - new ArrayData(array('Title' => 'Foo')), - new ArrayData(array('Title' => 'Bar')) - ) - )) - ) - ), - 'SomeArg - Foo - Bar - SomeArg' - ); - - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>', - new ArrayData(array('Item' => new ArrayData(array('Title' =>'B')))) - ), - 'A - B - A' - ); - - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>', - new ArrayData( + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>', + new ArrayData( + array('Items' => new ArrayList( array( - 'Item' => new ArrayData( - array( - 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C')) - ) - )) - ) - ), - 'A - B - C - B - A' - ); - - $this->assertEquals( - $this->render( - '<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>', - new ArrayData( - array( - 'Item' => new ArrayData( - array( - 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C')) - ) - )) - ) - ), - 'A - A - A' - ); - - $data = new ArrayData( - array( - 'Nested' => new ArrayData( - array( - 'Object' => new ArrayData(array('Key' => 'A')) - ) - ), - 'Object' => new ArrayData(array('Key' => 'B')) + new ArrayData(array('Title' => 'Foo')), + new ArrayData(array('Title' => 'Bar')) + ) + )) ) - ); + ), + 'SomeArg - Foo - Bar - SomeArg' + ); - $tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>'); - $res = $tmpl->process($data); - $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments'); - } + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>', + new ArrayData(array('Item' => new ArrayData(array('Title' =>'B')))) + ), + 'A - B - A' + ); - public function testNamespaceInclude() - { - $data = new ArrayData([]); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data), - 'Backslashes work for namespace references in includes' - ); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data), - 'Forward slashes work for namespace references in includes' - ); - } - - - public function testRecursiveInclude() - { - $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude')); - - $data = new ArrayData( - array( - 'Title' => 'A', - 'Children' => new ArrayList( + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>', + new ArrayData( array( - new ArrayData( + 'Item' => new ArrayData( array( - 'Title' => 'A1', - 'Children' => new ArrayList( - array( - new ArrayData(array( 'Title' => 'A1 i', )), - new ArrayData(array( 'Title' => 'A1 ii', )), - ) - ), + 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C')) + ) + )) + ) + ), + 'A - B - C - B - A' + ); + + $this->assertEquals( + $this->render( + '<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>', + new ArrayData( + array( + 'Item' => new ArrayData( + array( + 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C')) + ) + )) + ) + ), + 'A - A - A' + ); + + $data = new ArrayData( + array( + 'Nested' => new ArrayData( + array( + 'Object' => new ArrayData(array('Key' => 'A')) + ) + ), + 'Object' => new ArrayData(array('Key' => 'B')) + ) + ); + + $tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>'); + $res = $tmpl->process($data); + $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments'); +} + +public function testNamespaceInclude() +{ + $data = new ArrayData([]); + + $this->assertEquals( + "tests:( NamespaceInclude\n )", + $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data), + 'Backslashes work for namespace references in includes' + ); + + $this->assertEquals( + "tests:( NamespaceInclude\n )", + $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data), + 'Forward slashes work for namespace references in includes' + ); +} + + +public function testRecursiveInclude() +{ + $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude')); + + $data = new ArrayData( + array( + 'Title' => 'A', + 'Children' => new ArrayList( + array( + new ArrayData( + array( + 'Title' => 'A1', + 'Children' => new ArrayList( + array( + new ArrayData(array( 'Title' => 'A1 i', )), + new ArrayData(array( 'Title' => 'A1 ii', )), ) ), - new ArrayData(array( 'Title' => 'A2', )), - new ArrayData(array( 'Title' => 'A3', )), ) ), + new ArrayData(array( 'Title' => 'A2', )), + new ArrayData(array( 'Title' => 'A3', )), ) - ); + ), + ) + ); - $result = $view->process($data); - // We don't care about whitespace - $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result)); + $result = $view->process($data); + // We don't care about whitespace + $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result)); - $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult); - } + $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult); +} - public function assertEqualIgnoringWhitespace($a, $b, $message = '') - { - $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message); - } +public function assertEqualIgnoringWhitespace($a, $b, $message = '') +{ + $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message); +} /** * See {@link ViewableDataTest} for more extensive casting tests, * this test just ensures that basic casting is correctly applied during template parsing. */ - public function testCastingHelpers() - { - $vd = new SSViewerTest\TestViewableData(); - $vd->TextValue = 'html'; - $vd->HTMLValue = 'html'; - $vd->UncastedValue = 'html'; +public function testCastingHelpers() +{ + $vd = new SSViewerTest\TestViewableData(); + $vd->TextValue = 'html'; + $vd->HTMLValue = 'html'; + $vd->UncastedValue = 'html'; - // Value casted as "Text" - $this->assertEquals( - '<b>html</b>', - $t = SSViewer::fromString('$TextValue')->process($vd) - ); - $this->assertEquals( - 'html', - $t = SSViewer::fromString('$TextValue.RAW')->process($vd) - ); - $this->assertEquals( - '<b>html</b>', - $t = SSViewer::fromString('$TextValue.XML')->process($vd) - ); + // Value casted as "Text" + $this->assertEquals( + '<b>html</b>', + $t = SSViewer::fromString('$TextValue')->process($vd) + ); + $this->assertEquals( + 'html', + $t = SSViewer::fromString('$TextValue.RAW')->process($vd) + ); + $this->assertEquals( + '<b>html</b>', + $t = SSViewer::fromString('$TextValue.XML')->process($vd) + ); - // Value casted as "HTMLText" - $this->assertEquals( - 'html', - $t = SSViewer::fromString('$HTMLValue')->process($vd) - ); - $this->assertEquals( - 'html', - $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd) - ); - $this->assertEquals( - '<b>html</b>', - $t = SSViewer::fromString('$HTMLValue.XML')->process($vd) - ); + // Value casted as "HTMLText" + $this->assertEquals( + 'html', + $t = SSViewer::fromString('$HTMLValue')->process($vd) + ); + $this->assertEquals( + 'html', + $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd) + ); + $this->assertEquals( + '<b>html</b>', + $t = SSViewer::fromString('$HTMLValue.XML')->process($vd) + ); - // Uncasted value (falls back to ViewableData::$default_cast="Text") - $vd = new SSViewerTest\TestViewableData(); - $vd->UncastedValue = 'html'; - $this->assertEquals( - '<b>html</b>', - $t = SSViewer::fromString('$UncastedValue')->process($vd) - ); - $this->assertEquals( - 'html', - $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd) - ); - $this->assertEquals( - '<b>html</b>', - $t = SSViewer::fromString('$UncastedValue.XML')->process($vd) - ); - } + // Uncasted value (falls back to ViewableData::$default_cast="Text") + $vd = new SSViewerTest\TestViewableData(); + $vd->UncastedValue = 'html'; + $this->assertEquals( + '<b>html</b>', + $t = SSViewer::fromString('$UncastedValue')->process($vd) + ); + $this->assertEquals( + 'html', + $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd) + ); + $this->assertEquals( + '<b>html</b>', + $t = SSViewer::fromString('$UncastedValue.XML')->process($vd) + ); +} - public function testSSViewerBasicIteratorSupport() - { - $data = new ArrayData( +public function testSSViewerBasicIteratorSupport() +{ + $data = new ArrayData( + array( + 'Set' => new ArrayList( array( - 'Set' => new ArrayList( - array( - new SSViewerTest\TestObject("1"), - new SSViewerTest\TestObject("2"), - new SSViewerTest\TestObject("3"), - new SSViewerTest\TestObject("4"), - new SSViewerTest\TestObject("5"), - new SSViewerTest\TestObject("6"), - new SSViewerTest\TestObject("7"), - new SSViewerTest\TestObject("8"), - new SSViewerTest\TestObject("9"), - new SSViewerTest\TestObject("10"), - ) + new SSViewerTest\TestObject("1"), + new SSViewerTest\TestObject("2"), + new SSViewerTest\TestObject("3"), + new SSViewerTest\TestObject("4"), + new SSViewerTest\TestObject("5"), + new SSViewerTest\TestObject("6"), + new SSViewerTest\TestObject("7"), + new SSViewerTest\TestObject("8"), + new SSViewerTest\TestObject("9"), + new SSViewerTest\TestObject("10"), ) - ) - ); + ) + ) + ); - //base test - $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data); - $this->assertEquals("12345678910", $result, "Numbers rendered in order"); + //base test + $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data); + $this->assertEquals("12345678910", $result, "Numbers rendered in order"); - //test First - $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("1", $result, "Only the first number is rendered"); + //test First + $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("1", $result, "Only the first number is rendered"); - //test Last - $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("10", $result, "Only the last number is rendered"); + //test Last + $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("10", $result, "Only the last number is rendered"); - //test Even - $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); + //test Even + $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("246810", $result, "Even numbers rendered in order"); - //test Even with quotes - $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); + //test Even with quotes + $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("246810", $result, "Even numbers rendered in order"); - //test Even without quotes - $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); + //test Even without quotes + $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("246810", $result, "Even numbers rendered in order"); - //test Even with zero-based start index - $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order"); + //test Even with zero-based start index + $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order"); - //test Odd - $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("13579", $result, "Odd numbers rendered in order"); + //test Odd + $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("13579", $result, "Odd numbers rendered in order"); - //test FirstLast - $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data); - $this->assertEquals("1first10last", $result, "First and last numbers rendered in order"); + //test FirstLast + $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data); + $this->assertEquals("1first10last", $result, "First and last numbers rendered in order"); - //test Middle - $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("23456789", $result, "Middle numbers rendered in order"); + //test Middle + $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("23456789", $result, "Middle numbers rendered in order"); - //test MiddleString - $result = $this->render( - '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>' - . '<% end_loop %>', - $data - ); - $this->assertEquals( - "2middle3middle4middle5middle6middle7middle8middle9middle", - $result, - "Middle numbers rendered in order" - ); + //test MiddleString + $result = $this->render( + '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>' + . '<% end_loop %>', + $data + ); + $this->assertEquals( + "2middle3middle4middle5middle6middle7middle8middle9middle", + $result, + "Middle numbers rendered in order" + ); - //test EvenOdd - $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data); - $this->assertEquals( - "oddevenoddevenoddevenoddevenoddeven", - $result, - "Even and Odd is returned in sequence numbers rendered in order" - ); + //test EvenOdd + $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data); + $this->assertEquals( + "oddevenoddevenoddevenoddevenoddeven", + $result, + "Even and Odd is returned in sequence numbers rendered in order" + ); - //test Pos - $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data); - $this->assertEquals("12345678910", $result, '$Pos is rendered in order'); + //test Pos + $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data); + $this->assertEquals("12345678910", $result, '$Pos is rendered in order'); - //test Pos - $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data); - $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order'); + //test Pos + $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data); + $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order'); - //test FromEnd - $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data); - $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order'); + //test FromEnd + $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data); + $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order'); - //test FromEnd - $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data); - $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order'); + //test FromEnd + $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data); + $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order'); - //test Total - $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data); - $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned"); + //test Total + $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data); + $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned"); - //test Modulus - $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data); - $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order"); + //test Modulus + $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data); + $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order"); - //test MultipleOf 3 - $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned"); + //test MultipleOf 3 + $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned"); - //test MultipleOf 4 - $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned"); + //test MultipleOf 4 + $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned"); - //test MultipleOf 5 - $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned"); + //test MultipleOf 5 + $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned"); - //test MultipleOf 10 - $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("10", $result, "Only numbers that are multiples of 10 (with 1-based indexing) are returned"); + //test MultipleOf 10 + $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("10", $result, "Only numbers that are multiples of 10 (with 1-based indexing) are returned"); - //test MultipleOf 9 zero-based - $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals( - "110", - $result, - "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)" - ); + //test MultipleOf 9 zero-based + $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals( + "110", + $result, + "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)" + ); - //test MultipleOf 11 - $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned"); - } + //test MultipleOf 11 + $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data); + $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned"); +} /** * Test $Up works when the scope $Up refers to was entered with a "with" block */ - public function testUpInWith() - { +public function testUpInWith() +{ - // Data to run the loop tests on - three levels deep - $data = new ArrayData( + // Data to run the loop tests on - three levels deep + $data = new ArrayData( + array( + 'Name' => 'Top', + 'Foo' => new ArrayData( array( - 'Name' => 'Top', - 'Foo' => new ArrayData( + 'Name' => 'Foo', + 'Bar' => new ArrayData( array( - 'Name' => 'Foo', - 'Bar' => new ArrayData( + 'Name' => 'Bar', + 'Baz' => new ArrayData( array( - 'Name' => 'Bar', - 'Baz' => new ArrayData( - array( - 'Name' => 'Baz' - ) - ), - 'Qux' => new ArrayData( - array( - 'Name' => 'Qux' - ) + 'Name' => 'Baz' ) + ), + 'Qux' => new ArrayData( + array( + 'Name' => 'Qux' ) ) ) ) ) - ); + ) + ) + ); - // Basic functionality - $this->assertEquals( - 'BarFoo', - $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data) - ); + // Basic functionality + $this->assertEquals( + 'BarFoo', + $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data) + ); - // Two level with block, up refers to internally referenced Bar - $this->assertEquals( - 'BarFoo', - $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data) - ); + // Two level with block, up refers to internally referenced Bar + $this->assertEquals( + 'BarFoo', + $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data) + ); - // Stepping up & back down the scope tree - $this->assertEquals( - 'BazBarQux', - $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data) - ); + // Stepping up & back down the scope tree + $this->assertEquals( + 'BazBarQux', + $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data) + ); - // Using $Up in a with block - $this->assertEquals( - 'BazBarQux', - $this->render( - '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>' - .'<% end_with %>', - $data - ) - ); + // Using $Up in a with block + $this->assertEquals( + 'BazBarQux', + $this->render( + '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>' + .'<% end_with %>', + $data + ) + ); - // Stepping up & back down the scope tree with with blocks - $this->assertEquals( - 'BazBarQuxBarBaz', - $this->render( - '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>' - . '{$Name}<% end_with %>{$Name}<% end_with %>', - $data - ) - ); + // Stepping up & back down the scope tree with with blocks + $this->assertEquals( + 'BazBarQuxBarBaz', + $this->render( + '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>' + . '{$Name}<% end_with %>{$Name}<% end_with %>', + $data + ) + ); - // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo - $this->assertEquals( - 'Foo', - $this->render( - '<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>' - . '<% end_with %>', - $data - ) - ); + // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo + $this->assertEquals( + 'Foo', + $this->render( + '<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>' + . '<% end_with %>', + $data + ) + ); - // Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo - $this->assertEquals( - 'Foo', - $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data) - ); - } + // Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo + $this->assertEquals( + 'Foo', + $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data) + ); +} /** * Test $Up works when the scope $Up refers to was entered with a "loop" block */ - public function testUpInLoop() - { +public function testUpInLoop() +{ - // Data to run the loop tests on - one sequence of three items, each with a subitem - $data = new ArrayData( + // Data to run the loop tests on - one sequence of three items, each with a subitem + $data = new ArrayData( + array( + 'Name' => 'Top', + 'Foo' => new ArrayList( array( - 'Name' => 'Top', - 'Foo' => new ArrayList( + new ArrayData( array( - new ArrayData( + 'Name' => '1', + 'Sub' => new ArrayData( array( - 'Name' => '1', - 'Sub' => new ArrayData( - array( - 'Name' => 'Bar' - ) + 'Name' => 'Bar' ) - ) - ), - new ArrayData( + ) + ) + ), + new ArrayData( + array( + 'Name' => '2', + 'Sub' => new ArrayData( array( - 'Name' => '2', - 'Sub' => new ArrayData( - array( - 'Name' => 'Baz' - ) + 'Name' => 'Baz' ) - ) - ), - new ArrayData( + ) + ) + ), + new ArrayData( + array( + 'Name' => '3', + 'Sub' => new ArrayData( array( - 'Name' => '3', - 'Sub' => new ArrayData( - array( - 'Name' => 'Qux' - ) - ) + 'Name' => 'Qux' ) ) ) ) ) - ); + ) + ) + ); - // Make sure inside a loop, $Up refers to the current item of the loop - $this->assertEqualIgnoringWhitespace( - '111 222 333', - $this->render( - '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>', - $data - ) - ); + // Make sure inside a loop, $Up refers to the current item of the loop + $this->assertEqualIgnoringWhitespace( + '111 222 333', + $this->render( + '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>', + $data + ) + ); - // Make sure inside a loop, looping over $Up uses a separate iterator, - // and doesn't interfere with the original iterator - $this->assertEqualIgnoringWhitespace( - '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3', - $this->render( - '<% loop $Foo %> + // Make sure inside a loop, looping over $Up uses a separate iterator, + // and doesn't interfere with the original iterator + $this->assertEqualIgnoringWhitespace( + '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3', + $this->render( + '<% loop $Foo %> $Name <% with $Sub %> $Name @@ -1441,16 +1470,16 @@ after' <% end_with %> $Name <% end_loop %>', - $data - ) - ); + $data + ) + ); - // Make sure inside a loop, looping over $Up uses a separate iterator, - // and doesn't interfere with the original iterator or local lookups - $this->assertEqualIgnoringWhitespace( - '1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3', - $this->render( - '<% loop $Foo %> + // Make sure inside a loop, looping over $Up uses a separate iterator, + // and doesn't interfere with the original iterator or local lookups + $this->assertEqualIgnoringWhitespace( + '1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3', + $this->render( + '<% loop $Foo %> $Name <% with $Sub %> {$Name}{$Up.Name} @@ -1459,183 +1488,183 @@ after' <% end_with %> $Name <% end_loop %>', - $data - ) - ); - } + $data + ) + ); +} /** * Test that nested loops restore the loop variables correctly when pushing and popping states */ - public function testNestedLoops() - { +public function testNestedLoops() +{ - // Data to run the loop tests on - one sequence of three items, one with child elements - // (of a different size to the main sequence) - $data = new ArrayData( + // Data to run the loop tests on - one sequence of three items, one with child elements + // (of a different size to the main sequence) + $data = new ArrayData( + array( + 'Foo' => new ArrayList( array( - 'Foo' => new ArrayList( + new ArrayData( array( - new ArrayData( + 'Name' => '1', + 'Children' => new ArrayList( array( - 'Name' => '1', - 'Children' => new ArrayList( + new ArrayData( array( - new ArrayData( - array( - 'Name' => 'a' - ) - ), - new ArrayData( - array( - 'Name' => 'b' - ) - ), + 'Name' => 'a' + ) + ), + new ArrayData( + array( + 'Name' => 'b' ) ), ) ), - new ArrayData( - array( - 'Name' => '2', - 'Children' => new ArrayList(), - ) - ), - new ArrayData( - array( - 'Name' => '3', - 'Children' => new ArrayList(), - ) - ), + ) + ), + new ArrayData( + array( + 'Name' => '2', + 'Children' => new ArrayList(), + ) + ), + new ArrayData( + array( + 'Name' => '3', + 'Children' => new ArrayList(), ) ), ) - ); + ), + ) + ); - // Make sure that including a loop inside a loop will not destroy the internal count of - // items, checked by using "Last" - $this->assertEqualIgnoringWhitespace( - '1ab23last', - $this->render( - '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>' - . '<% end_loop %>', - $data - ) - ); - } + // Make sure that including a loop inside a loop will not destroy the internal count of + // items, checked by using "Last" + $this->assertEqualIgnoringWhitespace( + '1ab23last', + $this->render( + '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>' + . '<% end_loop %>', + $data + ) + ); +} - public function testLayout() - { - $this->useTestTheme( - __DIR__.'/SSViewerTest', - 'layouttest', - function () { - $template = new SSViewer(array('Page')); - $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array()))); +public function testLayout() +{ + $this->useTestTheme( + __DIR__.'/SSViewerTest', + 'layouttest', + function () { + $template = new SSViewer(array('Page')); + $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array()))); - $template = new SSViewer(array('Shortcodes', 'Page')); - $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array()))); - } - ); - } + $template = new SSViewer(array('Shortcodes', 'Page')); + $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array()))); + } + ); +} /** * @covers \SilverStripe\View\SSViewer::get_templates_by_class() */ - public function testGetTemplatesByClass() - { - $this->useTestTheme( - __DIR__ . '/SSViewerTest', - 'layouttest', - function () { - // Test passing a string - $templates = SSViewer::get_templates_by_class( +public function testGetTemplatesByClass() +{ + $this->useTestTheme( + __DIR__ . '/SSViewerTest', + 'layouttest', + function () { + // Test passing a string + $templates = SSViewer::get_templates_by_class( + SSViewerTestModelController::class, + '', + Controller::class + ); + $this->assertEquals( + [ SSViewerTestModelController::class, - '', - Controller::class - ); - $this->assertEquals( [ + 'type' => 'Includes', SSViewerTestModelController::class, - [ - 'type' => 'Includes', - SSViewerTestModelController::class, - ], - SSViewerTestModel::class, - Controller::class, - [ - 'type' => 'Includes', - Controller::class, - ], ], - $templates - ); - - // Test to ensure we're stopping at the base class. - $templates = SSViewer::get_templates_by_class( - SSViewerTestModelController::class, - '', - SSViewerTestModelController::class - ); - $this->assertEquals( - [ - SSViewerTestModelController::class, - [ - 'type' => 'Includes', - SSViewerTestModelController::class, - ], - SSViewerTestModel::class, - ], - $templates - ); - - // Make sure we can search templates by suffix. - $templates = SSViewer::get_templates_by_class( SSViewerTestModel::class, - 'Controller', - DataObject::class - ); - $this->assertEquals( + Controller::class, [ - SSViewerTestModelController::class, - [ - 'type' => 'Includes', - SSViewerTestModelController::class, - ], - DataObject::class . 'Controller', - [ - 'type' => 'Includes', - DataObject::class . 'Controller', - ], + 'type' => 'Includes', + Controller::class, ], - $templates - ); + ], + $templates + ); - // Let's throw something random in there. - $this->setExpectedException('InvalidArgumentException'); - SSViewer::get_templates_by_class(array()); - } - ); - } + // Test to ensure we're stopping at the base class. + $templates = SSViewer::get_templates_by_class( + SSViewerTestModelController::class, + '', + SSViewerTestModelController::class + ); + $this->assertEquals( + [ + SSViewerTestModelController::class, + [ + 'type' => 'Includes', + SSViewerTestModelController::class, + ], + SSViewerTestModel::class, + ], + $templates + ); - public function testRewriteHashlinks() - { - SSViewer::config()->update('rewrite_hash_links', true); + // Make sure we can search templates by suffix. + $templates = SSViewer::get_templates_by_class( + SSViewerTestModel::class, + 'Controller', + DataObject::class + ); + $this->assertEquals( + [ + SSViewerTestModelController::class, + [ + 'type' => 'Includes', + SSViewerTestModelController::class, + ], + DataObject::class . 'Controller', + [ + 'type' => 'Includes', + DataObject::class . 'Controller', + ], + ], + $templates + ); - $_SERVER['HTTP_HOST'] = 'www.mysite.com'; - $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""'; + // Let's throw something random in there. + $this->setExpectedException('InvalidArgumentException'); + SSViewer::get_templates_by_class(array()); + } + ); +} - // Emulate SSViewer::process() - // Note that leading double slashes have been rewritten to prevent these being mis-interepreted - // as protocol-less absolute urls - $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""'); +public function testRewriteHashlinks() +{ + SSViewer::config()->update('rewrite_hash_links', true); - $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss'; + $_SERVER['HTTP_HOST'] = 'www.mysite.com'; + $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""'; - // Note: SSViewer_FromString doesn't rewrite hash links. - file_put_contents( - $tmplFile, - ' + // Emulate SSViewer::process() + // Note that leading double slashes have been rewritten to prevent these being mis-interepreted + // as protocol-less absolute urls + $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""'); + + $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss'; + + // Note: SSViewer_FromString doesn't rewrite hash links. + file_put_contents( + $tmplFile, + ' <% base_tag %> @@ -1646,53 +1675,53 @@ after' ' - ); - $tmpl = new SSViewer($tmplFile); - $obj = new ViewableData(); - $obj->InsertedLink = DBField::create_field( - 'HTMLFragment', - 'InsertedLink' - ); - $obj->ExternalInsertedLink = DBField::create_field( - 'HTMLFragment', - 'ExternalInsertedLink' - ); - $result = $tmpl->process($obj); - $this->assertContains( - 'InsertedLink', - $result - ); - $this->assertContains( - 'ExternalInsertedLink', - $result - ); - $this->assertContains( - 'InlineLink', - $result - ); - $this->assertContains( - 'ExternalInlineLink', - $result - ); - $this->assertContains( - '', - $result, - 'SSTemplateParser should only rewrite anchor hrefs' - ); + ); + $tmpl = new SSViewer($tmplFile); + $obj = new ViewableData(); + $obj->InsertedLink = DBField::create_field( + 'HTMLFragment', + 'InsertedLink' + ); + $obj->ExternalInsertedLink = DBField::create_field( + 'HTMLFragment', + 'ExternalInsertedLink' + ); + $result = $tmpl->process($obj); + $this->assertContains( + 'InsertedLink', + $result + ); + $this->assertContains( + 'ExternalInsertedLink', + $result + ); + $this->assertContains( + 'InlineLink', + $result + ); + $this->assertContains( + 'ExternalInlineLink', + $result + ); + $this->assertContains( + '', + $result, + 'SSTemplateParser should only rewrite anchor hrefs' + ); - unlink($tmplFile); - } + unlink($tmplFile); +} - public function testRewriteHashlinksInPhpMode() - { - SSViewer::config()->update('rewrite_hash_links', 'php'); +public function testRewriteHashlinksInPhpMode() +{ + SSViewer::config()->update('rewrite_hash_links', 'php'); - $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinksInPhpMode_' . sha1(rand()) . '.ss'; + $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinksInPhpMode_' . sha1(rand()) . '.ss'; - // Note: SSViewer_FromString doesn't rewrite hash links. - file_put_contents( - $tmplFile, - ' + // Note: SSViewer_FromString doesn't rewrite hash links. + file_put_contents( + $tmplFile, + ' <% base_tag %> @@ -1701,236 +1730,236 @@ after' ' - ); - $tmpl = new SSViewer($tmplFile); - $obj = new ViewableData(); - $obj->InsertedLink = DBField::create_field( - 'HTMLFragment', - 'InsertedLink' - ); - $result = $tmpl->process($obj); + ); + $tmpl = new SSViewer($tmplFile); + $obj = new ViewableData(); + $obj->InsertedLink = DBField::create_field( + 'HTMLFragment', + 'InsertedLink' + ); + $result = $tmpl->process($obj); - $code = <<<'EOC' + $code = <<<'EOC' #anchor">InsertedLink EOC; - $this->assertContains($code, $result); - // TODO Fix inline links in PHP mode - // $this->assertContains( - // '', - $result, - 'SSTemplateParser should only rewrite anchor hrefs' - ); + $this->assertContains($code, $result); + // TODO Fix inline links in PHP mode + // $this->assertContains( + // '', + $result, + 'SSTemplateParser should only rewrite anchor hrefs' + ); - unlink($tmplFile); - } + unlink($tmplFile); +} - public function testRenderWithSourceFileComments() - { - Director::set_environment_type('dev'); - SSViewer::config()->update('source_file_comments', true); - $i = __DIR__ . '/SSViewerTest/templates/Includes'; - $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments'; - $templates = array( - array( - 'name' => 'SSViewerTestCommentsFullSource', - 'expected' => "" - . "" - . "" - . "" - . "\t" - . "\t" - . "" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype', - 'expected' => "" - . "" - . "" - . "" - . "\t" - . "\t" - . "" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsFullSourceNoDoctype', - 'expected' => "" - . "" - . "\t" - . "\t" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsFullSourceIfIE', - 'expected' => "" - . "" - . "" - . "" - . "" - . " " - . "\t" - . "\t" - . "" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype', - 'expected' => "" - . "" - . "" - . " " - . "" - . " " - . "\t" - . "\t" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsPartialSource', - 'expected' => - "" - . "
" - . "", - ), - array( - 'name' => 'SSViewerTestCommentsWithInclude', - 'expected' => - "" - . "
" - . "" - . "" - . "Included" - . "" - . "" - . "
" - . "", - ), - ); - foreach ($templates as $template) { - $this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']); - } - } - private function _renderWithSourceFileComments($name, $expected) - { - $viewer = new SSViewer(array($name)); - $data = new ArrayData(array()); - $result = $viewer->process($data); - $expected = str_replace(array("\r", "\n"), '', $expected); - $result = str_replace(array("\r", "\n"), '', $result); - $this->assertEquals($result, $expected); +public function testRenderWithSourceFileComments() +{ + Director::set_environment_type('dev'); + SSViewer::config()->update('source_file_comments', true); + $i = __DIR__ . '/SSViewerTest/templates/Includes'; + $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments'; + $templates = array( + array( + 'name' => 'SSViewerTestCommentsFullSource', + 'expected' => "" + . "" + . "" + . "" + . "\t" + . "\t" + . "" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype', + 'expected' => "" + . "" + . "" + . "" + . "\t" + . "\t" + . "" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsFullSourceNoDoctype', + 'expected' => "" + . "" + . "\t" + . "\t" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsFullSourceIfIE', + 'expected' => "" + . "" + . "" + . "" + . "" + . " " + . "\t" + . "\t" + . "" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype', + 'expected' => "" + . "" + . "" + . " " + . "" + . " " + . "\t" + . "\t" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsPartialSource', + 'expected' => + "" + . "
" + . "", + ), + array( + 'name' => 'SSViewerTestCommentsWithInclude', + 'expected' => + "" + . "
" + . "" + . "" + . "Included" + . "" + . "" + . "
" + . "", + ), + ); + foreach ($templates as $template) { + $this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']); } +} +private function _renderWithSourceFileComments($name, $expected) +{ + $viewer = new SSViewer(array($name)); + $data = new ArrayData(array()); + $result = $viewer->process($data); + $expected = str_replace(array("\r", "\n"), '', $expected); + $result = str_replace(array("\r", "\n"), '', $result); + $this->assertEquals($result, $expected); +} - public function testLoopIteratorIterator() - { - $list = new PaginatedList(new ArrayList()); - $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName
<% end_loop %>'); - $result = $viewer->process(new ArrayData(array('List' => $list))); - $this->assertEquals($result, ''); - } +public function testLoopIteratorIterator() +{ + $list = new PaginatedList(new ArrayList()); + $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName
<% end_loop %>'); + $result = $viewer->process(new ArrayData(array('List' => $list))); + $this->assertEquals($result, ''); +} - public function testProcessOnlyIncludesRequirementsOnce() - { +public function testProcessOnlyIncludesRequirementsOnce() +{ + $template = new SSViewer(array('SSViewerTestProcess')); + $basePath = $this->getCurrentRelativePath() . '/SSViewerTest'; + + $backend = Injector::inst()->create(Requirements_Backend::class); + $backend->setCombinedFilesEnabled(false); + $backend->combineFiles( + 'RequirementsTest_ab.css', + array( + $basePath . '/css/RequirementsTest_a.css', + $basePath . '/css/RequirementsTest_b.css' + ) + ); + + Requirements::set_backend($backend); + + $this->assertEquals(1, substr_count($template->process(array()), "a.css")); + $this->assertEquals(1, substr_count($template->process(array()), "b.css")); + + // if we disable the requirements then we should get nothing + $template->includeRequirements(false); + $this->assertEquals(0, substr_count($template->process(array()), "a.css")); + $this->assertEquals(0, substr_count($template->process(array()), "b.css")); +} + +public function testRequireCallInTemplateInclude() +{ + //TODO undo skip test on the event that templates ever obtain the ability to reference MODULE_DIR (or something to that effect) + if (FRAMEWORK_DIR === 'framework') { $template = new SSViewer(array('SSViewerTestProcess')); - $basePath = $this->getCurrentRelativePath() . '/SSViewerTest'; - $backend = Injector::inst()->create(Requirements_Backend::class); - $backend->setCombinedFilesEnabled(false); - $backend->combineFiles( - 'RequirementsTest_ab.css', - array( - $basePath . '/css/RequirementsTest_a.css', - $basePath . '/css/RequirementsTest_b.css' + Requirements::set_suffix_requirements(false); + + $this->assertEquals( + 1, + substr_count( + $template->process(array()), + "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js" ) ); - - Requirements::set_backend($backend); - - $this->assertEquals(1, substr_count($template->process(array()), "a.css")); - $this->assertEquals(1, substr_count($template->process(array()), "b.css")); - - // if we disable the requirements then we should get nothing - $template->includeRequirements(false); - $this->assertEquals(0, substr_count($template->process(array()), "a.css")); - $this->assertEquals(0, substr_count($template->process(array()), "b.css")); - } - - public function testRequireCallInTemplateInclude() - { - //TODO undo skip test on the event that templates ever obtain the ability to reference MODULE_DIR (or something to that effect) - if (FRAMEWORK_DIR === 'framework') { - $template = new SSViewer(array('SSViewerTestProcess')); - - Requirements::set_suffix_requirements(false); - - $this->assertEquals( - 1, - substr_count( - $template->process(array()), - "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js" - ) - ); - } else { - $this->markTestSkipped( - 'Requirement will always fail if the framework dir is not '. - 'named \'framework\', since templates require hard coded paths' - ); - } - } - - public function testCallsWithArguments() - { - $data = new ArrayData( - array( - 'Set' => new ArrayList( - array( - new SSViewerTest\TestObject("1"), - new SSViewerTest\TestObject("2"), - new SSViewerTest\TestObject("3"), - new SSViewerTest\TestObject("4"), - new SSViewerTest\TestObject("5"), - ) - ), - 'Level' => new SSViewerTest\LevelTestData(1), - 'Nest' => array( - 'Level' => new SSViewerTest\LevelTestData(2), - ), - ) + } else { + $this->markTestSkipped( + 'Requirement will always fail if the framework dir is not '. + 'named \'framework\', since templates require hard coded paths' ); + } +} - $tests = array( - '$Level.output(1)' => '1-1', - '$Nest.Level.output($Set.First.Number)' => '2-1', - '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1', - '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1', - '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5', - '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5', - '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1', - '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5', - '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi', - '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0', - '<% with $Nest %> +public function testCallsWithArguments() +{ + $data = new ArrayData( + array( + 'Set' => new ArrayList( + array( + new SSViewerTest\TestObject("1"), + new SSViewerTest\TestObject("2"), + new SSViewerTest\TestObject("3"), + new SSViewerTest\TestObject("4"), + new SSViewerTest\TestObject("5"), + ) + ), + 'Level' => new SSViewerTest\LevelTestData(1), + 'Nest' => array( + 'Level' => new SSViewerTest\LevelTestData(2), + ), + ) + ); + + $tests = array( + '$Level.output(1)' => '1-1', + '$Nest.Level.output($Set.First.Number)' => '2-1', + '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1', + '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1', + '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5', + '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5', + '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1', + '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5', + '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi', + '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0', + '<% with $Nest %> <% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %> <% end_with %>' => '1-hi', - '<% with $Nest %> + '<% with $Nest %> <% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %> <% end_with %>' => '!0!1!2!3!4', - ); + ); - foreach ($tests as $template => $expected) { - $this->assertEquals($expected, trim($this->render($template, $data))); - } + foreach ($tests as $template => $expected) { + $this->assertEquals($expected, trim($this->render($template, $data))); } +} - public function testRepeatedCallsAreCached() - { - $data = new SSViewerTest\CacheTestData(); - $template = ' +public function testRepeatedCallsAreCached() +{ + $data = new SSViewerTest\CacheTestData(); + $template = ' <% if $TestWithCall %> <% with $TestWithCall %> {$Message} @@ -1939,89 +1968,89 @@ EOC; {$TestWithCall.Message} <% end_if %>'; - $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data))); - $this->assertEquals( - 1, - $data->testWithCalls, - 'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached' - ); + $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data))); + $this->assertEquals( + 1, + $data->testWithCalls, + 'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached' + ); - $data = new SSViewerTest\CacheTestData(); - $template = ' + $data = new SSViewerTest\CacheTestData(); + $template = ' <% if $TestLoopCall %> <% loop $TestLoopCall %> {$Message} <% end_loop %> <% end_if %>'; - $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data))); - $this->assertEquals( - 1, - $data->testLoopCalls, - 'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached' - ); - } + $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data))); + $this->assertEquals( + 1, + $data->testLoopCalls, + 'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached' + ); +} - public function testClosedBlockExtension() - { - $count = 0; - $parser = new SSTemplateParser(); - $parser->addClosedBlock( - 'test', - function ($res) use (&$count) { - $count++; - } - ); +public function testClosedBlockExtension() +{ + $count = 0; + $parser = new SSTemplateParser(); + $parser->addClosedBlock( + 'test', + function ($res) use (&$count) { + $count++; + } + ); - $template = new SSViewer_FromString("<% test %><% end_test %>", $parser); - $template->process(new SSViewerTest\TestFixture()); + $template = new SSViewer_FromString("<% test %><% end_test %>", $parser); + $template->process(new SSViewerTest\TestFixture()); - $this->assertEquals(1, $count); - } + $this->assertEquals(1, $count); +} - public function testOpenBlockExtension() - { - $count = 0; - $parser = new SSTemplateParser(); - $parser->addOpenBlock( - 'test', - function ($res) use (&$count) { - $count++; - } - ); +public function testOpenBlockExtension() +{ + $count = 0; + $parser = new SSTemplateParser(); + $parser->addOpenBlock( + 'test', + function ($res) use (&$count) { + $count++; + } + ); - $template = new SSViewer_FromString("<% test %>", $parser); - $template->process(new SSViewerTest\TestFixture()); + $template = new SSViewer_FromString("<% test %>", $parser); + $template->process(new SSViewerTest\TestFixture()); - $this->assertEquals(1, $count); - } + $this->assertEquals(1, $count); +} /** * Tests if caching for SSViewer_FromString is working */ - public function testFromStringCaching() - { - $content = 'Test content'; - $cacheFile = TEMP_FOLDER . '/.cache.' . sha1($content); - if (file_exists($cacheFile)) { - unlink($cacheFile); - } - - // Test global behaviors - $this->render($content, null, null); - $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off'); - - SSViewer_FromString::config()->update('cache_template', true); - $this->render($content, null, null); - $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to'); - unlink($cacheFile); - - // Test instance behaviors - $this->render($content, null, false); - $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off'); - - $this->render($content, null, true); - $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to'); +public function testFromStringCaching() +{ + $content = 'Test content'; + $cacheFile = TEMP_FOLDER . '/.cache.' . sha1($content); + if (file_exists($cacheFile)) { unlink($cacheFile); } + + // Test global behaviors + $this->render($content, null, null); + $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off'); + + SSViewer_FromString::config()->update('cache_template', true); + $this->render($content, null, null); + $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to'); + unlink($cacheFile); + + // Test instance behaviors + $this->render($content, null, false); + $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off'); + + $this->render($content, null, true); + $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to'); + unlink($cacheFile); +} }