true, 'verboseOutput' => true ]); // 一時的なviewディレクトリにテンプレートを作る $viewDir = sys_get_temp_dir().'/lbview_'.bin2hex(random_bytes(6)); mkdir("{$viewDir}/common", 0700, true); $tmpl = function(string $src, array $vars = [], string $name = 't') use ($viewDir): string { file_put_contents("{$viewDir}/{$name}.maron", $src); $t = new Template('/', $viewDir); foreach ($vars as $k => $v) $t->assign($k, $v); return $t->fetch($name); }; $test->describe('Maronテンプレート', function($test) use ($tmpl, $viewDir): void { $test->it('変数をエスケープして出力するはず', function($test) use ($tmpl): void { $test->assertEquals('

<b>"x"</b>

', $tmpl('

{{ $v }}

', ['v' => '"x"'])); }); $test->it('{{{ }}}はそのまま出力するはず', function($test) use ($tmpl): void { $test->assertEquals('

x

', $tmpl('

{{{ $v }}}

', ['v' => 'x'])); $test->assertEquals('x', $tmpl('{{{$v}}}', ['v' => 'x'])); }); $test->it('if・elif・elseを処理するはず', function($test) use ($tmpl): void { $src = '{@ if ($n > 1) @}多{@ elif ($n === 1) @}一{@ else @}無{@ endif @}'; $test->assertEquals('多', $tmpl($src, ['n' => 5])); $test->assertEquals('一', $tmpl($src, ['n' => 1])); $test->assertEquals('無', $tmpl($src, ['n' => 0])); }); $test->it('foreachとforを処理するはず', function($test) use ($tmpl): void { $test->assertEquals('a=1,b=2,', $tmpl('{@ foreach ($xs as $k => $v) @}{{ $k }}={{ $v }},{@ endforeach @}', ['xs' => ['a' => 1, 'b' => 2]])); $test->assertEquals('012', $tmpl('{@ for ($i = 0; $i < 3; $i++) @}{{ $i }}{@ endfor @}')); }); $test->it('入れ子のブロックを処理するはず', function($test) use ($tmpl): void { $src = '{@ foreach ($xs as $x) @}{@ if ($x % 2) @}[{{ $x }}]{@ endif @}{@ endforeach @}'; $test->assertEquals('[1][3]', $tmpl($src, ['xs' => [1, 2, 3, 4]])); }); $test->it('変数の代入とPHPのコードを処理するはず', function($test) use ($tmpl): void { $test->assertEquals('200', $tmpl('{$ $x = 100 * 2 $}{{ $x }}')); $test->assertEquals('hi', $tmpl('{! echo "hi"; !}')); }); $test->it('コメントは出力も実行もしないはず', function($test) use ($tmpl): void { // 以前はコメントの中の{{ }}が先に変換され、「*/」でPHPのコメントが壊れた $test->assertEquals('ab', $tmpl('a{# {{ $undefinedVar }} */ #}b')); $test->assertEquals('ab', $tmpl("a{# 複数\n行 #}b")); }); $test->it('インクルードを処理するはず', function($test) use ($tmpl, $viewDir): void { file_put_contents("{$viewDir}/common/head.maron", '

{{ $title }}

'); $test->assertEquals('

T&T

本文', $tmpl('{@ include(common/head) @}本文', ['title' => 'T&T'])); }); $test->it('循環したインクルードで止まるはず', function($test) use ($tmpl, $viewDir): void { file_put_contents("{$viewDir}/common/loop.maron", '{@ include(common/loop) @}'); $test->assertThrows(fn() => $tmpl('{@ include(common/loop) @}'), \RuntimeException::class); }); $test->it('無いテンプレートは例外のはず', function($test) use ($viewDir): void { $test->assertThrows(fn() => (new Template('/', $viewDir))->fetch('nothing'), \RuntimeException::class); $test->assertThrows(fn() => (new Template('/', $viewDir))->render('nothing'), \RuntimeException::class); }); $test->it('「content」等の名前の変数も使えるはず', function($test) use ($tmpl): void { // 以前はextract()の後に$content等が上書きされ、テンプレートのソースが表示された $test->assertEquals('本文/パス', $tmpl('{{ $content }}/{{ $tmplPath }}', ['content' => '本文', 'tmplPath' => 'パス'])); }); $test->it('カスタム関数を呼べるはず', function($test) use ($viewDir): void { file_put_contents("{$viewDir}/f.maron", '[{@ shout(abc, def) @}]'); $t = new Template('/', $viewDir); $t->registerFunction('shout', fn($a, $b) => strtoupper($a.$b)); $test->assertEquals('[ABCDEF]', $t->fetch('f')); }); $test->it('CSSを追加出来るはず', function($test) use ($viewDir): void { file_put_contents("{$viewDir}/css.maron", '{@ foreach ($custCss as $c) @}{{{ $c }}}{@ endforeach @}'); $t = new Template('/', $viewDir); $t->addCss('code'); $test->assertStringContains('href="'.ass_url('style-code.css').'"', $t->fetch('css')); }); $test->it('サブディレクトリ(多言語)のテンプレートを使えるはず', function($test) use ($viewDir): void { mkdir("{$viewDir}/en"); file_put_contents("{$viewDir}/en/index.maron", 'English'); $test->assertEquals('English', (new Template('/en', $viewDir))->fetch('index')); $test->assertEquals('English', (new Template('en/', $viewDir))->fetch('index')); }); $test->it('例外が起きても一時ファイルを残さないはず', function($test) use ($tmpl): void { $before = count(glob(sys_get_temp_dir().'/tmpl_*') ?: []); try { $tmpl('{! throw new \RuntimeException("x"); !}'); } catch (\RuntimeException $e) {} $test->assertEquals($before, count(glob(sys_get_temp_dir().'/tmpl_*') ?: [])); }); }); $rm = function(string $dir) use (&$rm): void { foreach (scandir($dir) ?: [] as $f) { if ($f === '.' || $f === '..') continue; is_dir("{$dir}/{$f}") ? $rm("{$dir}/{$f}") : @unlink("{$dir}/{$f}"); } @rmdir($dir); }; $rm($viewDir); $test->printSummary();