From e8f99bb04e494265ba78670156951696b3f40bcb Mon Sep 17 00:00:00 2001 From: Mathew Davies Date: Sat, 2 Oct 2010 21:05:37 +0100 Subject: [PATCH 01/10] Use array_key_exists instead of isset for checking array values. --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 2b7551c..35377e8 100644 --- a/Mustache.php +++ b/Mustache.php @@ -608,7 +608,7 @@ class Mustache { } else if (isset($view->$tag_name)) { return $view->$tag_name; } - } else if (isset($view[$tag_name])) { + } else if (array_key_exists ($tag_name, $view)) { return $view[$tag_name]; } } From 65770a03f65fc2fea05c3d3c4af4aa06deffbbf4 Mon Sep 17 00:00:00 2001 From: Neuman Vong Date: Fri, 29 Oct 2010 14:05:55 -0700 Subject: [PATCH 02/10] Use is_callable instead of method_exists To use objects implementing __call(). Closes #18. --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 2b7551c..da79733 100644 --- a/Mustache.php +++ b/Mustache.php @@ -603,7 +603,7 @@ class Mustache { protected function _findVariableInContext($tag_name, $context) { foreach ($context as $view) { if (is_object($view)) { - if (method_exists($view, $tag_name)) { + if (is_callable(array($view, $tag_name))) { return $view->$tag_name(); } else if (isset($view->$tag_name)) { return $view->$tag_name; From f2d77ad6955df90dd1175d5947b6408278cf91dc Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 30 Oct 2010 17:13:27 -0400 Subject: [PATCH 03/10] Whitespace --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 35377e8..2766e15 100644 --- a/Mustache.php +++ b/Mustache.php @@ -608,7 +608,7 @@ class Mustache { } else if (isset($view->$tag_name)) { return $view->$tag_name; } - } else if (array_key_exists ($tag_name, $view)) { + } else if (array_key_exists($tag_name, $view)) { return $view[$tag_name]; } } From 35c725089be88d7a044a109a2d9cea594b6eb1dd Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 30 Oct 2010 18:06:39 -0400 Subject: [PATCH 04/10] Revert "Merge branch 'luciferous-master' into dev" This reverts commit 9202a37cf517a24b17fb554adbab5c6bfbb227bd, reversing changes made to 54b5800d420da0dea307fa79518f181f3dbe5554. Using `is_callable` is problematic as the `__call` magic method interferes with context all over the place. See http://hile.mn/buBS11 and http://hile.mn/9vy1S6 --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index a8abf74..4fded45 100644 --- a/Mustache.php +++ b/Mustache.php @@ -604,7 +604,7 @@ class Mustache { protected function _findVariableInContext($tag_name, $context) { foreach ($context as $view) { if (is_object($view)) { - if (is_callable(array($view, $tag_name))) { + if (method_exists($view, $tag_name)) { return $view->$tag_name(); } else if (isset($view->$tag_name)) { return $view->$tag_name; From 38accdaf89cd054f85db10b0471b608312e7fff4 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 03:16:18 -0500 Subject: [PATCH 05/10] More correct sections implementation. Allows nested sections of the same name, throws exceptions on incorrectly nested sections, e.g.: {{#foo}}{{#bar}}{{/foo}}{{/bar}} Add a set of tests for incorrectly nested sections. --- Mustache.php | 83 +++++++++++++++++++++++++++++++++++-------- test/MustacheTest.php | 20 +++++++++++ 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/Mustache.php b/Mustache.php index 4fded45..a41502b 100644 --- a/Mustache.php +++ b/Mustache.php @@ -191,7 +191,7 @@ class Mustache { * @return string Rendered Mustache template. */ protected function _renderTemplate($template) { - $template = $this->_renderSection($template); + $template = $this->_renderSections($template); return $this->_renderTags($template); } @@ -202,18 +202,9 @@ class Mustache { * @param string $template * @return string */ - protected function _renderSection($template) { - $otag = preg_quote($this->_otag, '/'); - $ctag = preg_quote($this->_ctag, '/'); - $regex = '/' . $otag . '(\\^|\\#)\\s*(.+?)\\s*' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\s*\\2\\s*' . $ctag . '\\s*/ms'; - - $matches = array(); - while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE)) { - $section = $matches[0][0]; - $offset = $matches[0][1]; - $type = $matches[1][0]; - $tag_name = trim($matches[2][0]); - $content = $matches[3][0]; + protected function _renderSections($template) { + while ($section_data = $this->_findSection($template)) { + list($section, $offset, $type, $tag_name, $content) = $section_data; $replace = ''; $val = $this->_getVariable($tag_name); @@ -267,6 +258,71 @@ class Mustache { return $template; } + /** + * Extract a section from $template. + * + * This is a helper function to find sections needed by _renderSections. + * + * @access protected + * @param string $template + * @return array $section, $offset, $type, $tag_name and $content + */ + protected function _findSection($template) { + $otag = preg_quote($this->_otag, '/'); + $ctag = preg_quote($this->_ctag, '/'); + $regex = '/' . $otag . '([\\^\\#\\/])\\s*(.+?)\\s*' . $ctag . '\\s*/ms'; + + $section_start = null; + $section_type = null; + $content_start = null; + + $search_offset = 0; + + $section_stack = array(); + $matches = array(); + while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { + + $match = $matches[0][0]; + $offset = $matches[0][1]; + $type = $matches[1][0]; + $tag_name = trim($matches[2][0]); + + $search_offset = $offset + strlen($match); + + switch ($type) { + case '^': + case '#': + if (empty($section_stack)) { + $section_start = $offset; + $section_type = $type; + $content_start = $search_offset; + } + array_push($section_stack, $tag_name); + break; + case '/': + if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) { + if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) { + throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION); + } + } + + if (empty($section_stack)) { + $section = substr($template, $section_start, $search_offset - $section_start); + $content = substr($template, $content_start, $offset - $content_start); + + return array($section, $section_start, $section_type, $tag_name, $content); + } + break; + } + } + + if (!empty($section_stack)) { + if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) { + throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION); + } + } + } + /** * Initialize pragmas and remove all pragma tags. * @@ -353,7 +409,6 @@ class Mustache { return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array(); } - /** * Check whether this Mustache instance throws a given exception. * diff --git a/test/MustacheTest.php b/test/MustacheTest.php index 7269b36..5c389d5 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -333,4 +333,24 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals('success', $m->render('{{=<< >>=}}<< result >>')); $this->assertEquals('success', $m->render('{{=<% %>=}}<% result %>')); } + + /** + * @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}}'), + ); + } } \ No newline at end of file From 943aa8849adda020461e551379422dfe59063c7f Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 03:31:02 -0500 Subject: [PATCH 06/10] Add test groups for easier test breakdown. --- test/MustacheExceptionTest.php | 15 ++++++++++ test/MustacheObjectSectionTest.php | 3 ++ test/MustachePragmaDotNotationTest.php | 3 ++ test/MustachePragmaImplicitIteratorTest.php | 3 ++ test/MustachePragmaTest.php | 3 ++ test/MustachePragmaUnescapedTest.php | 3 ++ test/MustacheTest.php | 33 ++++++++++----------- 7 files changed, 45 insertions(+), 18 deletions(-) diff --git a/test/MustacheExceptionTest.php b/test/MustacheExceptionTest.php index 39a0f5b..a4e0787 100644 --- a/test/MustacheExceptionTest.php +++ b/test/MustacheExceptionTest.php @@ -15,6 +15,7 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { } /** + * @group interpolation * @expectedException MustacheException */ public function testThrowsUnknownVariableException() { @@ -22,6 +23,7 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { } /** + * @group sections * @expectedException MustacheException */ public function testThrowsUnclosedSectionException() { @@ -29,6 +31,7 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { } /** + * @group sections * @expectedException MustacheException */ public function testThrowsUnexpectedCloseSectionException() { @@ -36,6 +39,7 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { } /** + * @group partials * @expectedException MustacheException */ public function testThrowsUnknownPartialException() { @@ -43,25 +47,36 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { } /** + * @group pragmas * @expectedException MustacheException */ public function testThrowsUnknownPragmaException() { $this->pickyMustache->render('{{%SWEET-MUSTACHE-BRO}}'); } + /** + * @group sections + */ public function testDoesntThrowUnclosedSectionException() { $this->assertEquals('', $this->slackerMustache->render('{{#unclosed}}')); } + /** + * @group sections + */ public function testDoesntThrowUnexpectedCloseSectionException() { $this->assertEquals('', $this->slackerMustache->render('{{/unopened}}')); } + /** + * @group partials + */ public function testDoesntThrowUnknownPartialException() { $this->assertEquals('', $this->slackerMustache->render('{{>impartial}}')); } /** + * @group pragmas * @expectedException MustacheException */ public function testGetPragmaOptionsThrowsExceptionsIfItThinksYouHaveAPragmaButItTurnsOutYouDont() { diff --git a/test/MustacheObjectSectionTest.php b/test/MustacheObjectSectionTest.php index 17ad2e0..82405d6 100644 --- a/test/MustacheObjectSectionTest.php +++ b/test/MustacheObjectSectionTest.php @@ -2,6 +2,9 @@ require_once '../Mustache.php'; +/** + * @group sections + */ class MustacheObjectSectionTest extends PHPUnit_Framework_TestCase { public function testBasicObject() { $alpha = new Alpha(); diff --git a/test/MustachePragmaDotNotationTest.php b/test/MustachePragmaDotNotationTest.php index 488962b..56f6f11 100644 --- a/test/MustachePragmaDotNotationTest.php +++ b/test/MustachePragmaDotNotationTest.php @@ -2,6 +2,9 @@ require_once '../Mustache.php'; +/** + * @group pragmas + */ class MustachePragmaDotNotationTest extends PHPUnit_Framework_TestCase { public function testDotTraversal() { diff --git a/test/MustachePragmaImplicitIteratorTest.php b/test/MustachePragmaImplicitIteratorTest.php index 207a8c6..20fdcce 100644 --- a/test/MustachePragmaImplicitIteratorTest.php +++ b/test/MustachePragmaImplicitIteratorTest.php @@ -2,6 +2,9 @@ require_once '../Mustache.php'; +/** + * @group pragmas + */ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase { public function testEnablePragma() { diff --git a/test/MustachePragmaTest.php b/test/MustachePragmaTest.php index 086e458..8be6e66 100644 --- a/test/MustachePragmaTest.php +++ b/test/MustachePragmaTest.php @@ -2,6 +2,9 @@ require_once '../Mustache.php'; +/** + * @group pragmas + */ class MustachePragmaTest extends PHPUnit_Framework_TestCase { public function testUnknownPragmaException() { diff --git a/test/MustachePragmaUnescapedTest.php b/test/MustachePragmaUnescapedTest.php index 8331312..df6d94c 100644 --- a/test/MustachePragmaUnescapedTest.php +++ b/test/MustachePragmaUnescapedTest.php @@ -2,6 +2,9 @@ require_once '../Mustache.php'; +/** + * @group pragmas + */ class MustachePragmaUnescapedTest extends PHPUnit_Framework_TestCase { public function testPragmaUnescaped() { diff --git a/test/MustacheTest.php b/test/MustacheTest.php index 7269b36..fba0327 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -132,8 +132,7 @@ class MustacheTest extends PHPUnit_Framework_TestCase { /** * Test render() with data. * - * @access public - * @return void + * @group interpolation */ public function testRenderWithData() { $m = new Mustache('{{first_name}} {{last_name}}'); @@ -141,6 +140,9 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $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'))); @@ -150,8 +152,7 @@ class MustacheTest extends PHPUnit_Framework_TestCase { /** * Mustache should allow newlines (and other whitespace) in comments and all other tags. * - * @access public - * @return void + * @group comments */ public function testNewlinesInComments() { $m = new Mustache("{{! comment \n \t still a comment... }}"); @@ -160,9 +161,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase { /** * Mustache should return the same thing when invoked multiple times. - * - * @access public - * @return void */ public function testMultipleInvocations() { $m = new Mustache('x'); @@ -176,8 +174,7 @@ class MustacheTest extends PHPUnit_Framework_TestCase { /** * Mustache should return the same thing when invoked multiple times. * - * @access public - * @return void + * @group interpolation */ public function testMultipleInvocationsWithTags() { $m = new Mustache('{{one}} {{two}}', array('one' => 'foo', 'two' => 'bar')); @@ -190,9 +187,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase { /** * Mustache should not use templates passed to the render() method for subsequent invocations. - * - * @access public - * @return void */ public function testResetTemplateForMultipleInvocations() { $m = new Mustache('Sirve.'); @@ -205,15 +199,14 @@ class MustacheTest extends PHPUnit_Framework_TestCase { } /** - * testClone function. + * Test the __clone() magic function. * * @group examples * @dataProvider getExamples - * @access public + * * @param string $class * @param string $template * @param string $output - * @return void */ public function test__clone($class, $template, $output) { if (isset($this->knownIssues[$class])) { @@ -240,11 +233,10 @@ class MustacheTest extends PHPUnit_Framework_TestCase { * * @group examples * @dataProvider getExamples - * @access public + * * @param string $class * @param string $template * @param string $output - * @return void */ public function testExamples($class, $template, $output) { if (isset($this->knownIssues[$class])) { @@ -266,7 +258,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase { * 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 :) * - * @access public * @return array */ public function getExamples() { @@ -317,6 +308,9 @@ class MustacheTest extends PHPUnit_Framework_TestCase { return $ret; } + /** + * @group delimiters + */ public function testCrazyDelimiters() { $m = new Mustache(null, array('result' => 'success')); $this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]')); @@ -327,6 +321,9 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals('success', $m->render('{{=// \\\\}}// result \\\\')); } + /** + * @group delimiters + */ public function testResetDelimiters() { $m = new Mustache(null, array('result' => 'success')); $this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]')); From bb04ccd05e833f1534845e2d4dcaf43d4ab61c87 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 03:36:41 -0500 Subject: [PATCH 07/10] Add an inverted section test to base test case. --- test/MustacheExceptionTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/MustacheExceptionTest.php b/test/MustacheExceptionTest.php index a4e0787..98bddce 100644 --- a/test/MustacheExceptionTest.php +++ b/test/MustacheExceptionTest.php @@ -30,6 +30,14 @@ class MustacheExceptionTest extends PHPUnit_Framework_TestCase { $this->pickyMustache->render('{{#unclosed}}'); } + /** + * @group sections + * @expectedException MustacheException + */ + public function testThrowsUnclosedInvertedSectionException() { + $this->pickyMustache->render('{{^unclosed}}'); + } + /** * @group sections * @expectedException MustacheException From 1282b8104a61ccb6c3c783e6668d409555cf807c Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 03:52:04 -0500 Subject: [PATCH 08/10] A broken example for issue #12 - recursive/nested sections. --- examples/sections_nested/SectionsNested.php | 33 +++++++++++++++++++ .../sections_nested/sections_nested.mustache | 7 ++++ examples/sections_nested/sections_nested.txt | 12 +++++++ 3 files changed, 52 insertions(+) create mode 100644 examples/sections_nested/SectionsNested.php create mode 100644 examples/sections_nested/sections_nested.mustache create mode 100644 examples/sections_nested/sections_nested.txt diff --git a/examples/sections_nested/SectionsNested.php b/examples/sections_nested/SectionsNested.php new file mode 100644 index 0000000..ec01b75 --- /dev/null +++ b/examples/sections_nested/SectionsNested.php @@ -0,0 +1,33 @@ + 'Von Kaiser', + 'enemies' => array( + array('name' => 'Super Macho Man'), + array('name' => 'Piston Honda'), + array('name' => 'Mr. Sandman'), + ) + ), + array( + 'name' => 'Mike Tyson', + 'enemies' => array( + array('name' => 'Soda Popinski'), + array('name' => 'King Hippo'), + array('name' => 'Great Tiger'), + array('name' => 'Glass Joe'), + ) + ), + array( + 'name' => 'Don Flamenco', + 'enemies' => array( + array('name' => 'Bald Bull'), + ) + ), + ); + } +} \ No newline at end of file diff --git a/examples/sections_nested/sections_nested.mustache b/examples/sections_nested/sections_nested.mustache new file mode 100644 index 0000000..9f8007d --- /dev/null +++ b/examples/sections_nested/sections_nested.mustache @@ -0,0 +1,7 @@ +Enemies of {{ name }}: +{{# enemies }} +{{ name }} ... who also has enemies: +{{# enemies }} +--> {{ name }} +{{/ enemies }} +{{/ enemies }} \ No newline at end of file diff --git a/examples/sections_nested/sections_nested.txt b/examples/sections_nested/sections_nested.txt new file mode 100644 index 0000000..72c44d0 --- /dev/null +++ b/examples/sections_nested/sections_nested.txt @@ -0,0 +1,12 @@ +Enemies of Little Mac: +Von Kaiser ... who also has enemies: +--> Super Macho Man +--> Piston Honda +--> Mr. Sandman +Mike Tyson ... who also has enemies: +--> Soda Popinski +--> King Hippo +--> Great Tiger +--> Glass Joe +Don Flamenco ... who also has enemies: +--> Bald Bull From fef6234954f7b6187cad99808f12b112694ff772 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 04:02:19 -0500 Subject: [PATCH 09/10] Test case for recursive sections with implicit iterators. --- test/MustachePragmaImplicitIteratorTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/MustachePragmaImplicitIteratorTest.php b/test/MustachePragmaImplicitIteratorTest.php index 20fdcce..d582325 100644 --- a/test/MustachePragmaImplicitIteratorTest.php +++ b/test/MustachePragmaImplicitIteratorTest.php @@ -50,4 +50,11 @@ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase { $this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR iterator=i}}{{%DOT-NOTATION}}{{#items}}{{i.name}}{{/items}}')); } + public function testRecursiveSections() { + $m = new Mustache( + '{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{.}}{{/.}}{{/items}}', + array('items' => array(array('a', 'b', 'c'), array('d', 'e', 'f'))) + ); + $this->assertEquals('abcdef', $m->render()); + } } \ No newline at end of file From d026315a2c1ff29d2fda1fbd9776635da2adcdbe Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 24 Nov 2010 04:10:37 -0500 Subject: [PATCH 10/10] Even more ludicrous recursive section / implicit iterator tests. --- test/MustachePragmaImplicitIteratorTest.php | 33 +++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/test/MustachePragmaImplicitIteratorTest.php b/test/MustachePragmaImplicitIteratorTest.php index d582325..9a88359 100644 --- a/test/MustachePragmaImplicitIteratorTest.php +++ b/test/MustachePragmaImplicitIteratorTest.php @@ -50,11 +50,34 @@ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase { $this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR iterator=i}}{{%DOT-NOTATION}}{{#items}}{{i.name}}{{/items}}')); } - public function testRecursiveSections() { - $m = new Mustache( - '{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{.}}{{/.}}{{/items}}', - array('items' => array(array('a', 'b', 'c'), array('d', 'e', 'f'))) + /** + * @dataProvider recursiveSectionData + */ + public function testRecursiveSections($template, $view, $result) { + $m = new Mustache(); + $this->assertEquals($result, $m->render($template, $view)); + } + + public function recursiveSectionData() { + return array( + array( + '{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{.}}{{/.}}{{/items}}', + array('items' => array(array('a', 'b', 'c'), array('d', 'e', 'f'))), + 'abcdef' + ), + array( + '{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{#.}}{{.}}{{/.}}{{/.}}{{/items}}', + array('items' => array(array(array('a', 'b'), array('c')), array(array('d'), array('e', 'f')))), + 'abcdef' + ), + array( + '{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{#items}}{{.}}{{/items}}{{/.}}{{/items}}', + array('items' => array( + array('items' => array('a', 'b', 'c')), + array('items' => array('d', 'e', 'f')), + )), + 'abcdef' + ), ); - $this->assertEquals('abcdef', $m->render()); } } \ No newline at end of file