Merge pull request #6902 from open-sausages/pulls/4/remove-jsmin

Pulls/4/remove jsmin
This commit is contained in:
Ingo Schommer 2017-05-11 11:54:14 +12:00 committed by GitHub
commit 36214343f0
8 changed files with 872 additions and 1124 deletions

View File

@ -169,13 +169,13 @@ replaced. For instance, the below will set a new set of dependencies to write to
---
Name: myrequirements
---
Requirements:
SilverStripe\View\Requirements:
disable_flush_combined: true
Requirements_Backend:
SilverStripe\View\Requirements_Backend:
combine_in_dev: true
combine_hash_querystring: true
default_combined_files_folder: 'combined'
Injector:
SilverStripe\Core\Injector\Injector:
MySiteAdapter:
class: 'SilverStripe\Filesystem\Flysystem\AssetAdapter'
constructor:
@ -192,7 +192,7 @@ replaced. For instance, the below will set a new set of dependencies to write to
class: SilverStripe\Filesystem\Storage\FlysystemGeneratedAssetHandler
properties:
Filesystem: '%$MySiteBackend'
Requirements_Backend:
SilverStripe\View\Requirements_Backend:
properties:
AssetHandler: '%$MySiteAssetHandler'
@ -248,6 +248,49 @@ $scripts = array(
Requirements::combine_files('scripts.js', $scripts, array('async' => true, 'defer' => true));
```
### Minification of CSS and JS files
You can minify combined Javascript and CSS files at runtime using an implementation of the
`SilverStripe\View\Requirements_Minifier` interface.
```php
namespace MyProject;
use SilverStripe\View\Requirements_Minifier;
class MyMinifier implements Requirements_Minifier
{
/**
* Minify the given content
*
* @param string $content
* @param string $type Either js or css
* @param string $filename Name of file to display in case of error
* @return string minified content
*/
public function minify ($content, $type, $fileName)
{
// Minify $content;
return $minifiedContent;
}
}
```
Then, inject this service in `Requirements_Backend`.
```yaml
SilverStripe\Core\Injector\Injector:
SilverStripe\View\Requirements_Backend:
properties:
Minifier: %$MyProject\MyMinifier
```
<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

View File

@ -1,30 +0,0 @@
<?php
namespace SilverStripe\View;
use Exception;
use JSMin;
class JSMinifier implements Requirements_Minifier
{
public function minify($content, $type, $filename)
{
// Non-js files aren't minified
if ($type !== 'js') {
return $content . "\n";
}
// Combine JS
try {
require_once('jsmin/jsmin.php');
increase_time_limit_to();
$content = JSMin::minify($content);
} catch (Exception $e) {
$message = $e->getMessage();
user_error("Failed to minify {$filename}, exception: {$message}", E_USER_WARNING);
} finally {
return $content . ";\n";
}
}
}

View File

@ -3,6 +3,7 @@
namespace SilverStripe\View;
use InvalidArgumentException;
use Exception;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Storage\GeneratedAssetHandler;
use SilverStripe\Control\Director;
@ -10,7 +11,6 @@ use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\SapphireTest;
@ -121,11 +121,11 @@ class Requirements_Backend
protected $combinedFiles = array();
/**
* Use the JSMin library to minify any javascript file passed to {@link combine_files()}.
* Use the injected minification service to minify any javascript file passed to {@link combine_files()}.
*
* @var bool
*/
protected $minifyCombinedJSFiles = true;
protected $minifyCombinedFiles = false;
/**
* Whether or not file headers should be written when combining files
@ -191,6 +191,11 @@ class Requirements_Backend
*/
protected $assetHandler = null;
/**
* @var Requirements_Minifier
*/
protected $minifier = null;
/**
* Gets the backend storage for generated files
*
@ -211,6 +216,27 @@ class Requirements_Backend
$this->assetHandler = $handler;
}
/**
* Gets the minification service for this backend
*
* @deprecated 4.0..5.0
* @return Requirements_Minifier
*/
public function getMinifier()
{
return $this->minifier;
}
/**
* Set a new minification service for this backend
*
* @param Requirements_Minifier $minifier
*/
public function setMinifier(Requirements_Minifier $minifier = null)
{
$this->minifier = $minifier;
}
/**
* Enable or disable the combination of CSS and JavaScript files
*
@ -340,24 +366,24 @@ class Requirements_Backend
}
/**
* Check if minify js files should be combined
* Check if minify files should be combined
*
* @return bool
*/
public function getMinifyCombinedJSFiles()
public function getMinifyCombinedFiles()
{
return $this->minifyCombinedJSFiles;
return $this->minifyCombinedFiles;
}
/**
* Set if combined js files should be minified
* Set if combined files should be minified
*
* @param bool $minify
* @return $this
*/
public function setMinifyCombinedJSFiles($minify)
public function setMinifyCombinedFiles($minify)
{
$this->minifyCombinedJSFiles = $minify;
$this->minifyCombinedFiles = $minify;
return $this;
}
@ -1278,7 +1304,20 @@ class Requirements_Backend
$combinedFileID = File::join_paths($this->getCombinedFilesFolder(), $combinedFile);
// Send file combination request to the backend, with an optional callback to perform regeneration
$minify = $this->getMinifyCombinedJSFiles();
$minify = $this->getMinifyCombinedFiles();
if ($minify && !$this->minifier) {
throw new Exception(
sprintf(
'Cannot minify files without a minification service defined.
Set %s::minifyCombinedFiles to false, or inject a %s service on
%s.properties.minifier',
__CLASS__,
Requirements_Minifier::class,
__CLASS__
)
);
}
$combinedURL = $this
->getAssetHandler()
->getContentURL(
@ -1287,12 +1326,11 @@ class Requirements_Backend
// Physically combine all file content
$combinedData = '';
$base = Director::baseFolder() . '/';
$minifier = Injector::inst()->get('SilverStripe\\View\\Requirements_Minifier');
foreach ($fileList as $file) {
$fileContent = file_get_contents($base . $file);
// Use configured minifier
if ($minify) {
$fileContent = $minifier->minify($fileContent, $type, $file);
$fileContent = $this->minifier->minify($fileContent, $type, $file);
}
if ($this->writeHeaderComment) {

View File

@ -4,6 +4,8 @@ namespace SilverStripe\View;
/**
* Provides an abstract interface for minifying content
*
* @deprecated 4.0..5.0
*/
interface Requirements_Minifier
{

View File

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

View File

@ -8,7 +8,6 @@ use SilverStripe\Control\ContentNegotiator;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DataObject;
@ -20,6 +19,7 @@ use SilverStripe\Security\SecurityToken;
use SilverStripe\Security\Permission;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements_Backend;
use SilverStripe\View\Requirements_Minifier;
use SilverStripe\View\SSViewer;
use SilverStripe\View\Requirements;
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
@ -28,7 +28,6 @@ use SilverStripe\View\ViewableData;
use SilverStripe\View\SSViewer_FromString;
use SilverStripe\View\SSTemplateParser;
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
use JSMin;
use Exception;
class SSViewerTest extends SapphireTest
@ -70,9 +69,9 @@ class SSViewerTest extends SapphireTest
{
SSViewer::config()->update('theme', 'mytheme');
$this->assertEquals(
'mytheme',
SSViewer::config()->uninherited('theme'),
'Current theme is the default - user has not defined one'
'mytheme',
SSViewer::config()->uninherited('theme'),
'Current theme is the default - user has not defined one'
);
}
@ -82,9 +81,9 @@ class SSViewerTest extends SapphireTest
public function testTemplateWithoutHeadRenders()
{
$data = new ArrayData(
array(
'Var' => 'var value'
)
array(
'Var' => 'var value'
)
);
$result = $data->renderWith("SSViewerTestPartialTemplate");
@ -95,12 +94,12 @@ class SSViewerTest extends SapphireTest
{
$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',
'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');
@ -108,12 +107,12 @@ class SSViewerTest extends SapphireTest
// 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',
'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');
@ -123,32 +122,32 @@ class SSViewerTest extends SapphireTest
public function testIncludeTruthyness()
{
$data = new ArrayData(
array(
'Title' => 'TruthyTest',
'Items' => new ArrayList(
array(
'Title' => 'TruthyTest',
'Items' => new ArrayList(
array(
new ArrayData(array('Title' => 'Item 1')),
new ArrayData(array('Title' => '')),
new ArrayData(array('Title' => true)),
new ArrayData(array('Title' => false)),
new ArrayData(array('Title' => null)),
new ArrayData(array('Title' => 0)),
new ArrayData(array('Title' => 7))
)
)
new ArrayData(array('Title' => 'Item 1')),
new ArrayData(array('Title' => '')),
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');
// We should not end up with empty values appearing as empty
$expected = array(
'Item 1 _ Item 1 - First-ODD top:Item 1',
'Untitled - EVEN top:',
'1 _ 1 - ODD top:1',
'Untitled - EVEN top:',
'Untitled - ODD top:',
'Untitled - EVEN top:0',
'7 _ 7 - Last-ODD top:7'
'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);
}
@ -156,19 +155,19 @@ class SSViewerTest extends SapphireTest
private function getScopeInheritanceTestData()
{
return new ArrayData(
array(
'Title' => 'TopTitleValue',
'Items' => new ArrayList(
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'))
)
)
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'))
)
)
)
);
}
@ -176,8 +175,8 @@ class SSViewerTest extends SapphireTest
{
foreach ($expected as $expectedStr) {
$this->assertTrue(
(boolean) preg_match("/{$expectedStr}/", $result),
"Didn't find '{$expectedStr}' in:\n{$result}"
(boolean) preg_match("/{$expectedStr}/", $result),
"Didn't find '{$expectedStr}' in:\n{$result}"
);
}
}
@ -202,7 +201,7 @@ class SSViewerTest extends SapphireTest
public function testRequirements()
{
$requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css"))
->getMock();
->getMock();
$jsFile = FRAMEWORK_DIR . '/tests/forms/a.js';
$cssFile = FRAMEWORK_DIR . '/tests/forms/a.js';
@ -212,7 +211,7 @@ class SSViewerTest extends SapphireTest
$origReq = Requirements::backend();
Requirements::set_backend($requirements);
$template = $this->render(
"<% require javascript($jsFile) %>
"<% require javascript($jsFile) %>
<% require css($cssFile) %>"
);
Requirements::set_backend($origReq);
@ -230,15 +229,6 @@ class SSViewerTest extends SapphireTest
$jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile);
$testBackend->combineFiles('testRequirementsCombine.js', array($jsFile));
// first make sure that our test js file causes an exception to be thrown
try {
include_once 'jsmin/jsmin.php';
JSMin::minify($jsFileContents);
$this->fail('JSMin did not throw exception on minify bad file: ');
} catch (Exception $e) {
// exception thrown... good
}
// secondly, make sure that requirements is generated, even though minification failed
$testBackend->processCombinedFiles();
$js = array_keys($testBackend->getJavascript());
@ -253,12 +243,50 @@ class SSViewerTest extends SapphireTest
$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
);
$testBackend->setMinifier($mockMinifier);
$testBackend->combineFiles('testRequirementsMinified.js', array($testFile));
$testBackend->processCombinedFiles();
$testBackend->setMinifyCombinedFiles(false);
$mockMinifier->expects($this->never())
->method('minify');
$testBackend->processCombinedFiles();
$this->setExpectedExceptionRegExp(
Exception::class,
'/minification service/'
);
$testBackend->setMinifyCombinedFiles(true);
$testBackend->setMinifier(null);
$testBackend->processCombinedFiles();
}
public function testComments()
{
$output = $this->render(
<<<SS
<<<SS
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content
<%-- Alone multi
line comment --%>
@ -300,9 +328,9 @@ SS;
$this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection');
$this->assertEquals(
'{\\[out:Test]}',
$this->render('{\\\\$Test}'),
'Escapes before injections are correctly unescaped'
'{\\[out:Test]}',
$this->render('{\\\\$Test}'),
'Escapes before injections are correctly unescaped'
);
}
@ -319,12 +347,12 @@ SS;
$this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments'));
$this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")'));
$this->assertEquals(
'zFoo:Bar:Bazz',
$this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
'zFoo:Bar:Bazz',
$this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
);
$this->assertEquals(
'zreferencez',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
'zreferencez',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
);
}
@ -334,101 +362,101 @@ SS;
$this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped'));
$this->assertEquals(
'z<div></div>z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
'z<div></div>z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
);
$this->assertEquals(
'z&lt;div&gt;&lt;/div&gt;z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
'z&lt;div&gt;&lt;/div&gt;z',
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
);
}
public function testCoreGlobalVariableCalls()
{
$this->assertEquals(
Director::absoluteBaseURL(),
$this->render('{$absoluteBaseURL}'),
'Director::absoluteBaseURL can be called from within template'
Director::absoluteBaseURL(),
$this->render('{$absoluteBaseURL}'),
'Director::absoluteBaseURL can be called from within template'
);
$this->assertEquals(
Director::absoluteBaseURL(),
$this->render('{$AbsoluteBaseURL}'),
'Upper-case %AbsoluteBaseURL can be called from within template'
Director::absoluteBaseURL(),
$this->render('{$AbsoluteBaseURL}'),
'Upper-case %AbsoluteBaseURL can be called from within template'
);
$this->assertEquals(
Director::is_ajax(),
$this->render('{$isAjax}'),
'All variations of is_ajax result in the correct call'
Director::is_ajax(),
$this->render('{$isAjax}'),
'All variations of is_ajax result in the correct call'
);
$this->assertEquals(
Director::is_ajax(),
$this->render('{$IsAjax}'),
'All variations of is_ajax result in the correct call'
Director::is_ajax(),
$this->render('{$IsAjax}'),
'All variations of is_ajax result in the correct call'
);
$this->assertEquals(
Director::is_ajax(),
$this->render('{$is_ajax}'),
'All variations of is_ajax result in the correct call'
Director::is_ajax(),
$this->render('{$is_ajax}'),
'All variations of is_ajax result in the correct call'
);
$this->assertEquals(
Director::is_ajax(),
$this->render('{$Is_ajax}'),
'All variations of is_ajax result in the correct call'
Director::is_ajax(),
$this->render('{$Is_ajax}'),
'All variations of is_ajax result in the correct call'
);
$this->assertEquals(
i18n::get_locale(),
$this->render('{$i18nLocale}'),
'i18n template functions result correct result'
i18n::get_locale(),
$this->render('{$i18nLocale}'),
'i18n template functions result correct result'
);
$this->assertEquals(
i18n::get_locale(),
$this->render('{$get_locale}'),
'i18n template functions result correct result'
i18n::get_locale(),
$this->render('{$get_locale}'),
'i18n template functions result correct result'
);
$this->assertEquals(
(string)Member::currentUser(),
$this->render('{$CurrentMember}'),
'Member template functions result correct result'
(string)Member::currentUser(),
$this->render('{$CurrentMember}'),
'Member template functions result correct result'
);
$this->assertEquals(
(string)Member::currentUser(),
$this->render('{$CurrentUser}'),
'Member template functions result correct result'
(string)Member::currentUser(),
$this->render('{$CurrentUser}'),
'Member template functions result correct result'
);
$this->assertEquals(
(string)Member::currentUser(),
$this->render('{$currentMember}'),
'Member template functions result correct result'
(string)Member::currentUser(),
$this->render('{$currentMember}'),
'Member template functions result correct result'
);
$this->assertEquals(
(string)Member::currentUser(),
$this->render('{$currentUser}'),
'Member template functions result correct result'
(string)Member::currentUser(),
$this->render('{$currentUser}'),
'Member template functions result correct result'
);
$this->assertEquals(
SecurityToken::getSecurityID(),
$this->render('{$getSecurityID}'),
'SecurityToken template functions result correct result'
SecurityToken::getSecurityID(),
$this->render('{$getSecurityID}'),
'SecurityToken template functions result correct result'
);
$this->assertEquals(
SecurityToken::getSecurityID(),
$this->render('{$SecurityID}'),
'SecurityToken template functions result correct result'
SecurityToken::getSecurityID(),
$this->render('{$SecurityID}'),
'SecurityToken template functions result correct result'
);
$this->assertEquals(
Permission::check("ADMIN"),
(bool)$this->render('{$HasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result'
Permission::check("ADMIN"),
(bool)$this->render('{$HasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result'
);
$this->assertEquals(
Permission::check("ADMIN"),
(bool)$this->render('{$hasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result'
Permission::check("ADMIN"),
(bool)$this->render('{$hasPerm(\'ADMIN\')}'),
'Permissions template functions result correct result'
);
}
@ -436,15 +464,15 @@ SS;
{
// check if Link without $ in front of variable
$result = $this->render(
'A<% if Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject()
'A<% if Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject()
);
$this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>');
// check if Link with $ in front of variable
$result = $this->render(
'A<% if $Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject()
'A<% if $Link %>$Link<% end_if %>B',
new SSViewerTest\TestObject()
);
$this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>');
}
@ -452,15 +480,15 @@ SS;
public function testLocalFunctionsTakePriorityOverGlobals()
{
$data = new ArrayData(
array(
'Page' => new SSViewerTest\TestObject()
)
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
'<% 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");
@ -475,9 +503,9 @@ SS;
//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"
"testLocalFunctionPriorityCalled",
$result,
"Local Object's public function called. Did not return the actual baseURL of the current site"
);
}
@ -485,47 +513,47 @@ SS;
{
// Data to run the loop tests on - one sequence of three items, each with a subitem
$data = new ArrayData(
array(
'Foo' => new ArrayList(
array(
'Foo' => new ArrayList(
'Subocean' => new ArrayData(
array(
'Subocean' => new ArrayData(
array(
'Name' => 'Higher'
)
),
new ArrayData(
array(
'Sub' => new ArrayData(
array(
'Name' => 'SubKid1'
)
)
)
),
new ArrayData(
array(
'Sub' => new ArrayData(
array(
'Name' => 'SubKid2'
)
)
)
),
new SSViewerTest\TestObject('Number6')
'Name' => 'Higher'
)
),
new ArrayData(
array(
'Sub' => new ArrayData(
array(
'Name' => 'SubKid1'
)
)
)
),
new ArrayData(
array(
'Sub' => new ArrayData(
array(
'Name' => 'SubKid2'
)
)
)
),
new SSViewerTest\TestObject('Number6')
)
)
)
)
);
$result = $this->render(
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data
);
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
$result = $this->render(
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data
'<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
$data
);
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
@ -533,16 +561,16 @@ SS;
$this->assertEquals("4", $result, "4 items in the DataObjectSet");
$result = $this->render(
'<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>',
$data
'<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>',
$data
);
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works");
$result = $this->render(
'<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>',
$data
'<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
. '<% end_if %><% end_loop %><% end_with %>',
$data
);
$this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works");
}
@ -550,7 +578,7 @@ SS;
public function testObjectDotArguments()
{
$this->assertEquals(
'[out:TestObject.methodWithOneArgument(one)]
'[out:TestObject.methodWithOneArgument(one)]
[out:TestObject.methodWithTwoArguments(one,two)]
[out:TestMethod(Arg1,Arg2).Bar.Val]
[out:TestMethod(Arg1,Arg2).Bar]
@ -558,8 +586,8 @@ SS;
[out:TestMethod(Arg1).Bar.Val]
[out:TestMethod(Arg1).Bar]
[out:TestMethod(Arg1)]',
$this->render(
'$TestObject.methodWithOneArgument(one)
$this->render(
'$TestObject.methodWithOneArgument(one)
$TestObject.methodWithTwoArguments(one,two)
$TestMethod(Arg1, Arg2).Bar.Val
$TestMethod(Arg1, Arg2).Bar
@ -567,14 +595,14 @@ SS;
$TestMethod(Arg1).Bar.Val
$TestMethod(Arg1).Bar
$TestMethod(Arg1)'
)
)
);
}
public function testEscapedArguments()
{
$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)]/Suffix
[out:Foo(Arg1).Bar.Val]textSuffix
@ -583,8 +611,8 @@ SS;
[out:Foo.Bar.Val].Suffix
[out:Foo.Bar].Suffix
[out:Foo].Suffix',
$this->render(
'{$Foo(Arg1, Arg2).Bar.Val}.Suffix
$this->render(
'{$Foo(Arg1, Arg2).Bar.Val}.Suffix
{$Foo(Arg1, Arg2).Val}_Suffix
{$Foo(Arg1, Arg2)}/Suffix
{$Foo(Arg1).Bar.Val}textSuffix
@ -593,44 +621,44 @@ SS;
{$Foo.Bar.Val}.Suffix
{$Foo.Bar}.Suffix
{$Foo}.Suffix'
)
)
);
}
public function testLoopWhitespace()
{
$this->assertEquals(
'before[out:SingleItem.Test]after
'before[out:SingleItem.Test]after
beforeTestafter',
$this->render(
'before<% loop SingleItem %>$Test<% end_loop %>after
$this->render(
'before<% loop SingleItem %>$Test<% end_loop %>after
before<% loop SingleItem %>Test<% end_loop %>after'
)
)
);
// The control tags are removed from the output, but no whitespace
// This is a quirk that could be changed, but included in the test to make the current
// behaviour explicit
$this->assertEquals(
'before
'before
[out:SingleItem.ItemOnItsOwnLine]
after',
$this->render(
'before
$this->render(
'before
<% loop SingleItem %>
$ItemOnItsOwnLine
<% end_loop %>
after'
)
)
);
// The whitespace within the control tags is preserve in a loop
// This is a quirk that could be changed, but included in the test to make the current
// behaviour explicit
$this->assertEquals(
'before
'before
[out:Loop3.ItemOnItsOwnLine]
@ -639,13 +667,13 @@ after'
[out:Loop3.ItemOnItsOwnLine]
after',
$this->render(
'before
$this->render(
'before
<% loop Loop3 %>
$ItemOnItsOwnLine
<% end_loop %>
after'
)
)
);
}
@ -653,44 +681,44 @@ after'
{
// Single item controls
$this->assertEquals(
'a[out:Foo.Bar.Item]b
'a[out:Foo.Bar.Item]b
[out:Foo.Bar(Arg1).Item]
[out:Foo(Arg1).Item]
[out:Foo(Arg1,Arg2).Item]
[out:Foo(Arg1,Arg2,Arg3).Item]',
$this->render(
'<% with Foo.Bar %>a{$Item}b<% end_with %>
$this->render(
'<% with Foo.Bar %>a{$Item}b<% end_with %>
<% with Foo.Bar(Arg1) %>$Item<% end_with %>
<% with Foo(Arg1) %>$Item<% end_with %>
<% with Foo(Arg1, Arg2) %>$Item<% end_with %>
<% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>'
)
)
);
// Loop controls
$this->assertEquals(
'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
$this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
$this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
);
$this->assertEquals(
'[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
$this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
'[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
$this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
);
$this->assertEquals(
'[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
$this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
'[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
$this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
);
$this->assertEquals(
'[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
$this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
'[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
$this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
);
$this->assertEquals(
'[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
$this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
'[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
$this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
);
}
@ -698,139 +726,139 @@ after'
{
// Basic test
$this->assertEquals(
'AC',
$this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
'AC',
$this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
);
// Nested test
$this->assertEquals(
'AB1C',
$this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
'AB1C',
$this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
);
// else_if
$this->assertEquals(
'ACD',
$this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
'AD',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
);
$this->assertEquals(
'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
);
$this->assertEquals(
'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
'ADE',
$this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
);
// Dot syntax
$this->assertEquals(
'ACD',
$this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
);
$this->assertEquals(
'ACD',
$this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
);
// Params
$this->assertEquals(
'ACD',
$this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
'ACD',
$this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
);
$this->assertEquals(
'ABD',
$this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
'ABD',
$this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
);
// Negation
$this->assertEquals(
'AC',
$this->render('A<% if not IsSet %>B<% end_if %>C')
'AC',
$this->render('A<% if not IsSet %>B<% end_if %>C')
);
$this->assertEquals(
'ABC',
$this->render('A<% if not NotSet %>B<% end_if %>C')
'ABC',
$this->render('A<% if not NotSet %>B<% end_if %>C')
);
// Or
$this->assertEquals(
'ABD',
$this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
'ABD',
$this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
);
$this->assertEquals(
'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
);
$this->assertEquals(
'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
'AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
);
// Negated Or
$this->assertEquals(
'ACD',
$this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
'ACD',
$this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
);
$this->assertEquals(
'ABD',
$this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
'ABD',
$this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
);
$this->assertEquals(
'ABD',
$this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
'ABD',
$this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
);
// And
$this->assertEquals(
'ABD',
$this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
'ABD',
$this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
);
$this->assertEquals(
'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
);
$this->assertEquals(
'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
'ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
'AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
);
// Equality
$this->assertEquals(
'ABC',
$this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
'ABC',
$this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
);
$this->assertEquals(
'ACD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
'ACD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
);
$this->assertEquals(
'ABC',
$this->render('A<% if Right != Wrong %>B<% end_if %>C')
'ABC',
$this->render('A<% if Right != Wrong %>B<% end_if %>C')
);
$this->assertEquals(
'AD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
'AD',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
);
// test inequalities with simple numbers
@ -853,30 +881,30 @@ after'
// the output would stop after A, thereby failing the assert
$this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D'));
$this->assertEquals(
'AD',
$this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
'AD',
$this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
);
$this->assertEquals(
'AD',
$this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
'AD',
$this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
);
// Bare words with ending space
$this->assertEquals(
'ABC',
$this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
'ABC',
$this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
);
// Else
$this->assertEquals(
'ADE',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
'ADE',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
);
// Empty if with else
$this->assertEquals(
'ABC',
$this->render('A<% if NotSet %><% else %>B<% end_if %>C')
'ABC',
$this->render('A<% if NotSet %><% else %>B<% end_if %>C')
);
}
@ -885,7 +913,7 @@ after'
// XHTML wil have a closed base tag
$tmpl1 = '<?xml version="1.0" encoding="UTF-8"?>
<!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>
<head><% base_tag %></head>
<body><p>test</p><body>
@ -899,8 +927,8 @@ after'
<body><p>test</p><body>
</html>';
$this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl2)
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl2)
);
@ -910,8 +938,8 @@ after'
<body><p>test</p><body>
</html>';
$this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl3)
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$this->render($tmpl3)
);
// Check that the content negotiator converts to the equally legal formats
@ -920,8 +948,8 @@ after'
$response = new HTTPResponse($this->render($tmpl1));
$negotiator->html($response);
$this->assertRegExp(
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$response->getBody()
'/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
$response->getBody()
);
$response = new HTTPResponse($this->render($tmpl1));
@ -932,103 +960,103 @@ after'
public function testIncludeWithArguments()
{
$this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments %>'),
'<p>[out:Arg1]</p><p>[out:Arg2]</p>'
$this->render('<% include SSViewerTestIncludeWithArguments %>'),
'<p>[out:Arg1]</p><p>[out:Arg2]</p>'
);
$this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
'<p>A</p><p>[out:Arg2]</p>'
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
'<p>A</p><p>[out:Arg2]</p>'
);
$this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
'<p>A</p><p>B</p>'
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
'<p>A</p><p>B</p>'
);
$this->assertEquals(
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
'<p>A Bare String</p><p>B Bare String</p>'
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
'<p>A Bare String</p><p>B Bare String</p>'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
new ArrayData(array('B' => 'Bar'))
),
'<p>A</p><p>Bar</p>'
$this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
new ArrayData(array('B' => 'Bar'))
),
'<p>A</p><p>Bar</p>'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
),
'<p>A</p><p>Bar</p>'
$this->render(
'<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
),
'<p>A</p><p>Bar</p>'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
new ArrayData(
array('Items' => new ArrayList(
array(
new ArrayData(array('Title' => 'Foo')),
new ArrayData(array('Title' => 'Bar'))
)
))
)
),
'SomeArg - Foo - Bar - SomeArg'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))
),
'A - B - A'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
new ArrayData(
$this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
new ArrayData(
array('Items' => new ArrayList(
array(
'Item' => new ArrayData(
array(
'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
)
))
)
),
'A - B - C - B - A'
new ArrayData(array('Title' => 'Foo')),
new ArrayData(array('Title' => 'Bar'))
)
))
)
),
'SomeArg - Foo - Bar - SomeArg'
);
$this->assertEquals(
$this->render(
'<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
new ArrayData(
$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(
'Item' => new ArrayData(
array(
'Item' => new ArrayData(
array(
'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
)
))
)
),
'A - A - A'
'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(
'Nested' => new ArrayData(
array(
'Object' => new ArrayData(array('Key' => 'A'))
)
),
'Object' => new ArrayData(array('Key' => 'B'))
'Object' => new ArrayData(array('Key' => 'A'))
)
),
'Object' => new ArrayData(array('Key' => 'B'))
)
);
$tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>');
@ -1041,15 +1069,15 @@ after'
$data = new ArrayData([]);
$this->assertEquals(
"tests:( NamespaceInclude\n )",
$this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data),
'Backslashes work for namespace references in includes'
"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'
"tests:( NamespaceInclude\n )",
$this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data),
'Forward slashes work for namespace references in includes'
);
}
@ -1059,26 +1087,26 @@ after'
$view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude'));
$data = new ArrayData(
array(
'Title' => 'A',
'Children' => new ArrayList(
array(
'Title' => 'A',
'Children' => new ArrayList(
new ArrayData(
array(
new ArrayData(
'Title' => 'A1',
'Children' => new ArrayList(
array(
'Title' => 'A1',
'Children' => new ArrayList(
array(
new ArrayData(array( 'Title' => 'A1 i', )),
new ArrayData(array( 'Title' => 'A1 ii', )),
)
),
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);
@ -1106,68 +1134,68 @@ after'
// Value casted as "Text"
$this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue')->process($vd)
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue')->process($vd)
);
$this->assertEquals(
'<b>html</b>',
$t = SSViewer::fromString('$TextValue.RAW')->process($vd)
'<b>html</b>',
$t = SSViewer::fromString('$TextValue.RAW')->process($vd)
);
$this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue.XML')->process($vd)
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$TextValue.XML')->process($vd)
);
// Value casted as "HTMLText"
$this->assertEquals(
'<b>html</b>',
$t = SSViewer::fromString('$HTMLValue')->process($vd)
'<b>html</b>',
$t = SSViewer::fromString('$HTMLValue')->process($vd)
);
$this->assertEquals(
'<b>html</b>',
$t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
'<b>html</b>',
$t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
);
$this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
);
// Uncasted value (falls back to ViewableData::$default_cast="Text")
$vd = new SSViewerTest\TestViewableData();
$vd->UncastedValue = '<b>html</b>';
$this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue')->process($vd)
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue')->process($vd)
);
$this->assertEquals(
'<b>html</b>',
$t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
'<b>html</b>',
$t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
);
$this->assertEquals(
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
'&lt;b&gt;html&lt;/b&gt;',
$t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
);
}
public function testSSViewerBasicIteratorSupport()
{
$data = new ArrayData(
array(
'Set' => new ArrayList(
array(
'Set' => new ArrayList(
array(
new SSViewerTest\TestObject("1"),
new SSViewerTest\TestObject("2"),
new SSViewerTest\TestObject("3"),
new SSViewerTest\TestObject("4"),
new SSViewerTest\TestObject("5"),
new SSViewerTest\TestObject("6"),
new SSViewerTest\TestObject("7"),
new SSViewerTest\TestObject("8"),
new SSViewerTest\TestObject("9"),
new SSViewerTest\TestObject("10"),
)
)
new SSViewerTest\TestObject("1"),
new SSViewerTest\TestObject("2"),
new SSViewerTest\TestObject("3"),
new SSViewerTest\TestObject("4"),
new SSViewerTest\TestObject("5"),
new SSViewerTest\TestObject("6"),
new SSViewerTest\TestObject("7"),
new SSViewerTest\TestObject("8"),
new SSViewerTest\TestObject("9"),
new SSViewerTest\TestObject("10"),
)
)
)
);
//base test
@ -1212,22 +1240,22 @@ after'
//test MiddleString
$result = $this->render(
'<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
. '<% end_loop %>',
$data
'<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
. '<% end_loop %>',
$data
);
$this->assertEquals(
"2middle3middle4middle5middle6middle7middle8middle9middle",
$result,
"Middle numbers rendered in order"
"2middle3middle4middle5middle6middle7middle8middle9middle",
$result,
"Middle numbers rendered in order"
);
//test EvenOdd
$result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data);
$this->assertEquals(
"oddevenoddevenoddevenoddevenoddeven",
$result,
"Even and Odd is returned in sequence numbers rendered in order"
"oddevenoddevenoddevenoddevenoddeven",
$result,
"Even and Odd is returned in sequence numbers rendered in order"
);
//test Pos
@ -1273,9 +1301,9 @@ after'
//test MultipleOf 9 zero-based
$result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data);
$this->assertEquals(
"110",
$result,
"Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
"110",
$result,
"Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
);
//test MultipleOf 11
@ -1291,83 +1319,83 @@ after'
// Data to run the loop tests on - three levels deep
$data = new ArrayData(
array(
'Name' => 'Top',
'Foo' => new ArrayData(
array(
'Name' => 'Top',
'Foo' => new ArrayData(
'Name' => 'Foo',
'Bar' => new ArrayData(
array(
'Name' => 'Foo',
'Bar' => new ArrayData(
'Name' => 'Bar',
'Baz' => new ArrayData(
array(
'Name' => 'Bar',
'Baz' => new ArrayData(
array(
'Name' => 'Baz'
)
),
'Qux' => new ArrayData(
array(
'Name' => 'Qux'
)
'Name' => 'Baz'
)
),
'Qux' => new ArrayData(
array(
'Name' => 'Qux'
)
)
)
)
)
)
)
);
// Basic functionality
$this->assertEquals(
'BarFoo',
$this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data)
'BarFoo',
$this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data)
);
// Two level with block, up refers to internally referenced Bar
$this->assertEquals(
'BarFoo',
$this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data)
'BarFoo',
$this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data)
);
// Stepping up & back down the scope tree
$this->assertEquals(
'BazBarQux',
$this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data)
'BazBarQux',
$this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data)
);
// Using $Up in a with block
$this->assertEquals(
'BazBarQux',
$this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>'
.'<% end_with %>',
$data
)
'BazBarQux',
$this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>'
.'<% end_with %>',
$data
)
);
// Stepping up & back down the scope tree with with blocks
$this->assertEquals(
'BazBarQuxBarBaz',
$this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>'
. '{$Name}<% end_with %>{$Name}<% end_with %>',
$data
)
'BazBarQuxBarBaz',
$this->render(
'<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>'
. '{$Name}<% end_with %>{$Name}<% end_with %>',
$data
)
);
// Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
$this->assertEquals(
'Foo',
$this->render(
'<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>'
. '<% end_with %>',
$data
)
'Foo',
$this->render(
'<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>'
. '<% end_with %>',
$data
)
);
// Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
$this->assertEquals(
'Foo',
$this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data)
'Foo',
$this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data)
);
}
@ -1379,60 +1407,60 @@ after'
// Data to run the loop tests on - one sequence of three items, each with a subitem
$data = new ArrayData(
array(
'Name' => 'Top',
'Foo' => new ArrayList(
array(
'Name' => 'Top',
'Foo' => new ArrayList(
new ArrayData(
array(
new ArrayData(
'Name' => '1',
'Sub' => new ArrayData(
array(
'Name' => '1',
'Sub' => new ArrayData(
array(
'Name' => 'Bar'
)
'Name' => 'Bar'
)
)
),
new ArrayData(
)
)
),
new ArrayData(
array(
'Name' => '2',
'Sub' => new ArrayData(
array(
'Name' => '2',
'Sub' => new ArrayData(
array(
'Name' => 'Baz'
)
'Name' => 'Baz'
)
)
),
new ArrayData(
)
)
),
new ArrayData(
array(
'Name' => '3',
'Sub' => new ArrayData(
array(
'Name' => '3',
'Sub' => new ArrayData(
array(
'Name' => 'Qux'
)
)
'Name' => 'Qux'
)
)
)
)
)
)
)
);
// Make sure inside a loop, $Up refers to the current item of the loop
$this->assertEqualIgnoringWhitespace(
'111 222 333',
$this->render(
'<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
$data
)
'111 222 333',
$this->render(
'<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
$data
)
);
// Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator
$this->assertEqualIgnoringWhitespace(
'1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
$this->render(
'<% loop $Foo %>
'1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
$this->render(
'<% loop $Foo %>
$Name
<% with $Sub %>
$Name
@ -1441,16 +1469,16 @@ after'
<% end_with %>
$Name
<% end_loop %>',
$data
)
$data
)
);
// Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator or local lookups
$this->assertEqualIgnoringWhitespace(
'1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3',
$this->render(
'<% loop $Foo %>
'1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3',
$this->render(
'<% loop $Foo %>
$Name
<% with $Sub %>
{$Name}{$Up.Name}
@ -1459,8 +1487,8 @@ after'
<% end_with %>
$Name
<% end_loop %>',
$data
)
$data
)
);
}
@ -1473,69 +1501,69 @@ after'
// Data to run the loop tests on - one sequence of three items, one with child elements
// (of a different size to the main sequence)
$data = new ArrayData(
array(
'Foo' => new ArrayList(
array(
'Foo' => new ArrayList(
new ArrayData(
array(
new ArrayData(
'Name' => '1',
'Children' => new ArrayList(
array(
'Name' => '1',
'Children' => new ArrayList(
new ArrayData(
array(
new ArrayData(
array(
'Name' => 'a'
)
),
new ArrayData(
array(
'Name' => 'b'
)
),
'Name' => 'a'
)
),
new ArrayData(
array(
'Name' => 'b'
)
),
)
),
new ArrayData(
array(
'Name' => '2',
'Children' => new ArrayList(),
)
),
new ArrayData(
array(
'Name' => '3',
'Children' => new ArrayList(),
)
),
)
),
new ArrayData(
array(
'Name' => '2',
'Children' => new ArrayList(),
)
),
new ArrayData(
array(
'Name' => '3',
'Children' => new ArrayList(),
)
),
)
),
)
);
// Make sure that including a loop inside a loop will not destroy the internal count of
// items, checked by using "Last"
$this->assertEqualIgnoringWhitespace(
'1ab23last',
$this->render(
'<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
. '<% end_loop %>',
$data
)
'1ab23last',
$this->render(
'<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
. '<% end_loop %>',
$data
)
);
}
public function testLayout()
{
$this->useTestTheme(
__DIR__.'/SSViewerTest',
'layouttest',
function () {
$template = new SSViewer(array('Page'));
$this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
__DIR__.'/SSViewerTest',
'layouttest',
function () {
$template = new SSViewer(array('Page'));
$this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
$template = new SSViewer(array('Shortcodes', 'Page'));
$this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
}
$template = new SSViewer(array('Shortcodes', 'Page'));
$this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
}
);
}
@ -1545,76 +1573,76 @@ after'
public function testGetTemplatesByClass()
{
$this->useTestTheme(
__DIR__ . '/SSViewerTest',
'layouttest',
function () {
// Test passing a string
$templates = SSViewer::get_templates_by_class(
__DIR__ . '/SSViewerTest',
'layouttest',
function () {
// Test passing a string
$templates = SSViewer::get_templates_by_class(
SSViewerTestModelController::class,
'',
Controller::class
);
$this->assertEquals(
[
SSViewerTestModelController::class,
'',
Controller::class
);
$this->assertEquals(
[
'type' => 'Includes',
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
SSViewerTestModel::class,
Controller::class,
[
'type' => 'Includes',
Controller::class,
],
],
$templates
);
// Test to ensure we're stopping at the base class.
$templates = SSViewer::get_templates_by_class(
SSViewerTestModelController::class,
'',
SSViewerTestModelController::class
);
$this->assertEquals(
[
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
SSViewerTestModel::class,
],
$templates
);
// Make sure we can search templates by suffix.
$templates = SSViewer::get_templates_by_class(
SSViewerTestModel::class,
'Controller',
DataObject::class
);
$this->assertEquals(
Controller::class,
[
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
DataObject::class . 'Controller',
[
'type' => 'Includes',
DataObject::class . 'Controller',
],
'type' => 'Includes',
Controller::class,
],
$templates
);
],
$templates
);
// Let's throw something random in there.
$this->setExpectedException('InvalidArgumentException');
SSViewer::get_templates_by_class(array());
}
// Test to ensure we're stopping at the base class.
$templates = SSViewer::get_templates_by_class(
SSViewerTestModelController::class,
'',
SSViewerTestModelController::class
);
$this->assertEquals(
[
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
SSViewerTestModel::class,
],
$templates
);
// Make sure we can search templates by suffix.
$templates = SSViewer::get_templates_by_class(
SSViewerTestModel::class,
'Controller',
DataObject::class
);
$this->assertEquals(
[
SSViewerTestModelController::class,
[
'type' => 'Includes',
SSViewerTestModelController::class,
],
DataObject::class . 'Controller',
[
'type' => 'Includes',
DataObject::class . 'Controller',
],
],
$templates
);
// Let's throw something random in there.
$this->setExpectedException('InvalidArgumentException');
SSViewer::get_templates_by_class(array());
}
);
}
@ -1634,8 +1662,8 @@ after'
// Note: SSViewer_FromString doesn't rewrite hash links.
file_put_contents(
$tmplFile,
'<!DOCTYPE html>
$tmplFile,
'<!DOCTYPE html>
<html>
<head><% base_tag %></head>
<body>
@ -1650,34 +1678,34 @@ after'
$tmpl = new SSViewer($tmplFile);
$obj = new ViewableData();
$obj->InsertedLink = DBField::create_field(
'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>'
'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>'
);
$obj->ExternalInsertedLink = DBField::create_field(
'HTMLFragment',
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
'HTMLFragment',
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
);
$result = $tmpl->process($obj);
$this->assertContains(
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
$result
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
$result
);
$this->assertContains(
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
$result
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
$result
);
$this->assertContains(
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
$result
);
$this->assertContains(
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
$result
);
$this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'SSTemplateParser should only rewrite anchor hrefs'
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'SSTemplateParser should only rewrite anchor hrefs'
);
unlink($tmplFile);
@ -1691,8 +1719,8 @@ after'
// Note: SSViewer_FromString doesn't rewrite hash links.
file_put_contents(
$tmplFile,
'<!DOCTYPE html>
$tmplFile,
'<!DOCTYPE html>
<html>
<head><% base_tag %></head>
<body>
@ -1705,8 +1733,8 @@ after'
$tmpl = new SSViewer($tmplFile);
$obj = new ViewableData();
$obj->InsertedLink = DBField::create_field(
'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>'
'HTMLFragment',
'<a class="inserted" href="#anchor">InsertedLink</a>'
);
$result = $tmpl->process($obj);
@ -1720,9 +1748,9 @@ EOC;
// $result
// );
$this->assertContains(
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'SSTemplateParser should only rewrite anchor hrefs'
'<svg><use xlink:href="#sprite"></use></svg>',
$result,
'SSTemplateParser should only rewrite anchor hrefs'
);
unlink($tmplFile);
@ -1735,82 +1763,82 @@ EOC;
$i = __DIR__ . '/SSViewerTest/templates/Includes';
$f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
$templates = array(
array(
'name' => 'SSViewerTestCommentsFullSource',
'expected' => ""
. "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
. "<html>"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
'expected' => ""
. "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
. "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
. "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
. "<html>"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
'expected' => ""
. "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
. "\t<head></head>"
. "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
),
array(
'name' => 'SSViewerTestCommentsFullSourceIfIE',
'expected' => ""
. "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
'expected' => ""
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
. " <!--<![endif]-->"
. "\t<head></head>"
. "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
),
array(
'name' => 'SSViewerTestCommentsPartialSource',
'expected' =>
"<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
. "<div class='typography'></div>"
. "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
),
array(
'name' => 'SSViewerTestCommentsWithInclude',
'expected' =>
"<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
. "<div class='typography'>"
. "<!-- include 'SSViewerTestCommentsInclude' -->"
. "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
. "Included"
. "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
. "<!-- end include 'SSViewerTestCommentsInclude' -->"
. "</div>"
. "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSource',
'expected' => ""
. "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
. "<html>"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
'expected' => ""
. "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
. "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
. "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
. "<html>"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
'expected' => ""
. "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
. "\t<head></head>"
. "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
),
array(
'name' => 'SSViewerTestCommentsFullSourceIfIE',
'expected' => ""
. "<!doctype html>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
. "\t<head></head>"
. "\t<body></body>"
. "</html>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
),
array(
'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
'expected' => ""
. "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
. "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
. "<!--[if !IE]><!--> <html class='no-ie'>"
. "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
. " <!--<![endif]-->"
. "\t<head></head>"
. "\t<body></body>"
. "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
),
array(
'name' => 'SSViewerTestCommentsPartialSource',
'expected' =>
"<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
. "<div class='typography'></div>"
. "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
),
array(
'name' => 'SSViewerTestCommentsWithInclude',
'expected' =>
"<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
. "<div class='typography'>"
. "<!-- include 'SSViewerTestCommentsInclude' -->"
. "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
. "Included"
. "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
. "<!-- end include 'SSViewerTestCommentsInclude' -->"
. "</div>"
. "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
),
);
foreach ($templates as $template) {
$this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']);
@ -1842,11 +1870,11 @@ EOC;
$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'
)
'RequirementsTest_ab.css',
array(
$basePath . '/css/RequirementsTest_a.css',
$basePath . '/css/RequirementsTest_b.css'
)
);
Requirements::set_backend($backend);
@ -1869,16 +1897,16 @@ EOC;
Requirements::set_suffix_requirements(false);
$this->assertEquals(
1,
substr_count(
$template->process(array()),
"tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
)
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'
'Requirement will always fail if the framework dir is not '.
'named \'framework\', since templates require hard coded paths'
);
}
}
@ -1886,38 +1914,38 @@ EOC;
public function testCallsWithArguments()
{
$data = new ArrayData(
array(
'Set' => new ArrayList(
array(
'Set' => new ArrayList(
array(
new SSViewerTest\TestObject("1"),
new SSViewerTest\TestObject("2"),
new SSViewerTest\TestObject("3"),
new SSViewerTest\TestObject("4"),
new SSViewerTest\TestObject("5"),
)
),
'Level' => new SSViewerTest\LevelTestData(1),
'Nest' => array(
'Level' => new SSViewerTest\LevelTestData(2),
),
new SSViewerTest\TestObject("1"),
new SSViewerTest\TestObject("2"),
new SSViewerTest\TestObject("3"),
new SSViewerTest\TestObject("4"),
new SSViewerTest\TestObject("5"),
)
),
'Level' => new SSViewerTest\LevelTestData(1),
'Nest' => array(
'Level' => new SSViewerTest\LevelTestData(2),
),
)
);
$tests = array(
'$Level.output(1)' => '1-1',
'$Nest.Level.output($Set.First.Number)' => '2-1',
'<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
'<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
'<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
'<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
'<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
'<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
'<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
'<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
'<% with $Nest %>
'$Level.output(1)' => '1-1',
'$Nest.Level.output($Set.First.Number)' => '2-1',
'<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
'<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
'<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
'<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
'<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
'<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
'<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
'<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
'<% with $Nest %>
<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
<% end_with %>' => '1-hi',
'<% with $Nest %>
'<% with $Nest %>
<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
<% end_with %>' => '!0!1!2!3!4',
);
@ -1941,9 +1969,9 @@ EOC;
$this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data)));
$this->assertEquals(
1,
$data->testWithCalls,
'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
1,
$data->testWithCalls,
'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
);
$data = new SSViewerTest\CacheTestData();
@ -1956,9 +1984,9 @@ EOC;
$this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data)));
$this->assertEquals(
1,
$data->testLoopCalls,
'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
1,
$data->testLoopCalls,
'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
);
}
@ -1967,10 +1995,10 @@ EOC;
$count = 0;
$parser = new SSTemplateParser();
$parser->addClosedBlock(
'test',
function ($res) use (&$count) {
$count++;
}
'test',
function ($res) use (&$count) {
$count++;
}
);
$template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
@ -1984,10 +2012,10 @@ EOC;
$count = 0;
$parser = new SSTemplateParser();
$parser->addOpenBlock(
'test',
function ($res) use (&$count) {
$count++;
}
'test',
function ($res) use (&$count) {
$count++;
}
);
$template = new SSViewer_FromString("<% test %>", $parser);

View File

@ -1,8 +0,0 @@
---
format: 1
handler:
commit: e71eac35ce7a0f191d199be9e35a8d12f1f999fd
branch: master
lock: false
repository_class: Piston::Git::Repository
repository_url: https://github.com/rgrove/jsmin-php/

View File

@ -1,325 +0,0 @@
<?php
/**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin.
*
* This is pretty much a direct port of jsmin.c to PHP with just a few
* PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
* outputs to stdout, this library accepts a string as input and returns another
* string as output.
*
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
* same terms as jsmin.c, which has the following license:
*
* --
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* --
*
* @package JSMin
* @author Ryan Grove <ryan@wonko.com>
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 1.1.1 (2008-03-02)
* @link http://code.google.com/p/jsmin-php/
*/
class JSMin {
const ORD_LF = 10;
const ORD_SPACE = 32;
protected $a = '';
protected $b = '';
protected $input = '';
protected $inputIndex = 0;
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
// -- Public Static Methods --------------------------------------------------
public static function minify($js) {
$jsmin = new JSMin($js);
return $jsmin->min();
}
// -- Public Instance Methods ------------------------------------------------
public function __construct($input) {
$this->input = str_replace("\r\n", "\n", $input);
$this->inputLength = strlen($this->input);
}
// -- Protected Instance Methods ---------------------------------------------
/* action -- do something! What you do is determined by the argument:
1 Output A. Copy B to A. Get the next B.
2 Copy B to A. Get the next B. (Delete A).
3 Get the next B. (Delete B).
action treats a string as a single character. Wow!
action recognizes a regular expression if it is preceded by ( or , or =.
*/
protected function action($d) {
switch($d) {
case 1:
$this->output .= $this->a;
case 2:
$this->a = $this->b;
if ($this->a === "'" || $this->a === '"') {
for (;;) {
$this->output .= $this->a;
$this->a = $this->get();
if ($this->a === $this->b) {
break;
}
if (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated string literal.');
}
if ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
}
}
}
case 3:
$this->b = $this->next();
if ($this->b === '/' && (
$this->a === '(' || $this->a === ',' || $this->a === '=' ||
$this->a === ':' || $this->a === '[' || $this->a === '!' ||
$this->a === '&' || $this->a === '|' || $this->a === '?' ||
$this->a === '{' || $this->a === '}' || $this->a === ';' ||
$this->a === "\n" )) {
$this->output .= $this->a . $this->b;
for (;;) {
$this->a = $this->get();
if ($this->a === '[') {
/*
inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
*/
for (;;) {
$this->output .= $this->a;
$this->a = $this->get();
if ($this->a === ']') {
break;
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
} elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated regular expression set in regex literal.');
}
}
} elseif ($this->a === '/') {
break;
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
} elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated regular expression literal.');
}
$this->output .= $this->a;
}
$this->b = $this->next();
}
}
}
protected function get() {
$c = $this->lookAhead;
$this->lookAhead = null;
if ($c === null) {
if ($this->inputIndex < $this->inputLength) {
$c = substr($this->input, $this->inputIndex, 1);
$this->inputIndex += 1;
} else {
$c = null;
}
}
if ($c === "\r") {
return "\n";
}
if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
return $c;
}
return ' ';
}
/* isAlphanum -- return true if the character is a letter, digit, underscore,
dollar sign, or non-ASCII character.
*/
protected function isAlphaNum($c) {
return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
}
protected function min() {
$this->a = "\n";
$this->action(3);
while ($this->a !== null) {
switch ($this->a) {
case ' ':
if ($this->isAlphaNum($this->b)) {
$this->action(1);
} else {
$this->action(2);
}
break;
case "\n":
switch ($this->b) {
case '{':
case '[':
case '(':
case '+':
case '-':
$this->action(1);
break;
case ' ':
$this->action(3);
break;
default:
if ($this->isAlphaNum($this->b)) {
$this->action(1);
}
else {
$this->action(2);
}
}
break;
default:
switch ($this->b) {
case ' ':
if ($this->isAlphaNum($this->a)) {
$this->action(1);
break;
}
$this->action(3);
break;
case "\n":
switch ($this->a) {
case '}':
case ']':
case ')':
case '+':
case '-':
case '"':
case "'":
$this->action(1);
break;
default:
if ($this->isAlphaNum($this->a)) {
$this->action(1);
}
else {
$this->action(3);
}
}
break;
default:
$this->action(1);
break;
}
}
}
return $this->output;
}
/* next -- get the next character, excluding comments. peek() is used to see
if a '/' is followed by a '/' or '*'.
*/
protected function next() {
$c = $this->get();
if ($c === '/') {
switch($this->peek()) {
case '/':
for (;;) {
$c = $this->get();
if (ord($c) <= self::ORD_LF) {
return $c;
}
}
case '*':
$this->get();
for (;;) {
switch($this->get()) {
case '*':
if ($this->peek() === '/') {
$this->get();
return ' ';
}
break;
case null:
throw new JSMinException('Unterminated comment.');
}
}
default:
return $c;
}
}
return $c;
}
protected function peek() {
$this->lookAhead = $this->get();
return $this->lookAhead;
}
}
// -- Exceptions ---------------------------------------------------------------
class JSMinException extends Exception {}
?>