New API for minified files using injectable service

This commit is contained in:
Aaron Carlino 2017-05-10 16:24:30 +12:00
parent 4dea6cb0d5
commit 7fa47e234f
4 changed files with 1698 additions and 1589 deletions

View File

@ -169,13 +169,13 @@ replaced. For instance, the below will set a new set of dependencies to write to
--- ---
Name: myrequirements Name: myrequirements
--- ---
Requirements: SilverStripe\View\Requirements:
disable_flush_combined: true disable_flush_combined: true
Requirements_Backend: SilverStripe\View\Requirements_Backend:
combine_in_dev: true combine_in_dev: true
combine_hash_querystring: true combine_hash_querystring: true
default_combined_files_folder: 'combined' default_combined_files_folder: 'combined'
Injector: SilverStripe\Core\Injector\Injector:
MySiteAdapter: MySiteAdapter:
class: 'SilverStripe\Filesystem\Flysystem\AssetAdapter' class: 'SilverStripe\Filesystem\Flysystem\AssetAdapter'
constructor: 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 class: SilverStripe\Filesystem\Storage\FlysystemGeneratedAssetHandler
properties: properties:
Filesystem: '%$MySiteBackend' Filesystem: '%$MySiteBackend'
Requirements_Backend: SilverStripe\View\Requirements_Backend:
properties: properties:
AssetHandler: '%$MySiteAssetHandler' AssetHandler: '%$MySiteAssetHandler'
@ -248,6 +248,49 @@ $scripts = array(
Requirements::combine_files('scripts.js', $scripts, array('async' => true, 'defer' => true)); 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
```
<div class="alert" markdown='1'>
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/).
</div>
## Clearing assets ## Clearing assets

View File

@ -3,6 +3,7 @@
namespace SilverStripe\View; namespace SilverStripe\View;
use InvalidArgumentException; use InvalidArgumentException;
use Exception;
use SilverStripe\Assets\File; use SilverStripe\Assets\File;
use SilverStripe\Assets\Storage\GeneratedAssetHandler; use SilverStripe\Assets\Storage\GeneratedAssetHandler;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
@ -10,7 +11,6 @@ use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug; use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
@ -121,11 +121,11 @@ class Requirements_Backend
protected $combinedFiles = array(); 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 * @var bool
*/ */
protected $minifyCombinedJSFiles = true; protected $minifyCombinedFiles = false;
/** /**
* Whether or not file headers should be written when combining files * Whether or not file headers should be written when combining files
@ -191,6 +191,11 @@ class Requirements_Backend
*/ */
protected $assetHandler = null; protected $assetHandler = null;
/**
* @var Requirements_Minifier
*/
protected $minifier = null;
/** /**
* Gets the backend storage for generated files * Gets the backend storage for generated files
* *
@ -211,6 +216,26 @@ class Requirements_Backend
$this->assetHandler = $handler; $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 * 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 * @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 * @param bool $minify
* @return $this * @return $this
*/ */
public function setMinifyCombinedJSFiles($minify) public function setMinifyCombinedFiles($minify)
{ {
$this->minifyCombinedJSFiles = $minify; $this->minifyCombinedFiles = $minify;
return $this; return $this;
} }
@ -1278,7 +1303,20 @@ class Requirements_Backend
$combinedFileID = File::join_paths($this->getCombinedFilesFolder(), $combinedFile); $combinedFileID = File::join_paths($this->getCombinedFilesFolder(), $combinedFile);
// Send file combination request to the backend, with an optional callback to perform regeneration // 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 $combinedURL = $this
->getAssetHandler() ->getAssetHandler()
->getContentURL( ->getContentURL(
@ -1287,12 +1325,11 @@ class Requirements_Backend
// Physically combine all file content // Physically combine all file content
$combinedData = ''; $combinedData = '';
$base = Director::baseFolder() . '/'; $base = Director::baseFolder() . '/';
$minifier = Injector::inst()->get('SilverStripe\\View\\Requirements_Minifier');
foreach ($fileList as $file) { foreach ($fileList as $file) {
$fileContent = file_get_contents($base . $file); $fileContent = file_get_contents($base . $file);
// Use configured minifier // Use configured minifier
if ($minify) { if ($minify) {
$fileContent = $minifier->minify($fileContent, $type, $file); $fileContent = $this->minifier->minify($fileContent, $type, $file);
} }
if ($this->writeHeaderComment) { if ($this->writeHeaderComment) {

View File

@ -86,7 +86,7 @@ class RequirementsTest extends SapphireTest
$backend->clear(); $backend->clear();
$backend->clearCombinedFiles(); $backend->clearCombinedFiles();
$backend->setCombinedFilesFolder('_combinedfiles'); $backend->setCombinedFilesFolder('_combinedfiles');
$backend->setMinifyCombinedJSFiles(false); $backend->setMinifyCombinedFiles(false);
Requirements::flush(); Requirements::flush();
} }

View File

@ -20,6 +20,7 @@ use SilverStripe\Security\SecurityToken;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements_Backend; use SilverStripe\View\Requirements_Backend;
use SilverStripe\View\Requirements_Minifier;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\Requirements; use SilverStripe\View\Requirements;
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel; use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
@ -28,10 +29,9 @@ use SilverStripe\View\ViewableData;
use SilverStripe\View\SSViewer_FromString; use SilverStripe\View\SSViewer_FromString;
use SilverStripe\View\SSTemplateParser; use SilverStripe\View\SSTemplateParser;
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore; use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
use JSMin;
use Exception; use Exception;
class SSViewerTest extends SapphireTest classSSViewerTest extends SapphireTest
{ {
/** /**
@ -39,148 +39,148 @@ class SSViewerTest extends SapphireTest
* *
* @var array * @var array
*/ */
protected $oldServer = array(); protected $oldServer = array();
protected static $extra_dataobjects = array( protected static $extra_dataobjects = array(
SSViewerTest\TestObject::class, SSViewerTest\TestObject::class,
); );
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
SSViewer::config()->update('source_file_comments', false); SSViewer::config()->update('source_file_comments', false);
SSViewer_FromString::config()->update('cache_template', false); SSViewer_FromString::config()->update('cache_template', false);
TestAssetStore::activate('SSViewerTest'); TestAssetStore::activate('SSViewerTest');
$this->oldServer = $_SERVER; $this->oldServer = $_SERVER;
} }
protected function tearDown() protected function tearDown()
{ {
$_SERVER = $this->oldServer; $_SERVER = $this->oldServer;
TestAssetStore::reset(); TestAssetStore::reset();
parent::tearDown(); parent::tearDown();
} }
/** /**
* Tests for {@link Config::inst()->get('SSViewer', 'theme')} for different behaviour * Tests for {@link Config::inst()->get('SSViewer', 'theme')} for different behaviour
* of user defined themes via {@link SiteConfig} and default theme * of user defined themes via {@link SiteConfig} and default theme
* when no user themes are defined. * when no user themes are defined.
*/ */
public function testCurrentTheme() public function testCurrentTheme()
{ {
SSViewer::config()->update('theme', 'mytheme'); SSViewer::config()->update('theme', 'mytheme');
$this->assertEquals( $this->assertEquals(
'mytheme', 'mytheme',
SSViewer::config()->uninherited('theme'), SSViewer::config()->uninherited('theme'),
'Current theme is the default - user has not defined one' 'Current theme is the default - user has not defined one'
); );
} }
/** /**
* Test that a template without a <head> tag still renders. * Test that a template without a <head> tag still renders.
*/ */
public function testTemplateWithoutHeadRenders() public function testTemplateWithoutHeadRenders()
{ {
$data = new ArrayData( $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( 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"); // We should not end up with empty values appearing as empty
$this->assertEquals('Test partial template: var value', trim(preg_replace("/<!--.*-->/U", '', $result))); $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() private function getScopeInheritanceTestData()
{ {
$data = $this->getScopeInheritanceTestData(); return new ArrayData(
$expected = array( array(
'Item 1 - First-ODD top:Item 1', 'Title' => 'TopTitleValue',
'Item 2 - EVEN top:Item 2', 'Items' => new ArrayList(
'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( array(
'Title' => 'TruthyTest', new ArrayData(array('Title' => 'Item 1')),
'Items' => new ArrayList( new ArrayData(array('Title' => 'Item 2')),
array( new ArrayData(array('Title' => 'Item 3')),
new ArrayData(array('Title' => 'Item 1')), new ArrayData(array('Title' => 'Item 4')),
new ArrayData(array('Title' => '')), new ArrayData(array('Title' => 'Item 5')),
new ArrayData(array('Title' => true)), new ArrayData(array('Title' => 'Item 6'))
new ArrayData(array('Title' => false)),
new ArrayData(array('Title' => null)),
new ArrayData(array('Title' => 0)),
new ArrayData(array('Title' => 7))
)
) )
) )
); )
$result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs'); );
}
// We should not end up with empty values appearing as empty private function assertExpectedStrings($result, $expected)
$expected = array( {
'Item 1 _ Item 1 - First-ODD top:Item 1', foreach ($expected as $expectedStr) {
'Untitled - EVEN top:', $this->assertTrue(
'1 _ 1 - ODD top:1', (boolean) preg_match("/{$expectedStr}/", $result),
'Untitled - EVEN top:', "Didn't find '{$expectedStr}' in:\n{$result}"
'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}"
);
}
}
/** /**
* Small helper to render templates from strings * Small helper to render templates from strings
@ -190,75 +190,104 @@ class SSViewerTest extends SapphireTest
* @param bool $cacheTemplate * @param bool $cacheTemplate
* @return string * @return string
*/ */
public function render($templateString, $data = null, $cacheTemplate = false) public function render($templateString, $data = null, $cacheTemplate = false)
{ {
$t = SSViewer::fromString($templateString, $cacheTemplate); $t = SSViewer::fromString($templateString, $cacheTemplate);
if (!$data) { if (!$data) {
$data = new SSViewerTest\TestFixture(); $data = new SSViewerTest\TestFixture();
}
return trim(''.$t->process($data));
} }
return trim(''.$t->process($data));
}
public function testRequirements() public function testRequirements()
{ {
$requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css")) $requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css"))
->getMock(); ->getMock();
$jsFile = FRAMEWORK_DIR . '/tests/forms/a.js'; $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js';
$cssFile = 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('javascript')->with($jsFile);
$requirements->expects($this->once())->method('css')->with($cssFile); $requirements->expects($this->once())->method('css')->with($cssFile);
$origReq = Requirements::backend(); $origReq = Requirements::backend();
Requirements::set_backend($requirements); Requirements::set_backend($requirements);
$template = $this->render( $template = $this->render(
"<% require javascript($jsFile) %> "<% require javascript($jsFile) %>
<% require css($cssFile) %>" <% 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() $this->setExpectedExceptionRegExp(
{ Exception::class,
$testBackend = Injector::inst()->create(Requirements_Backend::class); '/minification service/'
$testBackend->setSuffixRequirements(false); );
//$combinedTestFilePath = BASE_PATH . '/' . $testBackend->getCombinedFilesFolder() . '/testRequirementsCombine.js';
$jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js'; $testBackend->setMinifyCombinedFiles(true);
$jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile); $testBackend->setMinifier(null);
$testBackend->combineFiles('testRequirementsCombine.js', array($jsFile)); $testBackend->processCombinedFiles();
}
// 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);
}
public function testComments() public function testComments()
{ {
$output = $this->render( $output = $this->render(
<<<SS <<<SS
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content
<%-- Alone multi <%-- Alone multi
line comment --%> line comment --%>
@ -267,8 +296,8 @@ Mixing content and <%-- multi
line comment --%> Final final line comment --%> Final final
content content
SS SS
); );
$shouldbe = <<<SS $shouldbe = <<<SS
This is my templateThis is some contentFinal content This is my templateThis is some contentFinal content
Some more content Some more content
@ -276,281 +305,281 @@ Mixing content and Final final
content content
SS; SS;
$this->assertEquals($shouldbe, $output); $this->assertEquals($shouldbe, $output);
} }
public function testBasicText() public function testBasicText()
{ {
$this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone'); $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone');
$this->assertEquals("'", $this->render("'"), 'Single-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 characters are unescaped');
$this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped'); $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped');
} }
public function testBasicInjection() public function testBasicInjection()
{ {
$this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection'); $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('[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]!', $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[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 $ 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 {$ 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('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection');
$this->assertEquals( $this->assertEquals(
'{\\[out:Test]}', '{\\[out:Test]}',
$this->render('{\\\\$Test}'), $this->render('{\\\\$Test}'),
'Escapes before injections are correctly unescaped' 'Escapes before injections are correctly unescaped'
); );
} }
public function testGlobalVariableCalls() public function testGlobalVariableCalls()
{ {
$this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic')); $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic'));
$this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString')); $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString'));
$this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray')); $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray'));
} }
public function testGlobalVariableCallsWithArguments() public function testGlobalVariableCallsWithArguments()
{ {
$this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments')); $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments'));
$this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")')); $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")'));
$this->assertEquals( $this->assertEquals(
'zFoo:Bar:Bazz', 'zFoo:Bar:Bazz',
$this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")') $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
); );
$this->assertEquals( $this->assertEquals(
'zreferencez', 'zreferencez',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)') $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
); );
} }
public function testGlobalVariablesAreEscaped() public function testGlobalVariablesAreEscaped()
{ {
$this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLFragment')); $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLFragment'));
$this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped')); $this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped'));
$this->assertEquals( $this->assertEquals(
'z<div></div>z', 'z<div></div>z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)') $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
); );
$this->assertEquals( $this->assertEquals(
'z&lt;div&gt;&lt;/div&gt;z', 'z&lt;div&gt;&lt;/div&gt;z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)') $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
); );
} }
public function testCoreGlobalVariableCalls() public function testCoreGlobalVariableCalls()
{ {
$this->assertEquals( $this->assertEquals(
Director::absoluteBaseURL(), Director::absoluteBaseURL(),
$this->render('{$absoluteBaseURL}'), $this->render('{$absoluteBaseURL}'),
'Director::absoluteBaseURL can be called from within template' 'Director::absoluteBaseURL can be called from within template'
); );
$this->assertEquals( $this->assertEquals(
Director::absoluteBaseURL(), Director::absoluteBaseURL(),
$this->render('{$AbsoluteBaseURL}'), $this->render('{$AbsoluteBaseURL}'),
'Upper-case %AbsoluteBaseURL can be called from within template' 'Upper-case %AbsoluteBaseURL can be called from within template'
); );
$this->assertEquals( $this->assertEquals(
Director::is_ajax(), Director::is_ajax(),
$this->render('{$isAjax}'), $this->render('{$isAjax}'),
'All variations of is_ajax result in the correct call' 'All variations of is_ajax result in the correct call'
); );
$this->assertEquals( $this->assertEquals(
Director::is_ajax(), Director::is_ajax(),
$this->render('{$IsAjax}'), $this->render('{$IsAjax}'),
'All variations of is_ajax result in the correct call' 'All variations of is_ajax result in the correct call'
); );
$this->assertEquals( $this->assertEquals(
Director::is_ajax(), Director::is_ajax(),
$this->render('{$is_ajax}'), $this->render('{$is_ajax}'),
'All variations of is_ajax result in the correct call' 'All variations of is_ajax result in the correct call'
); );
$this->assertEquals( $this->assertEquals(
Director::is_ajax(), Director::is_ajax(),
$this->render('{$Is_ajax}'), $this->render('{$Is_ajax}'),
'All variations of is_ajax result in the correct call' 'All variations of is_ajax result in the correct call'
); );
$this->assertEquals( $this->assertEquals(
i18n::get_locale(), i18n::get_locale(),
$this->render('{$i18nLocale}'), $this->render('{$i18nLocale}'),
'i18n template functions result correct result' 'i18n template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
i18n::get_locale(), i18n::get_locale(),
$this->render('{$get_locale}'), $this->render('{$get_locale}'),
'i18n template functions result correct result' 'i18n template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
(string)Member::currentUser(), (string)Member::currentUser(),
$this->render('{$CurrentMember}'), $this->render('{$CurrentMember}'),
'Member template functions result correct result' 'Member template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
(string)Member::currentUser(), (string)Member::currentUser(),
$this->render('{$CurrentUser}'), $this->render('{$CurrentUser}'),
'Member template functions result correct result' 'Member template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
(string)Member::currentUser(), (string)Member::currentUser(),
$this->render('{$currentMember}'), $this->render('{$currentMember}'),
'Member template functions result correct result' 'Member template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
(string)Member::currentUser(), (string)Member::currentUser(),
$this->render('{$currentUser}'), $this->render('{$currentUser}'),
'Member template functions result correct result' 'Member template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
SecurityToken::getSecurityID(), SecurityToken::getSecurityID(),
$this->render('{$getSecurityID}'), $this->render('{$getSecurityID}'),
'SecurityToken template functions result correct result' 'SecurityToken template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
SecurityToken::getSecurityID(), SecurityToken::getSecurityID(),
$this->render('{$SecurityID}'), $this->render('{$SecurityID}'),
'SecurityToken template functions result correct result' 'SecurityToken template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
Permission::check("ADMIN"), Permission::check("ADMIN"),
(bool)$this->render('{$HasPerm(\'ADMIN\')}'), (bool)$this->render('{$HasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result' 'Permissions template functions result correct result'
); );
$this->assertEquals( $this->assertEquals(
Permission::check("ADMIN"), Permission::check("ADMIN"),
(bool)$this->render('{$hasPerm(\'ADMIN\')}'), (bool)$this->render('{$hasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result' 'Permissions template functions result correct result'
); );
} }
public function testNonFieldCastingHelpersNotUsedInHasValue() public function testNonFieldCastingHelpersNotUsedInHasValue()
{ {
// check if Link without $ in front of variable // check if Link without $ in front of variable
$result = $this->render( $result = $this->render(
'A<% if Link %>$Link<% end_if %>B', 'A<% if Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject() new SSViewerTest\TestObject()
); );
$this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>'); $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>');
// check if Link with $ in front of variable // check if Link with $ in front of variable
$result = $this->render( $result = $this->render(
'A<% if $Link %>$Link<% end_if %>B', 'A<% if $Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject() new SSViewerTest\TestObject()
); );
$this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>'); $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>');
} }
public function testLocalFunctionsTakePriorityOverGlobals() public function testLocalFunctionsTakePriorityOverGlobals()
{ {
$data = new ArrayData( $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( array(
'Page' => new SSViewerTest\TestObject() 'Subocean' => new ArrayData(
)
);
//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( array(
'Subocean' => new ArrayData( 'Name' => 'Higher'
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')
) )
),
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( $result = $this->render(
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data $data
); );
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
$result = $this->render( $result = $this->render(
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data $data
); );
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
$result = $this->render('<% with Foo %>$Count<% end_with %>', $data); $result = $this->render('<% with Foo %>$Count<% end_with %>', $data);
$this->assertEquals("4", $result, "4 items in the DataObjectSet"); $this->assertEquals("4", $result, "4 items in the DataObjectSet");
$result = $this->render( $result = $this->render(
'<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>', . '<% end_if %><% end_loop %><% end_with %>',
$data $data
); );
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works"); $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works");
$result = $this->render( $result = $this->render(
'<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>', . '<% end_if %><% end_loop %><% end_with %>',
$data $data
); );
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works"); $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works");
} }
public function testObjectDotArguments() public function testObjectDotArguments()
{ {
$this->assertEquals( $this->assertEquals(
'[out:TestObject.methodWithOneArgument(one)] '[out:TestObject.methodWithOneArgument(one)]
[out:TestObject.methodWithTwoArguments(one,two)] [out:TestObject.methodWithTwoArguments(one,two)]
[out:TestMethod(Arg1,Arg2).Bar.Val] [out:TestMethod(Arg1,Arg2).Bar.Val]
[out:TestMethod(Arg1,Arg2).Bar] [out:TestMethod(Arg1,Arg2).Bar]
@ -558,8 +587,8 @@ SS;
[out:TestMethod(Arg1).Bar.Val] [out:TestMethod(Arg1).Bar.Val]
[out:TestMethod(Arg1).Bar] [out:TestMethod(Arg1).Bar]
[out:TestMethod(Arg1)]', [out:TestMethod(Arg1)]',
$this->render( $this->render(
'$TestObject.methodWithOneArgument(one) '$TestObject.methodWithOneArgument(one)
$TestObject.methodWithTwoArguments(one,two) $TestObject.methodWithTwoArguments(one,two)
$TestMethod(Arg1, Arg2).Bar.Val $TestMethod(Arg1, Arg2).Bar.Val
$TestMethod(Arg1, Arg2).Bar $TestMethod(Arg1, Arg2).Bar
@ -567,14 +596,14 @@ SS;
$TestMethod(Arg1).Bar.Val $TestMethod(Arg1).Bar.Val
$TestMethod(Arg1).Bar $TestMethod(Arg1).Bar
$TestMethod(Arg1)' $TestMethod(Arg1)'
) )
); );
} }
public function testEscapedArguments() public function testEscapedArguments()
{ {
$this->assertEquals( $this->assertEquals(
'[out:Foo(Arg1,Arg2).Bar.Val].Suffix '[out:Foo(Arg1,Arg2).Bar.Val].Suffix
[out:Foo(Arg1,Arg2).Val]_Suffix [out:Foo(Arg1,Arg2).Val]_Suffix
[out:Foo(Arg1,Arg2)]/Suffix [out:Foo(Arg1,Arg2)]/Suffix
[out:Foo(Arg1).Bar.Val]textSuffix [out:Foo(Arg1).Bar.Val]textSuffix
@ -583,8 +612,8 @@ SS;
[out:Foo.Bar.Val].Suffix [out:Foo.Bar.Val].Suffix
[out:Foo.Bar].Suffix [out:Foo.Bar].Suffix
[out:Foo].Suffix', [out:Foo].Suffix',
$this->render( $this->render(
'{$Foo(Arg1, Arg2).Bar.Val}.Suffix '{$Foo(Arg1, Arg2).Bar.Val}.Suffix
{$Foo(Arg1, Arg2).Val}_Suffix {$Foo(Arg1, Arg2).Val}_Suffix
{$Foo(Arg1, Arg2)}/Suffix {$Foo(Arg1, Arg2)}/Suffix
{$Foo(Arg1).Bar.Val}textSuffix {$Foo(Arg1).Bar.Val}textSuffix
@ -593,44 +622,44 @@ SS;
{$Foo.Bar.Val}.Suffix {$Foo.Bar.Val}.Suffix
{$Foo.Bar}.Suffix {$Foo.Bar}.Suffix
{$Foo}.Suffix' {$Foo}.Suffix'
) )
); );
} }
public function testLoopWhitespace() public function testLoopWhitespace()
{ {
$this->assertEquals( $this->assertEquals(
'before[out:SingleItem.Test]after 'before[out:SingleItem.Test]after
beforeTestafter', beforeTestafter',
$this->render( $this->render(
'before<% loop SingleItem %>$Test<% end_loop %>after 'before<% loop SingleItem %>$Test<% end_loop %>after
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 // 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 // This is a quirk that could be changed, but included in the test to make the current
// behaviour explicit // behaviour explicit
$this->assertEquals( $this->assertEquals(
'before 'before
[out:SingleItem.ItemOnItsOwnLine] [out:SingleItem.ItemOnItsOwnLine]
after', after',
$this->render( $this->render(
'before 'before
<% loop SingleItem %> <% loop SingleItem %>
$ItemOnItsOwnLine $ItemOnItsOwnLine
<% end_loop %> <% end_loop %>
after' after'
) )
); );
// The whitespace within the control tags is preserve in a loop // 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 // This is a quirk that could be changed, but included in the test to make the current
// behaviour explicit // behaviour explicit
$this->assertEquals( $this->assertEquals(
'before 'before
[out:Loop3.ItemOnItsOwnLine] [out:Loop3.ItemOnItsOwnLine]
@ -639,800 +668,800 @@ after'
[out:Loop3.ItemOnItsOwnLine] [out:Loop3.ItemOnItsOwnLine]
after', after',
$this->render( $this->render(
'before 'before
<% loop Loop3 %> <% loop Loop3 %>
$ItemOnItsOwnLine $ItemOnItsOwnLine
<% end_loop %> <% end_loop %>
after' after'
) )
); );
} }
public function testControls() public function testControls()
{ {
// Single item controls // Single item controls
$this->assertEquals( $this->assertEquals(
'a[out:Foo.Bar.Item]b 'a[out:Foo.Bar.Item]b
[out:Foo.Bar(Arg1).Item] [out:Foo.Bar(Arg1).Item]
[out:Foo(Arg1).Item] [out:Foo(Arg1).Item]
[out:Foo(Arg1,Arg2).Item] [out:Foo(Arg1,Arg2).Item]
[out:Foo(Arg1,Arg2,Arg3).Item]', [out:Foo(Arg1,Arg2,Arg3).Item]',
$this->render( $this->render(
'<% with Foo.Bar %>a{$Item}b<% end_with %> '<% with Foo.Bar %>a{$Item}b<% end_with %>
<% with Foo.Bar(Arg1) %>$Item<% end_with %> <% with Foo.Bar(Arg1) %>$Item<% end_with %>
<% with Foo(Arg1) %>$Item<% end_with %> <% with Foo(Arg1) %>$Item<% end_with %>
<% with Foo(Arg1, Arg2) %>$Item<% end_with %> <% with Foo(Arg1, Arg2) %>$Item<% end_with %>
<% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>' <% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>'
) )
); );
// Loop controls // Loop controls
$this->assertEquals( $this->assertEquals(
'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b', 'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
$this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>') $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
); );
$this->assertEquals( $this->assertEquals(
'[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]', '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
$this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>') $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
); );
$this->assertEquals( $this->assertEquals(
'[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]', '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
$this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>') $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
); );
$this->assertEquals( $this->assertEquals(
'[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]', '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
$this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>') $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
); );
$this->assertEquals( $this->assertEquals(
'[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]', '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
$this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>') $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
); );
} }
public function testIfBlocks() public function testIfBlocks()
{ {
// Basic test // Basic test
$this->assertEquals( $this->assertEquals(
'AC', 'AC',
$this->render('A<% if NotSet %>B$NotSet<% end_if %>C') $this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
); );
// Nested test // Nested test
$this->assertEquals( $this->assertEquals(
'AB1C', 'AB1C',
$this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C') $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
); );
// else_if // else_if
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D') $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D') $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ADE', 'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
); );
$this->assertEquals( $this->assertEquals(
'ADE', 'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
); );
// Dot syntax // Dot syntax
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D') $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D') $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
); );
// Params // Params
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D') $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ABD', 'ABD',
$this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D') $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
); );
// Negation // Negation
$this->assertEquals( $this->assertEquals(
'AC', 'AC',
$this->render('A<% if not IsSet %>B<% end_if %>C') $this->render('A<% if not IsSet %>B<% end_if %>C')
); );
$this->assertEquals( $this->assertEquals(
'ABC', 'ABC',
$this->render('A<% if not NotSet %>B<% end_if %>C') $this->render('A<% if not NotSet %>B<% end_if %>C')
); );
// Or // Or
$this->assertEquals( $this->assertEquals(
'ABD', 'ABD',
$this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D') $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D') $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D') $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D') $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D') $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
); );
// Negated Or // Negated Or
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ABD', 'ABD',
$this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ABD', 'ABD',
$this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D') $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
); );
// And // And
$this->assertEquals( $this->assertEquals(
'ABD', 'ABD',
$this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D') $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D') $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D') $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D') $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D') $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
); );
// Equality // Equality
$this->assertEquals( $this->assertEquals(
'ABC', 'ABC',
$this->render('A<% if RawVal == RawVal %>B<% end_if %>C') $this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
); );
$this->assertEquals( $this->assertEquals(
'ACD', 'ACD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D') $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'ABC', 'ABC',
$this->render('A<% if Right != Wrong %>B<% end_if %>C') $this->render('A<% if Right != Wrong %>B<% end_if %>C')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D') $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
); );
// test inequalities with simple numbers // 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('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('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('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('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('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('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, // empty else_if and else tags, if this would not be supported,
// the output would stop after A, thereby failing the assert // 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 IsSet %><% else %><% end_if %>D'));
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D') $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
); );
$this->assertEquals( $this->assertEquals(
'AD', 'AD',
$this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D') $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
); );
// Bare words with ending space // Bare words with ending space
$this->assertEquals( $this->assertEquals(
'ABC', 'ABC',
$this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C') $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
); );
// Else // Else
$this->assertEquals( $this->assertEquals(
'ADE', 'ADE',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E') $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
); );
// Empty if with else // Empty if with else
$this->assertEquals( $this->assertEquals(
'ABC', 'ABC',
$this->render('A<% if NotSet %><% else %>B<% end_if %>C') $this->render('A<% if NotSet %><% else %>B<% end_if %>C')
); );
} }
public function testBaseTagGeneration() public function testBaseTagGeneration()
{ {
// XHTML wil have a closed base tag // XHTML wil have a closed base tag
$tmpl1 = '<?xml version="1.0" encoding="UTF-8"?> $tmpl1 = '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
. ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body><p>test</p><body> <body><p>test</p><body>
</html>'; </html>';
$this->assertRegExp('/<head><base href=".*" \/><\/head>/', $this->render($tmpl1)); $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $this->render($tmpl1));
// HTML4 and 5 will only have it for IE // HTML4 and 5 will only have it for IE
$tmpl2 = '<!DOCTYPE html> $tmpl2 = '<!DOCTYPE html>
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body><p>test</p><body> <body><p>test</p><body>
</html>'; </html>';
$this->assertRegExp( $this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/', '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl2) $this->render($tmpl2)
); );
$tmpl3 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> $tmpl3 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body><p>test</p><body> <body><p>test</p><body>
</html>'; </html>';
$this->assertRegExp( $this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/', '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl3) $this->render($tmpl3)
); );
// Check that the content negotiator converts to the equally legal formats // Check that the content negotiator converts to the equally legal formats
$negotiator = new ContentNegotiator(); $negotiator = new ContentNegotiator();
$response = new HTTPResponse($this->render($tmpl1)); $response = new HTTPResponse($this->render($tmpl1));
$negotiator->html($response); $negotiator->html($response);
$this->assertRegExp( $this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/', '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$response->getBody() $response->getBody()
); );
$response = new HTTPResponse($this->render($tmpl1)); $response = new HTTPResponse($this->render($tmpl1));
$negotiator->xhtml($response); $negotiator->xhtml($response);
$this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody()); $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
} }
public function testIncludeWithArguments() public function testIncludeWithArguments()
{ {
$this->assertEquals( $this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments %>'), $this->render('<% include SSViewerTestIncludeWithArguments %>'),
'<p>[out:Arg1]</p><p>[out:Arg2]</p>' '<p>[out:Arg1]</p><p>[out:Arg2]</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'), $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
'<p>A</p><p>[out:Arg2]</p>' '<p>A</p><p>[out:Arg2]</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'), $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
'<p>A</p><p>B</p>' '<p>A</p><p>B</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'), $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
'<p>A Bare String</p><p>B Bare String</p>' '<p>A Bare String</p><p>B Bare String</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render( $this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>', '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
new ArrayData(array('B' => 'Bar')) new ArrayData(array('B' => 'Bar'))
), ),
'<p>A</p><p>Bar</p>' '<p>A</p><p>Bar</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render( $this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A" %>', '<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar')) new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
), ),
'<p>A</p><p>Bar</p>' '<p>A</p><p>Bar</p>'
); );
$this->assertEquals( $this->assertEquals(
$this->render( $this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>', '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
new ArrayData( new ArrayData(
array('Items' => new ArrayList( 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(
array( array(
'Item' => new ArrayData( new ArrayData(array('Title' => 'Foo')),
array( new ArrayData(array('Title' => 'Bar'))
'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'))
) )
); ),
'SomeArg - Foo - Bar - SomeArg'
);
$tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>'); $this->assertEquals(
$res = $tmpl->process($data); $this->render(
$this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments'); '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
} new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))
),
'A - B - A'
);
public function testNamespaceInclude() $this->assertEquals(
{ $this->render(
$data = new ArrayData([]); '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
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( array(
new ArrayData( 'Item' => new ArrayData(
array( array(
'Title' => 'A1', 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
'Children' => new ArrayList( )
array( ))
new ArrayData(array( 'Title' => 'A1 i', )), )
new ArrayData(array( 'Title' => 'A1 ii', )), ),
) '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); $result = $view->process($data);
// We don't care about whitespace // We don't care about whitespace
$rationalisedResult = trim(preg_replace('/\s+/', ' ', $result)); $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 = '') public function assertEqualIgnoringWhitespace($a, $b, $message = '')
{ {
$this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message); $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message);
} }
/** /**
* See {@link ViewableDataTest} for more extensive casting tests, * See {@link ViewableDataTest} for more extensive casting tests,
* this test just ensures that basic casting is correctly applied during template parsing. * this test just ensures that basic casting is correctly applied during template parsing.
*/ */
public function testCastingHelpers() public function testCastingHelpers()
{ {
$vd = new SSViewerTest\TestViewableData(); $vd = new SSViewerTest\TestViewableData();
$vd->TextValue = '<b>html</b>'; $vd->TextValue = '<b>html</b>';
$vd->HTMLValue = '<b>html</b>'; $vd->HTMLValue = '<b>html</b>';
$vd->UncastedValue = '<b>html</b>'; $vd->UncastedValue = '<b>html</b>';
// Value casted as "Text" // Value casted as "Text"
$this->assertEquals( $this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;', '&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue')->process($vd) $t = SSViewer::fromString('$TextValue')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'<b>html</b>', '<b>html</b>',
$t = SSViewer::fromString('$TextValue.RAW')->process($vd) $t = SSViewer::fromString('$TextValue.RAW')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;', '&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue.XML')->process($vd) $t = SSViewer::fromString('$TextValue.XML')->process($vd)
); );
// Value casted as "HTMLText" // Value casted as "HTMLText"
$this->assertEquals( $this->assertEquals(
'<b>html</b>', '<b>html</b>',
$t = SSViewer::fromString('$HTMLValue')->process($vd) $t = SSViewer::fromString('$HTMLValue')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'<b>html</b>', '<b>html</b>',
$t = SSViewer::fromString('$HTMLValue.RAW')->process($vd) $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;', '&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$HTMLValue.XML')->process($vd) $t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
); );
// Uncasted value (falls back to ViewableData::$default_cast="Text") // Uncasted value (falls back to ViewableData::$default_cast="Text")
$vd = new SSViewerTest\TestViewableData(); $vd = new SSViewerTest\TestViewableData();
$vd->UncastedValue = '<b>html</b>'; $vd->UncastedValue = '<b>html</b>';
$this->assertEquals( $this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;', '&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue')->process($vd) $t = SSViewer::fromString('$UncastedValue')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'<b>html</b>', '<b>html</b>',
$t = SSViewer::fromString('$UncastedValue.RAW')->process($vd) $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
); );
$this->assertEquals( $this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;', '&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue.XML')->process($vd) $t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
); );
} }
public function testSSViewerBasicIteratorSupport() public function testSSViewerBasicIteratorSupport()
{ {
$data = new ArrayData( $data = new ArrayData(
array(
'Set' => new ArrayList(
array( array(
'Set' => new ArrayList( new SSViewerTest\TestObject("1"),
array( new SSViewerTest\TestObject("2"),
new SSViewerTest\TestObject("1"), new SSViewerTest\TestObject("3"),
new SSViewerTest\TestObject("2"), new SSViewerTest\TestObject("4"),
new SSViewerTest\TestObject("3"), new SSViewerTest\TestObject("5"),
new SSViewerTest\TestObject("4"), new SSViewerTest\TestObject("6"),
new SSViewerTest\TestObject("5"), new SSViewerTest\TestObject("7"),
new SSViewerTest\TestObject("6"), new SSViewerTest\TestObject("8"),
new SSViewerTest\TestObject("7"), new SSViewerTest\TestObject("9"),
new SSViewerTest\TestObject("8"), new SSViewerTest\TestObject("10"),
new SSViewerTest\TestObject("9"),
new SSViewerTest\TestObject("10"),
)
) )
) )
); )
);
//base test //base test
$result = $this->render('<% loop Set %>$Number<% end_loop %>', $data); $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data);
$this->assertEquals("12345678910", $result, "Numbers rendered in order"); $this->assertEquals("12345678910", $result, "Numbers rendered in order");
//test First //test First
$result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("1", $result, "Only the first number is rendered"); $this->assertEquals("1", $result, "Only the first number is rendered");
//test Last //test Last
$result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("10", $result, "Only the last number is rendered"); $this->assertEquals("10", $result, "Only the last number is rendered");
//test Even //test Even
$result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("246810", $result, "Even numbers rendered in order"); $this->assertEquals("246810", $result, "Even numbers rendered in order");
//test Even with quotes //test Even with quotes
$result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("246810", $result, "Even numbers rendered in order"); $this->assertEquals("246810", $result, "Even numbers rendered in order");
//test Even without quotes //test Even without quotes
$result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("246810", $result, "Even numbers rendered in order"); $this->assertEquals("246810", $result, "Even numbers rendered in order");
//test Even with zero-based start index //test Even with zero-based start index
$result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order");
//test Odd //test Odd
$result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("13579", $result, "Odd numbers rendered in order"); $this->assertEquals("13579", $result, "Odd numbers rendered in order");
//test FirstLast //test FirstLast
$result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("1first10last", $result, "First and last numbers rendered in order");
//test Middle //test Middle
$result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals("23456789", $result, "Middle numbers rendered in order"); $this->assertEquals("23456789", $result, "Middle numbers rendered in order");
//test MiddleString //test MiddleString
$result = $this->render( $result = $this->render(
'<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>' '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
. '<% end_loop %>', . '<% end_loop %>',
$data $data
); );
$this->assertEquals( $this->assertEquals(
"2middle3middle4middle5middle6middle7middle8middle9middle", "2middle3middle4middle5middle6middle7middle8middle9middle",
$result, $result,
"Middle numbers rendered in order" "Middle numbers rendered in order"
); );
//test EvenOdd //test EvenOdd
$result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data); $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data);
$this->assertEquals( $this->assertEquals(
"oddevenoddevenoddevenoddevenoddeven", "oddevenoddevenoddevenoddevenoddeven",
$result, $result,
"Even and Odd is returned in sequence numbers rendered in order" "Even and Odd is returned in sequence numbers rendered in order"
); );
//test Pos //test Pos
$result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data); $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data);
$this->assertEquals("12345678910", $result, '$Pos is rendered in order'); $this->assertEquals("12345678910", $result, '$Pos is rendered in order');
//test Pos //test Pos
$result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data); $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data);
$this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order'); $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order');
//test FromEnd //test FromEnd
$result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data); $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data);
$this->assertEquals("10987654321", $result, '$FromEnd is rendered in order'); $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order');
//test FromEnd //test FromEnd
$result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data); $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data);
$this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order'); $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order');
//test Total //test Total
$result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data); $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data);
$this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned"); $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned");
//test Modulus //test Modulus
$result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data); $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"); $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order");
//test MultipleOf 3 //test MultipleOf 3
$result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned");
//test MultipleOf 4 //test MultipleOf 4
$result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned");
//test MultipleOf 5 //test MultipleOf 5
$result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned");
//test MultipleOf 10 //test MultipleOf 10
$result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data); $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"); $this->assertEquals("10", $result, "Only numbers that are multiples of 10 (with 1-based indexing) are returned");
//test MultipleOf 9 zero-based //test MultipleOf 9 zero-based
$result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data); $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals( $this->assertEquals(
"110", "110",
$result, $result,
"Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)" "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
); );
//test MultipleOf 11 //test MultipleOf 11
$result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data); $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"); $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 * 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 to run the loop tests on - three levels deep
$data = new ArrayData( $data = new ArrayData(
array(
'Name' => 'Top',
'Foo' => new ArrayData(
array( array(
'Name' => 'Top', 'Name' => 'Foo',
'Foo' => new ArrayData( 'Bar' => new ArrayData(
array( array(
'Name' => 'Foo', 'Name' => 'Bar',
'Bar' => new ArrayData( 'Baz' => new ArrayData(
array( array(
'Name' => 'Bar', 'Name' => 'Baz'
'Baz' => new ArrayData(
array(
'Name' => 'Baz'
)
),
'Qux' => new ArrayData(
array(
'Name' => 'Qux'
)
) )
),
'Qux' => new ArrayData(
array(
'Name' => 'Qux'
) )
) )
) )
) )
) )
); )
)
);
// Basic functionality // Basic functionality
$this->assertEquals( $this->assertEquals(
'BarFoo', 'BarFoo',
$this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data) $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data)
); );
// Two level with block, up refers to internally referenced Bar // Two level with block, up refers to internally referenced Bar
$this->assertEquals( $this->assertEquals(
'BarFoo', 'BarFoo',
$this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data) $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data)
); );
// Stepping up & back down the scope tree // Stepping up & back down the scope tree
$this->assertEquals( $this->assertEquals(
'BazBarQux', 'BazBarQux',
$this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data) $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data)
); );
// Using $Up in a with block // Using $Up in a with block
$this->assertEquals( $this->assertEquals(
'BazBarQux', 'BazBarQux',
$this->render( $this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>' '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>'
.'<% end_with %>', .'<% end_with %>',
$data $data
) )
); );
// Stepping up & back down the scope tree with with blocks // Stepping up & back down the scope tree with with blocks
$this->assertEquals( $this->assertEquals(
'BazBarQuxBarBaz', 'BazBarQuxBarBaz',
$this->render( $this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>' '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>'
. '{$Name}<% end_with %>{$Name}<% end_with %>', . '{$Name}<% end_with %>{$Name}<% end_with %>',
$data $data
) )
); );
// Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
$this->assertEquals( $this->assertEquals(
'Foo', 'Foo',
$this->render( $this->render(
'<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>' '<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>'
. '<% end_with %>', . '<% end_with %>',
$data $data
) )
); );
// Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo // Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
$this->assertEquals( $this->assertEquals(
'Foo', 'Foo',
$this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data) $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 * 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 to run the loop tests on - one sequence of three items, each with a subitem
$data = new ArrayData( $data = new ArrayData(
array(
'Name' => 'Top',
'Foo' => new ArrayList(
array( array(
'Name' => 'Top', new ArrayData(
'Foo' => new ArrayList(
array( array(
new ArrayData( 'Name' => '1',
'Sub' => new ArrayData(
array( array(
'Name' => '1', 'Name' => 'Bar'
'Sub' => new ArrayData(
array(
'Name' => 'Bar'
)
) )
) )
), )
new ArrayData( ),
new ArrayData(
array(
'Name' => '2',
'Sub' => new ArrayData(
array( array(
'Name' => '2', 'Name' => 'Baz'
'Sub' => new ArrayData(
array(
'Name' => 'Baz'
)
) )
) )
), )
new ArrayData( ),
new ArrayData(
array(
'Name' => '3',
'Sub' => new ArrayData(
array( array(
'Name' => '3', 'Name' => 'Qux'
'Sub' => new ArrayData(
array(
'Name' => 'Qux'
)
)
) )
) )
) )
) )
) )
); )
)
);
// Make sure inside a loop, $Up refers to the current item of the loop // Make sure inside a loop, $Up refers to the current item of the loop
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
'111 222 333', '111 222 333',
$this->render( $this->render(
'<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>', '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
$data $data
) )
); );
// Make sure inside a loop, looping over $Up uses a separate iterator, // Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator // and doesn't interfere with the original iterator
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
'1Bar123Bar1 2Baz123Baz2 3Qux123Qux3', '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
$this->render( $this->render(
'<% loop $Foo %> '<% loop $Foo %>
$Name $Name
<% with $Sub %> <% with $Sub %>
$Name $Name
@ -1441,16 +1470,16 @@ after'
<% end_with %> <% end_with %>
$Name $Name
<% end_loop %>', <% end_loop %>',
$data $data
) )
); );
// Make sure inside a loop, looping over $Up uses a separate iterator, // Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator or local lookups // and doesn't interfere with the original iterator or local lookups
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
'1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3', '1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3',
$this->render( $this->render(
'<% loop $Foo %> '<% loop $Foo %>
$Name $Name
<% with $Sub %> <% with $Sub %>
{$Name}{$Up.Name} {$Name}{$Up.Name}
@ -1459,183 +1488,183 @@ after'
<% end_with %> <% end_with %>
$Name $Name
<% end_loop %>', <% end_loop %>',
$data $data
) )
); );
} }
/** /**
* Test that nested loops restore the loop variables correctly when pushing and popping states * 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 // Data to run the loop tests on - one sequence of three items, one with child elements
// (of a different size to the main sequence) // (of a different size to the main sequence)
$data = new ArrayData( $data = new ArrayData(
array(
'Foo' => new ArrayList(
array( array(
'Foo' => new ArrayList( new ArrayData(
array( array(
new ArrayData( 'Name' => '1',
'Children' => new ArrayList(
array( array(
'Name' => '1', new ArrayData(
'Children' => new ArrayList(
array( array(
new ArrayData( 'Name' => 'a'
array( )
'Name' => 'a' ),
) new ArrayData(
), array(
new ArrayData( 'Name' => 'b'
array(
'Name' => 'b'
)
),
) )
), ),
) )
), ),
new ArrayData( )
array( ),
'Name' => '2', new ArrayData(
'Children' => new ArrayList(), array(
) 'Name' => '2',
), 'Children' => new ArrayList(),
new ArrayData( )
array( ),
'Name' => '3', new ArrayData(
'Children' => new ArrayList(), array(
) 'Name' => '3',
), 'Children' => new ArrayList(),
) )
), ),
) )
); ),
)
);
// Make sure that including a loop inside a loop will not destroy the internal count of // Make sure that including a loop inside a loop will not destroy the internal count of
// items, checked by using "Last" // items, checked by using "Last"
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
'1ab23last', '1ab23last',
$this->render( $this->render(
'<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>' '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
. '<% end_loop %>', . '<% end_loop %>',
$data $data
) )
); );
} }
public function testLayout() public function testLayout()
{ {
$this->useTestTheme( $this->useTestTheme(
__DIR__.'/SSViewerTest', __DIR__.'/SSViewerTest',
'layouttest', 'layouttest',
function () { function () {
$template = new SSViewer(array('Page')); $template = new SSViewer(array('Page'));
$this->assertEquals("Foo\n\n", $template->process(new ArrayData(array()))); $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
$template = new SSViewer(array('Shortcodes', 'Page')); $template = new SSViewer(array('Shortcodes', 'Page'));
$this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array()))); $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
} }
); );
} }
/** /**
* @covers \SilverStripe\View\SSViewer::get_templates_by_class() * @covers \SilverStripe\View\SSViewer::get_templates_by_class()
*/ */
public function testGetTemplatesByClass() public function testGetTemplatesByClass()
{ {
$this->useTestTheme( $this->useTestTheme(
__DIR__ . '/SSViewerTest', __DIR__ . '/SSViewerTest',
'layouttest', 'layouttest',
function () { function () {
// Test passing a string // Test passing a string
$templates = SSViewer::get_templates_by_class( $templates = SSViewer::get_templates_by_class(
SSViewerTestModelController::class,
'',
Controller::class
);
$this->assertEquals(
[
SSViewerTestModelController::class, SSViewerTestModelController::class,
'',
Controller::class
);
$this->assertEquals(
[ [
'type' => 'Includes',
SSViewerTestModelController::class, 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, SSViewerTestModel::class,
'Controller', Controller::class,
DataObject::class
);
$this->assertEquals(
[ [
SSViewerTestModelController::class, 'type' => 'Includes',
[ Controller::class,
'type' => 'Includes',
SSViewerTestModelController::class,
],
DataObject::class . 'Controller',
[
'type' => 'Includes',
DataObject::class . 'Controller',
],
], ],
$templates ],
); $templates
);
// Let's throw something random in there. // Test to ensure we're stopping at the base class.
$this->setExpectedException('InvalidArgumentException'); $templates = SSViewer::get_templates_by_class(
SSViewer::get_templates_by_class(array()); SSViewerTestModelController::class,
} '',
); SSViewerTestModelController::class
} );
$this->assertEquals(
[
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
SSViewerTestModel::class,
],
$templates
);
public function testRewriteHashlinks() // Make sure we can search templates by suffix.
{ $templates = SSViewer::get_templates_by_class(
SSViewer::config()->update('rewrite_hash_links', true); 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'; // Let's throw something random in there.
$_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""'; $this->setExpectedException('InvalidArgumentException');
SSViewer::get_templates_by_class(array());
}
);
}
// Emulate SSViewer::process() public function testRewriteHashlinks()
// Note that leading double slashes have been rewritten to prevent these being mis-interepreted {
// as protocol-less absolute urls SSViewer::config()->update('rewrite_hash_links', true);
$base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
$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. // Emulate SSViewer::process()
file_put_contents( // Note that leading double slashes have been rewritten to prevent these being mis-interepreted
$tmplFile, // as protocol-less absolute urls
'<!DOCTYPE html> $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,
'<!DOCTYPE html>
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body> <body>
@ -1646,53 +1675,53 @@ after'
<svg><use xlink:href="#sprite"></use></svg> <svg><use xlink:href="#sprite"></use></svg>
<body> <body>
</html>' </html>'
); );
$tmpl = new SSViewer($tmplFile); $tmpl = new SSViewer($tmplFile);
$obj = new ViewableData(); $obj = new ViewableData();
$obj->InsertedLink = DBField::create_field( $obj->InsertedLink = DBField::create_field(
'HTMLFragment', 'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>' '<a class="inserted" href="#anchor">InsertedLink</a>'
); );
$obj->ExternalInsertedLink = DBField::create_field( $obj->ExternalInsertedLink = DBField::create_field(
'HTMLFragment', 'HTMLFragment',
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>' '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
); );
$result = $tmpl->process($obj); $result = $tmpl->process($obj);
$this->assertContains( $this->assertContains(
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>', '<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
$result $result
); );
$this->assertContains( $this->assertContains(
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>', '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
$result $result
); );
$this->assertContains( $this->assertContains(
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>', '<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result $result
); );
$this->assertContains( $this->assertContains(
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>', '<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result $result
); );
$this->assertContains( $this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>', '<svg><use xlink:href="#sprite"></use></svg>',
$result, $result,
'SSTemplateParser should only rewrite anchor hrefs' 'SSTemplateParser should only rewrite anchor hrefs'
); );
unlink($tmplFile); unlink($tmplFile);
} }
public function testRewriteHashlinksInPhpMode() public function testRewriteHashlinksInPhpMode()
{ {
SSViewer::config()->update('rewrite_hash_links', 'php'); 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. // Note: SSViewer_FromString doesn't rewrite hash links.
file_put_contents( file_put_contents(
$tmplFile, $tmplFile,
'<!DOCTYPE html> '<!DOCTYPE html>
<html> <html>
<head><% base_tag %></head> <head><% base_tag %></head>
<body> <body>
@ -1701,236 +1730,236 @@ after'
<svg><use xlink:href="#sprite"></use></svg> <svg><use xlink:href="#sprite"></use></svg>
<body> <body>
</html>' </html>'
); );
$tmpl = new SSViewer($tmplFile); $tmpl = new SSViewer($tmplFile);
$obj = new ViewableData(); $obj = new ViewableData();
$obj->InsertedLink = DBField::create_field( $obj->InsertedLink = DBField::create_field(
'HTMLFragment', 'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>' '<a class="inserted" href="#anchor">InsertedLink</a>'
); );
$result = $tmpl->process($obj); $result = $tmpl->process($obj);
$code = <<<'EOC' $code = <<<'EOC'
<a class="inserted" href="<?php echo \SilverStripe\Core\Convert::raw2att(preg_replace("/^(\/)+/", "/", $_SERVER['REQUEST_URI'])); ?>#anchor">InsertedLink</a> <a class="inserted" href="<?php echo \SilverStripe\Core\Convert::raw2att(preg_replace("/^(\/)+/", "/", $_SERVER['REQUEST_URI'])); ?>#anchor">InsertedLink</a>
EOC; EOC;
$this->assertContains($code, $result); $this->assertContains($code, $result);
// TODO Fix inline links in PHP mode // TODO Fix inline links in PHP mode
// $this->assertContains( // $this->assertContains(
// '<a class="inline" href="<?php echo str_replace(', // '<a class="inline" href="<?php echo str_replace(',
// $result // $result
// ); // );
$this->assertContains( $this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>', '<svg><use xlink:href="#sprite"></use></svg>',
$result, $result,
'SSTemplateParser should only rewrite anchor hrefs' 'SSTemplateParser should only rewrite anchor hrefs'
); );
unlink($tmplFile); unlink($tmplFile);
} }
public function testRenderWithSourceFileComments() public function testRenderWithSourceFileComments()
{ {
Director::set_environment_type('dev'); Director::set_environment_type('dev');
SSViewer::config()->update('source_file_comments', true); SSViewer::config()->update('source_file_comments', true);
$i = __DIR__ . '/SSViewerTest/templates/Includes'; $i = __DIR__ . '/SSViewerTest/templates/Includes';
$f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments'; $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
$templates = array( $templates = array(
array( array(
'name' => 'SSViewerTestCommentsFullSource', 'name' => 'SSViewerTestCommentsFullSource',
'expected' => "" 'expected' => ""
. "<!doctype html>" . "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSource.ss -->" . "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
. "<html>" . "<html>"
. "\t<head></head>" . "\t<head></head>"
. "\t<body></body>" . "\t<body></body>"
. "</html>" . "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->", . "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
), ),
array( array(
'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype', 'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
'expected' => "" 'expected' => ""
. "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML " . "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
. "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">" . "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
. "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->" . "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
. "<html>" . "<html>"
. "\t<head></head>" . "\t<head></head>"
. "\t<body></body>" . "\t<body></body>"
. "</html>" . "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->", . "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
), ),
array( array(
'name' => 'SSViewerTestCommentsFullSourceNoDoctype', 'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
'expected' => "" 'expected' => ""
. "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->" . "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
. "\t<head></head>" . "\t<head></head>"
. "\t<body></body>" . "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>", . "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
), ),
array( array(
'name' => 'SSViewerTestCommentsFullSourceIfIE', 'name' => 'SSViewerTestCommentsFullSourceIfIE',
'expected' => "" 'expected' => ""
. "<!doctype html>" . "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->" . "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->" . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->" . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->" . "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
. "\t<head></head>" . "\t<head></head>"
. "\t<body></body>" . "\t<body></body>"
. "</html>" . "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->", . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
), ),
array( array(
'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype', 'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
'expected' => "" 'expected' => ""
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->" . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->" . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'>" . "<!--[if !IE]><!--> <html class='no-ie'>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->" . "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
. " <!--<![endif]-->" . " <!--<![endif]-->"
. "\t<head></head>" . "\t<head></head>"
. "\t<body></body>" . "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>", . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
), ),
array( array(
'name' => 'SSViewerTestCommentsPartialSource', 'name' => 'SSViewerTestCommentsPartialSource',
'expected' => 'expected' =>
"<!-- template $f/SSViewerTestCommentsPartialSource.ss -->" "<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
. "<div class='typography'></div>" . "<div class='typography'></div>"
. "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->", . "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
), ),
array( array(
'name' => 'SSViewerTestCommentsWithInclude', 'name' => 'SSViewerTestCommentsWithInclude',
'expected' => 'expected' =>
"<!-- template $f/SSViewerTestCommentsWithInclude.ss -->" "<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
. "<div class='typography'>" . "<div class='typography'>"
. "<!-- include 'SSViewerTestCommentsInclude' -->" . "<!-- include 'SSViewerTestCommentsInclude' -->"
. "<!-- template $i/SSViewerTestCommentsInclude.ss -->" . "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
. "Included" . "Included"
. "<!-- end template $i/SSViewerTestCommentsInclude.ss -->" . "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
. "<!-- end include 'SSViewerTestCommentsInclude' -->" . "<!-- end include 'SSViewerTestCommentsInclude' -->"
. "</div>" . "</div>"
. "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->", . "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
), ),
); );
foreach ($templates as $template) { foreach ($templates as $template) {
$this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']); $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);
} }
}
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() public function testLoopIteratorIterator()
{ {
$list = new PaginatedList(new ArrayList()); $list = new PaginatedList(new ArrayList());
$viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName<br /><% end_loop %>'); $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName<br /><% end_loop %>');
$result = $viewer->process(new ArrayData(array('List' => $list))); $result = $viewer->process(new ArrayData(array('List' => $list)));
$this->assertEquals($result, ''); $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')); $template = new SSViewer(array('SSViewerTestProcess'));
$basePath = $this->getCurrentRelativePath() . '/SSViewerTest';
$backend = Injector::inst()->create(Requirements_Backend::class); Requirements::set_suffix_requirements(false);
$backend->setCombinedFilesEnabled(false);
$backend->combineFiles( $this->assertEquals(
'RequirementsTest_ab.css', 1,
array( substr_count(
$basePath . '/css/RequirementsTest_a.css', $template->process(array()),
$basePath . '/css/RequirementsTest_b.css' "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
) )
); );
} else {
Requirements::set_backend($backend); $this->markTestSkipped(
'Requirement will always fail if the framework dir is not '.
$this->assertEquals(1, substr_count($template->process(array()), "a.css")); 'named \'framework\', since templates require hard coded paths'
$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),
),
)
); );
}
}
$tests = array( public function testCallsWithArguments()
'$Level.output(1)' => '1-1', {
'$Nest.Level.output($Set.First.Number)' => '2-1', $data = new ArrayData(
'<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1', array(
'<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1', 'Set' => new ArrayList(
'<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5', array(
'<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5', new SSViewerTest\TestObject("1"),
'<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1', new SSViewerTest\TestObject("2"),
'<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5', new SSViewerTest\TestObject("3"),
'<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi', new SSViewerTest\TestObject("4"),
'<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0', new SSViewerTest\TestObject("5"),
'<% with $Nest %> )
),
'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 %> <% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
<% end_with %>' => '1-hi', <% end_with %>' => '1-hi',
'<% with $Nest %> '<% with $Nest %>
<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %> <% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
<% end_with %>' => '!0!1!2!3!4', <% end_with %>' => '!0!1!2!3!4',
); );
foreach ($tests as $template => $expected) { foreach ($tests as $template => $expected) {
$this->assertEquals($expected, trim($this->render($template, $data))); $this->assertEquals($expected, trim($this->render($template, $data)));
}
} }
}
public function testRepeatedCallsAreCached() public function testRepeatedCallsAreCached()
{ {
$data = new SSViewerTest\CacheTestData(); $data = new SSViewerTest\CacheTestData();
$template = ' $template = '
<% if $TestWithCall %> <% if $TestWithCall %>
<% with $TestWithCall %> <% with $TestWithCall %>
{$Message} {$Message}
@ -1939,89 +1968,89 @@ EOC;
{$TestWithCall.Message} {$TestWithCall.Message}
<% end_if %>'; <% end_if %>';
$this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data))); $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data)));
$this->assertEquals( $this->assertEquals(
1, 1,
$data->testWithCalls, $data->testWithCalls,
'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached' 'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
); );
$data = new SSViewerTest\CacheTestData(); $data = new SSViewerTest\CacheTestData();
$template = ' $template = '
<% if $TestLoopCall %> <% if $TestLoopCall %>
<% loop $TestLoopCall %> <% loop $TestLoopCall %>
{$Message} {$Message}
<% end_loop %> <% end_loop %>
<% end_if %>'; <% end_if %>';
$this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data))); $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data)));
$this->assertEquals( $this->assertEquals(
1, 1,
$data->testLoopCalls, $data->testLoopCalls,
'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached' 'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
); );
} }
public function testClosedBlockExtension() public function testClosedBlockExtension()
{ {
$count = 0; $count = 0;
$parser = new SSTemplateParser(); $parser = new SSTemplateParser();
$parser->addClosedBlock( $parser->addClosedBlock(
'test', 'test',
function ($res) use (&$count) { function ($res) use (&$count) {
$count++; $count++;
} }
); );
$template = new SSViewer_FromString("<% test %><% end_test %>", $parser); $template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
$template->process(new SSViewerTest\TestFixture()); $template->process(new SSViewerTest\TestFixture());
$this->assertEquals(1, $count); $this->assertEquals(1, $count);
} }
public function testOpenBlockExtension() public function testOpenBlockExtension()
{ {
$count = 0; $count = 0;
$parser = new SSTemplateParser(); $parser = new SSTemplateParser();
$parser->addOpenBlock( $parser->addOpenBlock(
'test', 'test',
function ($res) use (&$count) { function ($res) use (&$count) {
$count++; $count++;
} }
); );
$template = new SSViewer_FromString("<% test %>", $parser); $template = new SSViewer_FromString("<% test %>", $parser);
$template->process(new SSViewerTest\TestFixture()); $template->process(new SSViewerTest\TestFixture());
$this->assertEquals(1, $count); $this->assertEquals(1, $count);
} }
/** /**
* Tests if caching for SSViewer_FromString is working * Tests if caching for SSViewer_FromString is working
*/ */
public function testFromStringCaching() public function testFromStringCaching()
{ {
$content = 'Test content'; $content = 'Test content';
$cacheFile = TEMP_FOLDER . '/.cache.' . sha1($content); $cacheFile = TEMP_FOLDER . '/.cache.' . sha1($content);
if (file_exists($cacheFile)) { 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); 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);
}
} }