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();