From b0d288cb183f984e627e179d9e23a0bb09fae845 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 16 Oct 2023 23:56:11 +0900 Subject: [PATCH] Complete unit tests for all the features of template parser v2 --- .../parsers/TemplateParserV2Test.php | 655 +++++++++++++++++- 1 file changed, 646 insertions(+), 9 deletions(-) diff --git a/tests/unit/framework/parsers/TemplateParserV2Test.php b/tests/unit/framework/parsers/TemplateParserV2Test.php index 9a8ade8d5..c011d3860 100644 --- a/tests/unit/framework/parsers/TemplateParserV2Test.php +++ b/tests/unit/framework/parsers/TemplateParserV2Test.php @@ -173,6 +173,16 @@ class TemplateParserV2Test extends \Codeception\Test\Unit $target = ""; $this->assertEquals($target, $this->_parse($source)); + // External script + $source = ''; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + + // External webfont + $source = ''; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + // Path relative to Rhymix installation directory $source = ''; $target = ""; @@ -214,6 +224,16 @@ class TemplateParserV2Test extends \Codeception\Test\Unit $target = ""; $this->assertEquals($target, $this->_parse($source)); + // Blade-style external script + $source = "@load ('//cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js')"; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + + // Blade-style external webfont + $source = "@load('https://fonts.googleapis.com/css2?family=Roboto&display=swap')"; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + // Blade-style path relative to Rhymix installation directory $source = '@load ("^/common/js/foobar.js")'; $target = ""; @@ -289,52 +309,669 @@ class TemplateParserV2Test extends \Codeception\Test\Unit public function testOutputFilters() { + // Filters with no whitespace + $source = '{$foo|upper|noescape}'; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + // Randomly distributed whitespace + $source = '{$foo | upper |noescape }'; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Pipe character in filter option + $source = "{\$foo|join:'|'|noescape}"; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Pipe character in filter option, escaped + $source = "{\$foo|join:'foo\|bar'|noescape}"; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Pipe character in OR operator + $source = '{$foo || $bar | noescape}'; + $target = "foo || \$__Context->bar; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Autoescape + $source = '{{ $foo|autoescape }}'; + $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Autolang + $source = '{{ $foo|autolang }}'; + $target = "\w+$/', \$__Context->foo ?? '') ? (\$__Context->foo ?? '') : htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Escape + $source = '{{ $foo|escape }}'; + $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', true); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Noescape + $source = '{{ $foo|escape|noescape }}'; + $target = "foo ?? ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Escape for Javascript + $source = '{{ $foo|js }}'; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Escape for Javascript (alternate name) + $source = '{{ $foo|escapejs }}'; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // JSON using context-aware escape + $source = '{{ $foo|json }}'; + $target = implode('', [ + "config->context === 'JS' ? ", + "(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)) : ", + "htmlspecialchars(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES), \ENT_QUOTES, 'UTF-8', false); ?>", + ]); + $this->assertEquals($target, $this->_parse($source)); + + // strip_tags + $source = '{{ $foo|strip }}'; + $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // strip_tags (alternate name) + $source = '{{ $foo|upper|strip_tags }}'; + $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Trim + $source = '{{ $foo|trim|noescape }}'; + $target = "foo ?? ''); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // URL encode + $source = '{{ $foo|trim|urlencode }}'; + $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Lowercase + $source = '{{ $foo|trim|lower }}'; + $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Uppercase + $source = '{{ $foo|upper|escape }}'; + $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', true); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // nl2br() + $source = '{{ $foo|nl2br }}'; + $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // nl2br() with gratuitous escape + $source = '{{ $foo|nl2br|escape }}'; + $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)), \ENT_QUOTES, 'UTF-8', true); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Array join (default joiner is comma) + $source = '{{ $foo|join }}'; + $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Array join (custom joiner) + $source = '{{ $foo|join:"!@!" }}'; + $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Date conversion (default format) + $source = '{{ $item->regdate | date }}'; + $target = "item->regdate ?? ''), 'Y-m-d H:i:s'), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Date conversion (custom format) + $source = "{{ \$item->regdate | date:'n/j H:i' }}"; + $target = "item->regdate ?? ''), 'n/j H:i'), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Date conversion (custom format in variable) + $source = "{{ \$item->regdate | date:\$format }}"; + $target = "item->regdate ?? ''), \$__Context->format), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number format + $source = '{{ $num | format }}'; + $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number format (alternate name) + $source = '{{ $num | number_format }}'; + $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number format (custom format) + $source = '{{ $num | number_format:6 }}'; + $target = "num ?? '', '6'), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number format (custom format in variable) + $source = '{{ $num | number_format:$digits }}'; + $target = "num ?? '', \$__Context->digits), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number shorten + $source = '{{ $num | shorten }}'; + $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number shorten (alternate name) + $source = '{{ $num | number_shorten }}'; + $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Number shorten (custom format) + $source = '{{ $num | number_shorten:1 }}'; + $target = "num ?? '', '1'), \ENT_QUOTES, 'UTF-8', false); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Link + $source = '{{ $foo|link }}'; + $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . '\">' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Link (custom link text) + $source = '{{ $foo|link:"Hello World" }}'; + $target = "' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Link (custom link text in variable) + $source = '{{ $foo|link:$bar->baz[0] }}'; + $target = "bar->baz[0], \ENT_QUOTES, 'UTF-8', false)) . '\">' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + } + + public function testVariableScopeConversion() + { + // Local variable + $source = '{$foo|noescape}'; + $target = "foo ?? ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Class and array keys + $source = '{!! ClassName::getInstance()->$foo[$bar] !!}'; + $target = "{\$__Context->foo}[\$__Context->bar]; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Superglobals + $source = "{!! \$_SERVER['HTTP_USER_AGENT'] . \$GLOBALS[\$_GET['foo']] !!}"; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + + // $this + $source = "{!! \$this->func(\$args) !!}"; + $target = "func(\$__Context->args); ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // $lang + $source = "{!! \$lang->cmd_yes !!}"; + $target = "lang->cmd_yes ?? ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // $loop + $source = "{!! \$loop->first !!}"; + $target = "first ?? ''; ?>"; + $this->assertEquals($target, $this->_parse($source)); + + // Escaped dollar sign + $source = "{!! \\\$escaped !!}"; + $target = ""; + $this->assertEquals($target, $this->_parse($source)); + + // Escaped and unescaped variables used together in closure + $source = "{!! (function(\\\$i) use(\$__Context) { return \\\$i * \$j; })(\$k); !!}"; + $target = "j; })(\$__Context->k);; ?>"; + $this->assertEquals($target, $this->_parse($source)); } public function testPathConversion() { + // Image + $source = 'foo'; + $target = 'foo'; + $this->assertEquals($target, $this->_parse($source)); + //