From 0034bf657b1250277de1e31c043f043d7d10f087 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 29 Feb 2012 18:55:33 -0800 Subject: [PATCH] Add test coverage for Mustache.php 2.0 Update legacy tests for new codebase. --- test/Mustache/Test/BufferTest.php | 90 +++ test/Mustache/Test/CompilerTest.php | 75 +++ test/Mustache/Test/ContextTest.php | 150 +++++ test/Mustache/Test/Functional/CallTest.php | 17 +- .../Mustache/Test/Functional/ExamplesTest.php | 5 +- .../Functional/HigherOrderSectionsTest.php | 95 +-- .../Test/Functional/MustacheInjectionTest.php | 206 +++---- .../Test/Functional/MustacheSpecTest.php | 132 +++-- .../Test/Functional/ObjectSectionTest.php | 52 +- test/Mustache/Test/Loader/ArrayLoaderTest.php | 43 ++ .../Test/Loader/FilesystemLoaderTest.php | 79 +-- .../Mustache/Test/Loader/StringLoaderTest.php | 18 + test/Mustache/Test/MustacheTest.php | 551 ++++-------------- test/Mustache/Test/ParserTest.php | 157 +++++ test/Mustache/Test/TemplateTest.php | 43 ++ test/Mustache/Test/TokenizerTest.php | 117 ++++ 16 files changed, 1123 insertions(+), 707 deletions(-) create mode 100644 test/Mustache/Test/BufferTest.php create mode 100644 test/Mustache/Test/CompilerTest.php create mode 100644 test/Mustache/Test/ContextTest.php create mode 100644 test/Mustache/Test/Loader/ArrayLoaderTest.php create mode 100644 test/Mustache/Test/Loader/StringLoaderTest.php create mode 100644 test/Mustache/Test/ParserTest.php create mode 100644 test/Mustache/Test/TemplateTest.php create mode 100644 test/Mustache/Test/TokenizerTest.php diff --git a/test/Mustache/Test/BufferTest.php b/test/Mustache/Test/BufferTest.php new file mode 100644 index 0000000..4fd886c --- /dev/null +++ b/test/Mustache/Test/BufferTest.php @@ -0,0 +1,90 @@ +assertEquals($indent, $buffer->getIndent()); + $this->assertEquals($charset, $buffer->getCharset()); + } + + public function getConstructorArgs() { + return array( + array('', 'UTF-8'), + array(' ', 'ISO-8859-1'), + array("\t\t\t", 'Shift_JIS'), + ); + } + + public function testWrite() { + $buffer = new Buffer; + $this->assertEquals('', $buffer->flush()); + + $buffer->writeLine(); + $buffer->writeLine(); + $buffer->writeLine(); + $this->assertEquals("\n\n\n", $buffer->flush()); + $this->assertEquals('', $buffer->flush()); + + $buffer->write('foo'); + $buffer->write('bar'); + $buffer->writeLine(); + $buffer->write('baz'); + $this->assertEquals("foobar\nbaz", $buffer->flush()); + $this->assertEquals('', $buffer->flush()); + + $indent = "\t\t"; + $buffer->setIndent($indent); + $buffer->writeText('foo'); + $buffer->writeLine(); + $buffer->writeText('bar'); + $this->assertEquals("\t\tfoo\n\t\tbar", $buffer->flush()); + $this->assertEquals('', $buffer->flush()); + } + + /** + * @dataProvider getEscapeAndIndent + */ + public function testEscapingAndIndenting($text, $escape, $indent, $whitespace, $expected) { + $buffer = new Buffer; + $buffer->setIndent($whitespace); + + $buffer->write($text, $indent, $escape); + $this->assertEquals($expected, $buffer->flush()); + } + + public function getEscapeAndIndent() { + return array( + array('> "fun & games" <', false, false, "\t", '> "fun & games" <'), + array('> "fun & games" <', true, false, "\t", '> "fun & games" <'), + array('> "fun & games" <', true, true, "\t", "\t> "fun & games" <"), + array('> "fun & games" <', false, true, "\t", "\t> \"fun & games\" <"), + ); + } + + public function testChangeIndent() { + $indent = "\t\t"; + $buffer = new Buffer($indent); + $this->assertEquals($indent, $buffer->getIndent()); + + $indent = ""; + $buffer->setIndent($indent); + $this->assertEquals($indent, $buffer->getIndent()); + + $indent = " "; + $buffer->setIndent($indent); + $this->assertEquals($indent, $buffer->getIndent()); + } + +} diff --git a/test/Mustache/Test/CompilerTest.php b/test/Mustache/Test/CompilerTest.php new file mode 100644 index 0000000..c9abe1e --- /dev/null +++ b/test/Mustache/Test/CompilerTest.php @@ -0,0 +1,75 @@ +compile($source, $tree, $name); + foreach ($expected as $contains) { + $this->assertContains($contains, $compiled); + } + } + + public function getCompileValues() { + return array( + array('', array(), 'Banana', array( + "\nclass Banana extends \Mustache\Template", + '$buffer->flush();', + )), + + array('', array('TEXT'), 'Monkey', array( + "\nclass Monkey extends \Mustache\Template", + '$buffer->writeText(\'TEXT\');', + '$buffer->flush();', + )), + + array( + '', + array( + 'foo', + "\n", + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name', + ), + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => '.', + ), + "'bar'", + ), + 'Monkey', + array( + "\nclass Monkey extends \Mustache\Template", + '$buffer->writeText(\'foo\');', + '$buffer->writeLine();', + '$value = $context->find(\'name\');', + '$buffer->writeText($value, true);', + '$value = $context->last();', + '$buffer->writeText(\'\\\'bar\\\'\');', + '$buffer->flush();', + ) + ), + ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCompilerThrowsUnknownNodeTypeException() { + $compiler = new Compiler; + $compiler->compile('', array(array(Tokenizer::TAG => 'invalid')), 'SomeClass'); + } +} diff --git a/test/Mustache/Test/ContextTest.php b/test/Mustache/Test/ContextTest.php new file mode 100644 index 0000000..678e976 --- /dev/null +++ b/test/Mustache/Test/ContextTest.php @@ -0,0 +1,150 @@ +assertSame('', $one->find('foo')); + $this->assertSame('', $one->find('bar')); + + $two = new Context(array( + 'foo' => 'FOO', + 'bar' => '' + )); + $this->assertEquals('FOO', $two->find('foo')); + $this->assertEquals('', $two->find('bar')); + + $obj = new \StdClass; + $obj->name = 'NAME'; + $three = new Context($obj); + $this->assertSame($obj, $three->last()); + $this->assertEquals('NAME', $three->find('name')); + } + + public function testIsTruthy() { + $context = new Context; + + $this->assertTrue($context->isTruthy('string')); + $this->assertTrue($context->isTruthy(new \StdClass)); + $this->assertTrue($context->isTruthy(1)); + $this->assertTrue($context->isTruthy(array('a', 'b'))); + + $this->assertFalse($context->isTruthy(null)); + $this->assertFalse($context->isTruthy('')); + $this->assertFalse($context->isTruthy(0)); + $this->assertFalse($context->isTruthy(array())); + } + + public function testIsCallable() { + $dummy = new TestDummy; + $context = new Context; + + $this->assertTrue($context->isCallable(function() { return null; })); + $this->assertTrue($context->isCallable(array('\Mustache\Test\TestDummy', 'foo'))); + $this->assertTrue($context->isCallable(array($dummy, 'bar'))); + $this->assertTrue($context->isCallable($dummy)); + + $this->assertFalse($context->isCallable('count')); + $this->assertFalse($context->isCallable('TestDummy::foo')); + $this->assertFalse($context->isCallable(array('\Mustache\Test\TestDummy', 'name'))); + $this->assertFalse($context->isCallable(array('NotReallyAClass', 'foo'))); + $this->assertFalse($context->isCallable(array($dummy, 'name'))); + } + + /** + * @dataProvider getIterables + */ + public function testIsIterable($value, $iterable) { + $context = new Context; + $this->assertEquals($iterable, $context->isIterable($value)); + } + + public function getIterables() { + return array( + array(array(0 => 'a', 1 => 'b'), true), + array(array(0 => 'a', 2 => 'b'), false), + array(array(1 => 'a', 2 => 'b'), false), + array(array('a' => 0, 'b' => 1), false), + array('some string', false), + array(new \ArrayIterator, true), + ); + } + + public function testPushPopAndLast() { + $context = new Context; + $this->assertFalse($context->last()); + + $dummy = new TestDummy; + $context->push($dummy); + $this->assertSame($dummy, $context->last()); + $this->assertSame($dummy, $context->pop()); + $this->assertFalse($context->last()); + + $obj = new \StdClass; + $context->push($dummy); + $this->assertSame($dummy, $context->last()); + $context->push($obj); + $this->assertSame($obj, $context->last()); + $this->assertSame($obj, $context->pop()); + $this->assertSame($dummy, $context->pop()); + $this->assertFalse($context->last()); + } + + public function testFind() { + $context = new Context; + + $dummy = new TestDummy; + + $obj = new \StdClass; + $obj->name = 'obj'; + + $arr = array( + 'a' => array('b' => array('c' => 'see')), + 'b' => 'bee', + ); + + $string = 'some arbitrary string'; + + $context->push($dummy); + $this->assertEquals('dummy', $context->find('name')); + + $context->push($obj); + $this->assertEquals('obj', $context->find('name')); + + $context->pop(); + $this->assertEquals('dummy', $context->find('name')); + + $dummy->name = 'dummyer'; + $this->assertEquals('dummyer', $context->find('name')); + + $context->push($arr); + $this->assertEquals('bee', $context->find('b')); + $this->assertEquals('see', $context->findDot('a.b.c')); + + $dummy->name = 'dummy'; + + $context->push($string); + $this->assertSame($string, $context->last()); + $this->assertEquals('dummy', $context->find('name')); + $this->assertEquals('see', $context->findDot('a.b.c')); + $this->assertEquals('', $context->find('foo')); + $this->assertEquals('', $context->findDot('bar')); + } +} + +class TestDummy { + public $name = 'dummy'; + public function __invoke() {} + public static function foo() { + return ''; + } + public function bar() { + return ''; + } +} diff --git a/test/Mustache/Test/Functional/CallTest.php b/test/Mustache/Test/Functional/CallTest.php index 366d2c4..0b0c342 100644 --- a/test/Mustache/Test/Functional/CallTest.php +++ b/test/Mustache/Test/Functional/CallTest.php @@ -1,18 +1,25 @@ loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}'); + $foo = new ClassWithCall(); $foo->name = 'Bob'; - $template = '{{# foo }}{{ label }}: {{ name }}{{/ foo }}'; $data = array('label' => 'name', 'foo' => $foo); - $m = new Mustache($template, $data); - $this->assertEquals('name: Bob', $m->render()); + $this->assertEquals('name: Bob', $tpl->render($data)); } } diff --git a/test/Mustache/Test/Functional/ExamplesTest.php b/test/Mustache/Test/Functional/ExamplesTest.php index 5635dd2..6aff929 100644 --- a/test/Mustache/Test/Functional/ExamplesTest.php +++ b/test/Mustache/Test/Functional/ExamplesTest.php @@ -4,12 +4,15 @@ namespace Mustache\Test\Functional; use Mustache\Mustache; +/** + * @group examples + * @group functional + */ class ExamplesTest extends \PHPUnit_Framework_TestCase { /** * Test everything in the `examples` directory. * - * @group examples * @dataProvider getExamples * * @param string $context diff --git a/test/Mustache/Test/Functional/HigherOrderSectionsTest.php b/test/Mustache/Test/Functional/HigherOrderSectionsTest.php index 87e4142..680efd7 100644 --- a/test/Mustache/Test/Functional/HigherOrderSectionsTest.php +++ b/test/Mustache/Test/Functional/HigherOrderSectionsTest.php @@ -1,93 +1,115 @@ foo = new Foo(); + $this->mustache = new Mustache; } public function testAnonymousFunctionSectionCallback() { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3'); - return; - } + $tpl = $this->mustache->loadTemplate('{{#wrapper}}{{name}}{{/wrapper}}'); - $this->foo->wrapper = function($text) { + $foo = new Foo; + $foo->name = 'Mario'; + $foo->wrapper = function($text) { return sprintf('
%s
', $text); }; - $this->assertEquals( - sprintf('
%s
', $this->foo->name), - $this->foo->render('{{#wrapper}}{{name}}{{/wrapper}}') - ); + $this->assertEquals(sprintf('
%s
', $foo->name), $tpl->render($foo)); } public function testSectionCallback() { - $this->assertEquals(sprintf('%s', $this->foo->name), $this->foo->render('{{name}}')); - $this->assertEquals(sprintf('%s', $this->foo->name), $this->foo->render('{{#wrap}}{{name}}{{/wrap}}')); + $one = $this->mustache->loadTemplate('{{name}}'); + $two = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}'); + + $foo = new Foo; + $foo->name = 'Luigi'; + + $this->assertEquals($foo->name, $one->render($foo)); + $this->assertEquals(sprintf('%s', $foo->name), $two->render($foo)); } public function testRuntimeSectionCallback() { - $this->foo->double_wrap = array($this->foo, 'wrapWithBoth'); - $this->assertEquals( - sprintf('%s', $this->foo->name), - $this->foo->render('{{#double_wrap}}{{name}}{{/double_wrap}}') - ); + $tpl = $this->mustache->loadTemplate('{{#double_wrap}}{{name}}{{/double_wrap}}'); + + $foo = new Foo; + $foo->double_wrap = array($foo, 'wrapWithBoth'); + + $this->assertEquals(sprintf('%s', $foo->name), $tpl->render($foo)); } public function testStaticSectionCallback() { - $this->foo->trimmer = array(get_class($this->foo), 'staticTrim'); - $this->assertEquals($this->foo->name, $this->foo->render('{{#trimmer}} {{name}} {{/trimmer}}')); + $tpl = $this->mustache->loadTemplate('{{#trimmer}} {{name}} {{/trimmer}}'); + + $foo = new Foo; + $foo->trimmer = array(get_class($foo), 'staticTrim'); + + $this->assertEquals($foo->name, $tpl->render($foo)); } public function testViewArraySectionCallback() { + $tpl = $this->mustache->loadTemplate('{{#trim}} {{name}} {{/trim}}'); + + $foo = new Foo; + $data = array( 'name' => 'Bob', - 'trim' => array(get_class($this->foo), 'staticTrim'), + 'trim' => array(get_class($foo), 'staticTrim'), ); - $this->assertEquals($data['name'], $this->foo->render('{{#trim}} {{name}} {{/trim}}', $data)); + + $this->assertEquals($data['name'], $tpl->render($data)); } public function testViewArrayAnonymousSectionCallback() { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3'); - return; - } + $tpl = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}'); + $data = array( 'name' => 'Bob', 'wrap' => function($text) { return sprintf('[[%s]]', $text); } ); + $this->assertEquals( sprintf('[[%s]]', $data['name']), - $this->foo->render('{{#wrap}}{{name}}{{/wrap}}', $data) + $tpl->render($data) ); } public function testMonsters() { + $tpl = $this->mustache->loadTemplate('{{#title}}{{title}} {{/title}}{{name}}'); + $frank = new Monster(); $frank->title = 'Dr.'; $frank->name = 'Frankenstein'; - $this->assertEquals('Dr. Frankenstein', $frank->render()); + $this->assertEquals('Dr. Frankenstein', $tpl->render($frank)); $dracula = new Monster(); $dracula->title = 'Count'; $dracula->name = 'Dracula'; - $this->assertEquals('Count Dracula', $dracula->render()); + $this->assertEquals('Count Dracula', $tpl->render($dracula)); } } -class Foo extends Mustache { +class Foo { public $name = 'Justin'; public $lorem = 'Lorem ipsum dolor sit amet,'; public $wrap; - public function __construct($template = null, $view = null, $partials = null) { - $this->wrap = array($this, 'wrapWithEm'); - parent::__construct($template, $view, $partials); + public function __construct() { + $this->wrap = function($text) { + return sprintf('%s', $text); + }; } public function wrapWithEm($text) { @@ -107,8 +129,7 @@ class Foo extends Mustache { } } -class Monster extends Mustache { - public $_template = '{{#title}}{{title}} {{/title}}{{name}}'; +class Monster { public $title; public $name; -} \ No newline at end of file +} diff --git a/test/Mustache/Test/Functional/MustacheInjectionTest.php b/test/Mustache/Test/Functional/MustacheInjectionTest.php index de74943..eec0e1c 100644 --- a/test/Mustache/Test/Functional/MustacheInjectionTest.php +++ b/test/Mustache/Test/Functional/MustacheInjectionTest.php @@ -1,127 +1,127 @@ '{{ b }}', - 'b' => 'FAIL' - ); - $template = '{{ a }}'; - $output = '{{ b }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + public function setUp() { + $this->mustache = new Mustache; + } - public function testUnescapedInterpolationInjection() { - $data = array( - 'a' => '{{ b }}', - 'b' => 'FAIL' - ); - $template = '{{{ a }}}'; - $output = '{{ b }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + // interpolation + + public function testInterpolationInjection() { + $tpl = $this->mustache->loadTemplate('{{ a }}'); + + $data = array( + 'a' => '{{ b }}', + 'b' => 'FAIL' + ); + + $this->assertEquals('{{ b }}', $tpl->render($data)); + } + + public function testUnescapedInterpolationInjection() { + $tpl = $this->mustache->loadTemplate('{{{ a }}}'); + + $data = array( + 'a' => '{{ b }}', + 'b' => 'FAIL' + ); + + $this->assertEquals('{{ b }}', $tpl->render($data)); + } - // sections + // sections - public function testSectionInjection() { - $data = array( - 'a' => true, - 'b' => '{{ c }}', - 'c' => 'FAIL' - ); - $template = '{{# a }}{{ b }}{{/ a }}'; - $output = '{{ c }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + public function testSectionInjection() { + $tpl = $this->mustache->loadTemplate('{{# a }}{{ b }}{{/ a }}'); - public function testUnescapedSectionInjection() { - $data = array( - 'a' => true, - 'b' => '{{ c }}', - 'c' => 'FAIL' - ); - $template = '{{# a }}{{{ b }}}{{/ a }}'; - $output = '{{ c }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + $data = array( + 'a' => true, + 'b' => '{{ c }}', + 'c' => 'FAIL' + ); + + $this->assertEquals('{{ c }}', $tpl->render($data)); + } + + public function testUnescapedSectionInjection() { + $tpl = $this->mustache->loadTemplate('{{# a }}{{{ b }}}{{/ a }}'); + + $data = array( + 'a' => true, + 'b' => '{{ c }}', + 'c' => 'FAIL' + ); + + $this->assertEquals('{{ c }}', $tpl->render($data)); + } - // partials + // partials - public function testPartialInjection() { - $data = array( - 'a' => '{{ b }}', - 'b' => 'FAIL' - ); - $template = '{{> partial }}'; - $partials = array( - 'partial' => '{{ a }}', - ); - $output = '{{ b }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data, $partials)); - } + public function testPartialInjection() { + $tpl = $this->mustache->loadTemplate('{{> partial }}'); + $this->mustache->setPartials(array( + 'partial' => '{{ a }}', + )); - public function testPartialUnescapedInjection() { - $data = array( - 'a' => '{{ b }}', - 'b' => 'FAIL' - ); - $template = '{{> partial }}'; - $partials = array( - 'partial' => '{{{ a }}}', - ); - $output = '{{ b }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data, $partials)); - } + $data = array( + 'a' => '{{ b }}', + 'b' => 'FAIL' + ); + + $this->assertEquals('{{ b }}', $tpl->render($data)); + } + + public function testPartialUnescapedInjection() { + $tpl = $this->mustache->loadTemplate('{{> partial }}'); + $this->mustache->setPartials(array( + 'partial' => '{{{ a }}}', + )); + + $data = array( + 'a' => '{{ b }}', + 'b' => 'FAIL' + ); + + $this->assertEquals('{{ b }}', $tpl->render($data)); + } - // lambdas + // lambdas - public function testLambdaInterpolationInjection() { - $data = array( - 'a' => array($this, 'interpolationLambda'), - 'b' => '{{ c }}', - 'c' => 'FAIL' - ); - $template = '{{ a }}'; - $output = '{{ c }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + public function testLambdaInterpolationInjection() { + $tpl = $this->mustache->loadTemplate('{{ a }}'); - public function interpolationLambda() { - return '{{ b }}'; - } + $data = array( + 'a' => function() { return '{{ b }}'; }, + 'b' => '{{ c }}', + 'c' => 'FAIL' + ); - public function testLambdaSectionInjection() { - $data = array( - 'a' => array($this, 'sectionLambda'), - 'b' => '{{ c }}', - 'c' => 'FAIL' - ); - $template = '{{# a }}b{{/ a }}'; - $output = '{{ c }}'; - $m = new Mustache(); - $this->assertEquals($output, $m->render($template, $data)); - } + $this->assertEquals('{{ c }}', $tpl->render($data)); + } - public function sectionLambda($content) { - return '{{ ' . $content . ' }}'; - } + public function testLambdaSectionInjection() { + $tpl = $this->mustache->loadTemplate('{{# a }}b{{/ a }}'); -} \ No newline at end of file + $data = array( + 'a' => function ($text) { return '{{ ' . $text . ' }}'; }, + 'b' => '{{ c }}', + 'c' => 'FAIL' + ); + + $this->assertEquals('{{ c }}', $tpl->render($data)); + } +} diff --git a/test/Mustache/Test/Functional/MustacheSpecTest.php b/test/Mustache/Test/Functional/MustacheSpecTest.php index d3a3413..831a029 100644 --- a/test/Mustache/Test/Functional/MustacheSpecTest.php +++ b/test/Mustache/Test/Functional/MustacheSpecTest.php @@ -1,22 +1,30 @@ markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"'); } } @@ -25,56 +33,72 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group comments * @dataProvider loadCommentSpec */ - public function testCommentSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function testCommentSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); + } + + public function loadCommentSpec() { + return $this->loadSpec('comments'); } /** * @group delimiters * @dataProvider loadDelimitersSpec */ - public function testDelimitersSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function testDelimitersSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); + } + + public function loadDelimitersSpec() { + return $this->loadSpec('delimiters'); } /** * @group interpolation * @dataProvider loadInterpolationSpec */ - public function testInterpolationSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function testInterpolationSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); + } + + public function loadInterpolationSpec() { + return $this->loadSpec('interpolation'); } /** + * @group inverted * @group inverted-sections * @dataProvider loadInvertedSpec */ - public function testInvertedSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function testInvertedSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); + } + + public function loadInvertedSpec() { + return $this->loadSpec('inverted'); } /** * @group lambdas * @dataProvider loadLambdasSpec */ - public function testLambdasSpec($desc, $template, $data, $partials, $expected) { - if (!version_compare(PHP_VERSION, '5.3.0', '>=')) { - $this->markTestSkipped('Unable to test Lambdas spec with PHP < 5.3.'); - } + public function testLambdasSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($this->prepareLambdasSpec($data)), $desc); + } - $data = $this->prepareLambdasSpec($data); - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function loadLambdasSpec() { + return $this->loadSpec('~lambdas'); } /** * Extract and lambdafy any 'lambda' values found in the $data array. */ - protected function prepareLambdasSpec($data) { + private function prepareLambdasSpec($data) { foreach ($data as $key => $val) { if ($key === 'lambda') { if (!isset($val['php'])) { @@ -94,42 +118,22 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group partials * @dataProvider loadPartialsSpec */ - public function testPartialsSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); + public function testPartialsSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); + } + + public function loadPartialsSpec() { + return $this->loadSpec('partials'); } /** * @group sections * @dataProvider loadSectionsSpec */ - public function testSectionsSpec($desc, $template, $data, $partials, $expected) { - $m = new Mustache($template, $data, $partials); - $this->assertEquals($expected, $m->render(), $desc); - } - - public function loadCommentSpec() { - return $this->loadSpec('comments'); - } - - public function loadDelimitersSpec() { - return $this->loadSpec('delimiters'); - } - - public function loadInterpolationSpec() { - return $this->loadSpec('interpolation'); - } - - public function loadInvertedSpec() { - return $this->loadSpec('inverted'); - } - - public function loadLambdasSpec() { - return $this->loadSpec('~lambdas'); - } - - public function loadPartialsSpec() { - return $this->loadSpec('partials'); + public function testSectionsSpec($desc, $source, $partials, $data, $expected) { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template($data), $desc); } public function loadSectionsSpec() { @@ -144,14 +148,14 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @access public * @return array */ - protected function loadSpec($name) { - $filename = dirname(__FILE__) . '/spec/specs/' . $name . '.yml'; + private function loadSpec($name) { + $filename = __DIR__ . '/../../../spec/specs/' . $name . '.yml'; if (!file_exists($filename)) { return array(); } $data = array(); - $yaml = new sfYamlParser(); + $yaml = new \sfYamlParser; $file = file_get_contents($filename); // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain. @@ -160,15 +164,23 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { } $spec = $yaml->parse($file); + foreach ($spec['tests'] as $test) { $data[] = array( $test['name'] . ': ' . $test['desc'], $test['template'], - $test['data'], isset($test['partials']) ? $test['partials'] : array(), + $test['data'], $test['expected'], ); } + return $data; } -} \ No newline at end of file + + private static function loadTemplate($source, $partials) { + self::$mustache->setPartials($partials); + + return self::$mustache->loadTemplate($source); + } +} diff --git a/test/Mustache/Test/Functional/ObjectSectionTest.php b/test/Mustache/Test/Functional/ObjectSectionTest.php index 82405d6..ccf1ca6 100644 --- a/test/Mustache/Test/Functional/ObjectSectionTest.php +++ b/test/Mustache/Test/Functional/ObjectSectionTest.php @@ -1,48 +1,64 @@ mustache = new Mustache; + } + public function testBasicObject() { - $alpha = new Alpha(); - $this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}')); + $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}'); + $this->assertEquals('Foo', $tpl->render(new Alpha)); } + /** + * @group magic_methods + */ public function testObjectWithGet() { - $beta = new Beta(); - $this->assertEquals('Foo', $beta->render('{{#foo}}{{name}}{{/foo}}')); + $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}'); + $this->assertEquals('Foo', $tpl->render(new Beta)); } + /** + * @group magic_methods + */ public function testSectionObjectWithGet() { - $gamma = new Gamma(); - $this->assertEquals('Foo', $gamma->render('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}')); + $tpl = $this->mustache->loadTemplate('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}'); + $this->assertEquals('Foo', $tpl->render(new Gamma)); } public function testSectionObjectWithFunction() { - $alpha = new Alpha(); - $alpha->foo = new Delta(); - $this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}')); + $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}'); + $alpha = new Alpha; + $alpha->foo = new Delta; + $this->assertEquals('Foo', $tpl->render($alpha)); } } -class Alpha extends Mustache { +class Alpha { public $foo; public function __construct() { - $this->foo = new StdClass(); + $this->foo = new \StdClass(); $this->foo->name = 'Foo'; $this->foo->number = 1; } } -class Beta extends Mustache { +class Beta { protected $_data = array(); public function __construct() { - $this->_data['foo'] = new StdClass(); + $this->_data['foo'] = new \StdClass(); $this->_data['foo']->name = 'Foo'; $this->_data['foo']->number = 1; } @@ -56,7 +72,7 @@ class Beta extends Mustache { } } -class Gamma extends Mustache { +class Gamma { public $bar; public function __construct() { @@ -64,10 +80,10 @@ class Gamma extends Mustache { } } -class Delta extends Mustache { +class Delta { protected $_name = 'Foo'; public function name() { return $this->_name; } -} \ No newline at end of file +} diff --git a/test/Mustache/Test/Loader/ArrayLoaderTest.php b/test/Mustache/Test/Loader/ArrayLoaderTest.php new file mode 100644 index 0000000..eccd64d --- /dev/null +++ b/test/Mustache/Test/Loader/ArrayLoaderTest.php @@ -0,0 +1,43 @@ + 'bar' + )); + + $this->assertEquals('bar', $loader->load('foo')); + } + + public function testSetAndLoadTemplates() { + $loader = new ArrayLoader(array( + 'foo' => 'bar' + )); + $this->assertEquals('bar', $loader->load('foo')); + + $loader->setTemplate('baz', 'qux'); + $this->assertEquals('qux', $loader->load('baz')); + + $loader->setTemplates(array( + 'foo' => 'FOO', + 'baz' => 'BAZ', + )); + $this->assertEquals('FOO', $loader->load('foo')); + $this->assertEquals('BAZ', $loader->load('baz')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMissingTemplatesThrowExceptions() { + $loader = new ArrayLoader; + $loader->load('not_a_real_template'); + } +} diff --git a/test/Mustache/Test/Loader/FilesystemLoaderTest.php b/test/Mustache/Test/Loader/FilesystemLoaderTest.php index 2674a0f..693527a 100644 --- a/test/Mustache/Test/Loader/FilesystemLoaderTest.php +++ b/test/Mustache/Test/Loader/FilesystemLoaderTest.php @@ -1,60 +1,41 @@ assertEquals(file_get_contents(dirname(__FILE__).'/fixtures/foo.mustache'), $loader['foo']); - $this->assertEquals(file_get_contents(dirname(__FILE__).'/fixtures/bar.mustache'), $loader['bar']); +class FilesystemLoaderTest extends \PHPUnit_Framework_TestCase { + public function testConstructor() { + $baseDir = realpath(__DIR__.'/../../../fixtures/templates'); + $loader = new FilesystemLoader($baseDir, array('extension' => '.ms')); + $this->assertEquals('alpha contents', $loader->load('alpha')); + $this->assertEquals('beta contents', $loader->load('beta.ms')); } - public function testMustacheUsesFilesystemLoader() { - $template = '{{> foo }} {{> bar }}'; - $data = array( - 'truthy' => true, - 'foo' => 'FOO', - 'bar' => 'BAR', - ); - $output = 'FOO BAR'; - $m = new Mustache(); - $partials = new MustacheLoader(dirname(__FILE__).'/fixtures'); - $this->assertEquals($output, $m->render($template, $data, $partials)); + public function testLoadTemplates() { + $baseDir = realpath(__DIR__.'/../../../fixtures/templates'); + $loader = new FilesystemLoader($baseDir); + $this->assertEquals('one contents', $loader->load('one')); + $this->assertEquals('two contents', $loader->load('two.mustache')); } - public function testMustacheUsesDifferentLoadersToo() { - $template = '{{> foo }} {{> bar }}'; - $data = array( - 'truthy' => true, - 'foo' => 'FOO', - 'bar' => 'BAR', - ); - $output = 'FOO BAR'; - $m = new Mustache(); - $partials = new DifferentMustacheLoader(); - $this->assertEquals($output, $m->render($template, $data, $partials)); + /** + * @expectedException \RuntimeException + */ + public function testMissingBaseDirThrowsException() { + $loader = new FilesystemLoader(__DIR__.'/not_a_directory'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMissingTemplateThrowsException() { + $baseDir = realpath(__DIR__.'/../../../fixtures/templates'); + $loader = new FilesystemLoader($baseDir); + + $loader->load('fake'); } } - -class DifferentMustacheLoader implements ArrayAccess { - protected $partials = array( - 'foo' => '{{ foo }}', - 'bar' => '{{# truthy }}{{ bar }}{{/ truthy }}', - ); - - public function offsetExists($offset) { - return isset($this->partials[$offset]); - } - - public function offsetGet($offset) { - return $this->partials[$offset]; - } - - public function offsetSet($offset, $value) {} - public function offsetUnset($offset) {} -} diff --git a/test/Mustache/Test/Loader/StringLoaderTest.php b/test/Mustache/Test/Loader/StringLoaderTest.php new file mode 100644 index 0000000..ca359e1 --- /dev/null +++ b/test/Mustache/Test/Loader/StringLoaderTest.php @@ -0,0 +1,18 @@ +assertEquals('foo', $loader->load('foo')); + $this->assertEquals('{{ bar }}', $loader->load('{{ bar }}')); + $this->assertEquals("\n{{! comment }}\n", $loader->load("\n{{! comment }}\n")); + } +} diff --git a/test/Mustache/Test/MustacheTest.php b/test/Mustache/Test/MustacheTest.php index 0c0bb13..9883058 100644 --- a/test/Mustache/Test/MustacheTest.php +++ b/test/Mustache/Test/MustacheTest.php @@ -1,464 +1,147 @@ array( - array('type' => 'Natural'), - array('type' => 'Hungarian'), - array('type' => 'Dali'), - array('type' => 'English'), - array('type' => 'Imperial'), - array('type' => 'Freestyle', 'last' => 'true'), - ) - ); - $output = 'Natural, Hungarian, Dali, English, Imperial, and Freestyle'; - - $m1 = new Mustache(); - $this->assertEquals($output, $m1->render($template, $data)); - - $m2 = new Mustache($template); - $this->assertEquals($output, $m2->render(null, $data)); - - $m3 = new Mustache($template, $data); - $this->assertEquals($output, $m3->render()); - - $m4 = new Mustache(null, $data); - $this->assertEquals($output, $m4->render($template)); - } - - /** - * @dataProvider constructorOptions - */ - public function testConstructorOptions($options, $charset, $delimiters, $pragmas) { - $mustache = new MustacheExposedOptionsStub(null, null, null, $options); - $this->assertEquals($charset, $mustache->getCharset()); - $this->assertEquals($delimiters, $mustache->getDelimiters()); - $this->assertEquals($pragmas, $mustache->getPragmas()); - } - - public function constructorOptions() { - return array( - array( - array(), - 'UTF-8', - array('{{', '}}'), - array(), - ), - array( - array( - 'charset' => 'UTF-8', - 'delimiters' => '<< >>', - 'pragmas' => array(Mustache::PRAGMA_UNESCAPED => true) - ), - 'UTF-8', - array('<<', '>>'), - array(Mustache::PRAGMA_UNESCAPED => true), - ), - array( - array( - 'charset' => 'cp866', - 'delimiters' => array('[[[[', ']]]]'), - 'pragmas' => array(Mustache::PRAGMA_UNESCAPED => true) - ), - 'cp866', - array('[[[[', ']]]]'), - array(Mustache::PRAGMA_UNESCAPED => true), - ), - ); - } - - /** - * @expectedException MustacheException - */ - public function testConstructorInvalidPragmaOptionsThrowExceptions() { - $mustache = new Mustache(null, null, null, array('pragmas' => array('banana phone' => true))); - } - - /** - * Test __toString() function. - * - * @access public - * @return void - */ - public function test__toString() { - $m = new Mustache('{{first_name}} {{last_name}}', array('first_name' => 'Karl', 'last_name' => 'Marx')); - - $this->assertEquals('Karl Marx', $m->__toString()); - $this->assertEquals('Karl Marx', (string) $m); - - $m2 = $this->getMock(self::TEST_CLASS, array('render'), array()); - $m2->expects($this->once()) - ->method('render') - ->will($this->returnValue('foo')); - - $this->assertEquals('foo', $m2->render()); - } - - public function test__toStringException() { - $m = $this->getMock(self::TEST_CLASS, array('render'), array()); - $m->expects($this->once()) - ->method('render') - ->will($this->throwException(new Exception)); - - try { - $out = (string) $m; - } catch (Exception $e) { - $this->fail('__toString should catch all exceptions'); + public static function setUpBeforeClass() { + self::$tempDir = sys_get_temp_dir() . '/mustache_test'; + if (file_exists(self::$tempDir)) { + self::rmdir(self::$tempDir); } } - /** - * Test render(). - * - * @access public - * @return void - */ - public function testRender() { - $m = new Mustache(); - - $this->assertEquals('', $m->render('')); - $this->assertEquals('foo', $m->render('foo')); - $this->assertEquals('', $m->render(null)); - - $m2 = new Mustache('foo'); - $this->assertEquals('foo', $m2->render()); - - $m3 = new Mustache(''); - $this->assertEquals('', $m3->render()); - - $m3 = new Mustache(); - $this->assertEquals('', $m3->render(null)); - } - - /** - * Test render() with data. - * - * @group interpolation - */ - public function testRenderWithData() { - $m = new Mustache('{{first_name}} {{last_name}}'); - $this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin'))); - $this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa'))); - } - - /** - * @group partials - */ - public function testRenderWithPartials() { - $m = new Mustache('{{>stache}}', null, array('stache' => '{{first_name}} {{last_name}}')); - $this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin'))); - $this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa'))); - } - - /** - * @group interpolation - * @dataProvider interpolationData - */ - public function testDoubleRenderMustacheTags($template, $context, $expected) { - $m = new Mustache($template, $context); - $this->assertEquals($expected, $m->render()); - } - - public function interpolationData() { - return array( - array( - '{{#a}}{{=<% %>=}}{{b}} c<%={{ }}=%>{{/a}}', - array('a' => array(array('b' => 'Do Not Render'))), - '{{b}} c' - ), - array( - '{{#a}}{{b}}{{/a}}', - array('a' => array('b' => '{{c}}'), 'c' => 'FAIL'), - '{{c}}' - ), - ); - } - - /** - * Mustache should allow newlines (and other whitespace) in comments and all other tags. - * - * @group comments - */ - public function testNewlinesInComments() { - $m = new Mustache("{{! comment \n \t still a comment... }}"); - $this->assertEquals('', $m->render()); - } - - /** - * Mustache should return the same thing when invoked multiple times. - */ - public function testMultipleInvocations() { - $m = new Mustache('x'); - $first = $m->render(); - $second = $m->render(); - - $this->assertEquals('x', $first); - $this->assertEquals($first, $second); - } - - /** - * Mustache should return the same thing when invoked multiple times. - * - * @group interpolation - */ - public function testMultipleInvocationsWithTags() { - $m = new Mustache('{{one}} {{two}}', array('one' => 'foo', 'two' => 'bar')); - $first = $m->render(); - $second = $m->render(); - - $this->assertEquals('foo bar', $first); - $this->assertEquals($first, $second); - } - - /** - * Mustache should not use templates passed to the render() method for subsequent invocations. - */ - public function testResetTemplateForMultipleInvocations() { - $m = new Mustache('Sirve.'); - $this->assertEquals('No sirve.', $m->render('No sirve.')); - $this->assertEquals('Sirve.', $m->render()); - - $m2 = new Mustache(); - $this->assertEquals('No sirve.', $m2->render('No sirve.')); - $this->assertEquals('', $m2->render()); - } - - /** - * Test the __clone() magic function. - * - * @group examples - * @dataProvider getExamples - * - * @param string $class - * @param string $template - * @param string $output - */ - public function test__clone($class, $template, $output) { - if (isset($this->knownIssues[$class])) { - return $this->markTestSkipped($this->knownIssues[$class]); - } - - $m = new $class; - $n = clone $m; - - $n_output = $n->render($template); - - $o = clone $n; - - $this->assertEquals($m->render($template), $n_output); - $this->assertEquals($n_output, $o->render($template)); - - $this->assertNotSame($m, $n); - $this->assertNotSame($n, $o); - $this->assertNotSame($m, $o); - } - - /** - * Test everything in the `examples` directory. - * - * @group examples - * @dataProvider getExamples - * - * @param string $class - * @param string $template - * @param string $output - */ - public function testExamples($class, $template, $output) { - if (isset($this->knownIssues[$class])) { - return $this->markTestSkipped($this->knownIssues[$class]); - } - - $m = new $class; - $this->assertEquals($output, $m->render($template)); - } - - /** - * Data provider for testExamples method. - * - * Assumes that an `examples` directory exists inside parent directory. - * This examples directory should contain any number of subdirectories, each of which contains - * three files: one Mustache class (.php), one Mustache template (.mustache), and one output file - * (.txt). - * - * This whole mess will be refined later to be more intuitive and less prescriptive, but it'll - * do for now. Especially since it means we can have unit tests :) - * - * @return array - */ - public function getExamples() { - $basedir = dirname(__FILE__) . '/../examples/'; - - $ret = array(); - - $files = new RecursiveDirectoryIterator($basedir); - while ($files->valid()) { - - if ($files->hasChildren() && $children = $files->getChildren()) { - $example = $files->getSubPathname(); - $class = null; - $template = null; - $output = null; - - foreach ($children as $file) { - if (!$file->isFile()) continue; - - $filename = $file->getPathname(); - $info = pathinfo($filename); - - if (isset($info['extension'])) { - switch($info['extension']) { - case 'php': - $class = $info['filename']; - include_once($filename); - break; - - case 'mustache': - $template = file_get_contents($filename); - break; - - case 'txt': - $output = file_get_contents($filename); - break; - } - } - } - - if (!empty($class)) { - $ret[$example] = array($class, $template, $output); - } + private static function rmdir($path) { + $path = rtrim($path, '/').'/'; + $handle = opendir($path); + while (($file = readdir($handle)) !== false) { + if ($file == '.' || $file == '..') { + continue; } - $files->next(); + $fullpath = $path.$file; + if (is_dir($fullpath)) { + self::rmdir($fullpath); + } else { + unlink($fullpath); + } } - return $ret; + + closedir($handle); + rmdir($path); + } + + public function testConstructor() { + $loader = new StringLoader; + $partialsLoader = new ArrayLoader; + $mustache = new Mustache(array( + 'template_class_prefix' => '__whot__', + 'cache' => self::$tempDir, + 'loader' => $loader, + 'partials_loader' => $partialsLoader, + 'partials' => array( + 'foo' => '{{ foo }}', + ), + 'charset' => 'ISO-8859-1', + )); + + $this->assertSame($loader, $mustache->getLoader()); + $this->assertSame($partialsLoader, $mustache->getPartialsLoader()); + $this->assertEquals('{{ foo }}', $partialsLoader->load('foo')); + $this->assertContains('__whot__', $mustache->getTemplateClassName('{{ foo }}')); + $this->assertEquals('ISO-8859-1', $mustache->getCharset()); + } + + public function testSettingServices() { + $loader = new StringLoader; + $tokenizer = new Tokenizer; + $parser = new Parser; + $compiler = new Compiler; + $mustache = new Mustache; + + $this->assertNotSame($loader, $mustache->getLoader()); + $mustache->setLoader($loader); + $this->assertSame($loader, $mustache->getLoader()); + + $this->assertNotSame($loader, $mustache->getPartialsLoader()); + $mustache->setPartialsLoader($loader); + $this->assertSame($loader, $mustache->getPartialsLoader()); + + $this->assertNotSame($tokenizer, $mustache->getTokenizer()); + $mustache->setTokenizer($tokenizer); + $this->assertSame($tokenizer, $mustache->getTokenizer()); + + $this->assertNotSame($parser, $mustache->getParser()); + $mustache->setParser($parser); + $this->assertSame($parser, $mustache->getParser()); + + $this->assertNotSame($compiler, $mustache->getCompiler()); + $mustache->setCompiler($compiler); + $this->assertSame($compiler, $mustache->getCompiler()); } /** - * @group delimiters + * @group functional */ - public function testCrazyDelimiters() { - $m = new Mustache(null, array('result' => 'success')); - $this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]')); - $this->assertEquals('success', $m->render('{{=(( ))=}}(( result ))')); - $this->assertEquals('success', $m->render('{{={$ $}=}}{$ result $}')); - $this->assertEquals('success', $m->render('{{=<.. ..>=}}<.. result ..>')); - $this->assertEquals('success', $m->render('{{=^^ ^^}}^^ result ^^')); - $this->assertEquals('success', $m->render('{{=// \\\\}}// result \\\\')); + public function testCache() { + $mustache = new Mustache(array( + 'template_class_prefix' => '__whot__', + 'cache' => self::$tempDir, + )); + + $source = '{{ foo }}'; + $template = $mustache->loadTemplate($source); + $className = $mustache->getTemplateClassName($source); + $fileName = self::$tempDir . '/' . $className . '.php'; + $this->assertInstanceOf($className, $template); + $this->assertFileExists($fileName); + $this->assertContains("\nclass $className extends \Mustache\Template", file_get_contents($fileName)); } /** - * @group delimiters + * @group functional + * @expectedException \RuntimeException */ - public function testResetDelimiters() { - $m = new Mustache(null, array('result' => 'success')); - $this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]')); - $this->assertEquals('success', $m->render('{{=<< >>=}}<< result >>')); - $this->assertEquals('success', $m->render('{{=<% %>=}}<% result %>')); + public function testCacheFailsThrowException() { + global $mustacheFilesystemRenameHax; + + $mustacheFilesystemRenameHax = true; + + $mustache = new Mustache(array('cache' => self::$tempDir)); + $mustache->loadTemplate('{{ foo }}'); } /** - * @group delimiters + * @expectedException \RuntimeException */ - public function testStickyDelimiters() { - $m = new Mustache(null, array('result' => 'FAIL')); - $this->assertEquals('{{ result }}', $m->render('{{=[[ ]]=}}{{ result }}[[={{ }}=]]')); - $this->assertEquals('{{#result}}{{/result}}', $m->render('{{=[[ ]]=}}{{#result}}{{/result}}[[={{ }}=]]')); - $this->assertEquals('{{ result }}', $m->render('{{=[[ ]]=}}[[#result]]{{ result }}[[/result]][[={{ }}=]]')); - $this->assertEquals('{{ result }}', $m->render('{{#result}}{{=[[ ]]=}}{{ result }}[[/result]][[^result]][[={{ }}=]][[ result ]]{{/result}}')); - } + public function testImmutablePartialsLoadersThrowException() { + $mustache = new Mustache(array( + 'partials_loader' => new StringLoader, + )); - /** - * @group sections - * @dataProvider poorlyNestedSections - * @expectedException MustacheException - */ - public function testPoorlyNestedSections($template) { - $m = new Mustache($template); - $m->render(); - } - - public function poorlyNestedSections() { - return array( - array('{{#foo}}'), - array('{{#foo}}{{/bar}}'), - array('{{#foo}}{{#bar}}{{/foo}}'), - array('{{#foo}}{{#bar}}{{/foo}}{{/bar}}'), - array('{{#foo}}{{/bar}}{{/foo}}'), - ); - } - - /** - * Ensure that Mustache doesn't double-render sections (allowing mustache injection). - * - * @group sections - */ - public function testMustacheInjection() { - $template = '{{#foo}}{{bar}}{{/foo}}'; - $view = array( - 'foo' => true, - 'bar' => '{{win}}', - 'win' => 'FAIL', - ); - - $m = new Mustache($template, $view); - $this->assertEquals('{{win}}', $m->render()); + $mustache->setPartials(array('foo' => '{{ foo }}')); } } -class MustacheExposedOptionsStub extends Mustache { - public function getPragmas() { - return $this->_pragmas; - } - public function getCharset() { - return $this->_charset; - } - public function getDelimiters() { - return array($this->_otag, $this->_ctag); - } + +// It's prob'ly best if you ignore this bit. + +namespace Mustache; + +function rename($a, $b) { + global $mustacheFilesystemRenameHax; + + return ($mustacheFilesystemRenameHax) ? false : \rename($a, $b); } diff --git a/test/Mustache/Test/ParserTest.php b/test/Mustache/Test/ParserTest.php new file mode 100644 index 0000000..d234182 --- /dev/null +++ b/test/Mustache/Test/ParserTest.php @@ -0,0 +1,157 @@ +assertEquals($expected, $parser->parse($tokens)); + } + + public function getTokenSets() + { + return array( + array( + array(), + array() + ), + + array( + array('text'), + array('text') + ), + + array( + array(array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name' + )), + array(array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name' + )), + ), + + array( + array( + 'foo', + array( + Tokenizer::TAG => '^', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name' + ), + array( + Tokenizer::TAG => '/', + Tokenizer::INDEX => 456, + Tokenizer::NAME => 'parent' + ), + 'bar', + ), + array( + 'foo', + array( + Tokenizer::TAG => '^', + Tokenizer::NAME => 'parent', + Tokenizer::INDEX => 123, + Tokenizer::END => 456, + Tokenizer::NODES => array( + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name' + ), + ), + ), + 'bar', + ), + ), + + ); + } + + /** + * @dataProvider getBadParseTrees + * @expectedException \LogicException + */ + public function testParserThrowsExceptions($tokens) { + $parser = new Parser; + $parser->parse($tokens); + } + + public function getBadParseTrees() { + return array( + // no close + array( + array( + array( + Tokenizer::TAG => '#', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + ), + ), + + // no close inverted + array( + array( + array( + Tokenizer::TAG => '^', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + ), + ), + + // no opening inverted + array( + array( + array( + Tokenizer::TAG => '/', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + ), + ), + + // weird nesting + array( + array( + array( + Tokenizer::TAG => '#', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + array( + Tokenizer::TAG => '#', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'child' + ), + array( + Tokenizer::TAG => '/', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'parent' + ), + array( + Tokenizer::TAG => '/', + Tokenizer::INDEX => 123, + Tokenizer::NAME => 'child' + ), + ), + ), + ); + } +} diff --git a/test/Mustache/Test/TemplateTest.php b/test/Mustache/Test/TemplateTest.php new file mode 100644 index 0000000..b67a25f --- /dev/null +++ b/test/Mustache/Test/TemplateTest.php @@ -0,0 +1,43 @@ +assertSame($mustache, $template->getMustache()); + } + + public function testRendering() { + $rendered = '<< wheee >>'; + $mustache = new Mustache; + $template = new TemplateStub($mustache); + $template->rendered = $rendered; + $context = new Context; + + $this->assertEquals($rendered, $template()); + $this->assertEquals($rendered, $template->render()); + $this->assertEquals($rendered, $template->renderInternal($context)); + $this->assertEquals($rendered, $template->render(array('foo' => 'bar'))); + } +} + +class TemplateStub extends Template { + public $rendered; + + public function getMustache() { + return $this->mustache; + } + + public function renderInternal(Context $context) { + return $this->rendered; + } +} diff --git a/test/Mustache/Test/TokenizerTest.php b/test/Mustache/Test/TokenizerTest.php new file mode 100644 index 0000000..53f7b4f --- /dev/null +++ b/test/Mustache/Test/TokenizerTest.php @@ -0,0 +1,117 @@ +assertSame($expected, $tokenizer->scan($text, $delimiters)); + } + + public function getTokens() { + return array( + array( + 'text', + null, + array('text'), + ), + + array( + 'text', + '<<< >>>', + array('text'), + ), + + array( + '{{ name }}', + null, + array( + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name', + Tokenizer::OTAG => '{{', + Tokenizer::CTAG => '}}', + Tokenizer::INDEX => 10, + ) + ) + ), + + array( + '{{ name }}', + '<<< >>>', + array('{{ name }}'), + ), + + array( + '<<< name >>>', + '<<< >>>', + array( + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'name', + Tokenizer::OTAG => '<<<', + Tokenizer::CTAG => '>>>', + Tokenizer::INDEX => 12, + ) + ) + ), + + array( + "{{{ a }}}\n{{# b }} \n{{= | | =}}| c ||/ b |\n|{ d }|", + null, + array( + array( + Tokenizer::TAG => '{', + Tokenizer::NAME => 'a', + Tokenizer::OTAG => '{{', + Tokenizer::CTAG => '}}', + Tokenizer::INDEX => 8, + ), + "\n", + array( + Tokenizer::TAG => '#', + Tokenizer::NAME => 'b', + Tokenizer::OTAG => '{{', + Tokenizer::CTAG => '}}', + Tokenizer::INDEX => 18, + ), + null, + array( + Tokenizer::TAG => '_v', + Tokenizer::NAME => 'c', + Tokenizer::OTAG => '|', + Tokenizer::CTAG => '|', + Tokenizer::INDEX => 37, + ), + array( + Tokenizer::TAG => '/', + Tokenizer::NAME => 'b', + Tokenizer::OTAG => '|', + Tokenizer::CTAG => '|', + Tokenizer::INDEX => 37, + ), + "\n", + array( + Tokenizer::TAG => '{', + Tokenizer::NAME => 'd', + Tokenizer::OTAG => '|', + Tokenizer::CTAG => '|', + Tokenizer::INDEX => 51, + ), + + ) + ), + + + ); + } +}